##// END OF EJS Templates
rawdata: update callers in test-revlog-raw...
marmoute -
r43045:74045067 default
parent child Browse files
Show More
@@ -1,452 +1,455 b''
1 1 # test revlog interaction about raw data (flagprocessor)
2 2
3 3 from __future__ import absolute_import, print_function
4 4
5 5 import collections
6 6 import hashlib
7 7 import sys
8 8
9 9 from mercurial import (
10 10 encoding,
11 11 node,
12 12 revlog,
13 13 transaction,
14 14 vfs,
15 15 )
16 16
17 17 from mercurial.revlogutils import (
18 18 deltas,
19 19 flagutil,
20 20 )
21 21
22 22 # TESTTMP is optional. This makes it convenient to run without run-tests.py
23 23 tvfs = vfs.vfs(encoding.environ.get(b'TESTTMP', b'/tmp'))
24 24
25 25 # Enable generaldelta otherwise revlog won't use delta as expected by the test
26 26 tvfs.options = {b'generaldelta': True, b'revlogv1': True,
27 27 b'sparse-revlog': True}
28 28
29 29 # The test wants to control whether to use delta explicitly, based on
30 30 # "storedeltachains".
31 31 revlog.revlog._isgooddeltainfo = lambda self, d, textlen: self._storedeltachains
32 32
33 33 def abort(msg):
34 34 print('abort: %s' % msg)
35 35 # Return 0 so run-tests.py could compare the output.
36 36 sys.exit()
37 37
38 38 # Register a revlog processor for flag EXTSTORED.
39 39 #
40 40 # It simply prepends a fixed header, and replaces '1' to 'i'. So it has
41 41 # insertion and replacement, and may be interesting to test revlog's line-based
42 42 # deltas.
43 43 _extheader = b'E\n'
44 44
45 45 def readprocessor(self, rawtext):
46 46 # True: the returned text could be used to verify hash
47 47 text = rawtext[len(_extheader):].replace(b'i', b'1')
48 48 return text, True
49 49
50 50 def writeprocessor(self, text):
51 51 # False: the returned rawtext shouldn't be used to verify hash
52 52 rawtext = _extheader + text.replace(b'1', b'i')
53 53 return rawtext, False
54 54
55 55 def rawprocessor(self, rawtext):
56 56 # False: do not verify hash. Only the content returned by "readprocessor"
57 57 # can be used to verify hash.
58 58 return False
59 59
60 60 flagutil.addflagprocessor(revlog.REVIDX_EXTSTORED,
61 61 (readprocessor, writeprocessor, rawprocessor))
62 62
63 63 # Utilities about reading and appending revlog
64 64
65 65 def newtransaction():
66 66 # A transaction is required to write revlogs
67 67 report = lambda msg: None
68 68 return transaction.transaction(report, tvfs, {'plain': tvfs}, b'journal')
69 69
70 70 def newrevlog(name=b'_testrevlog.i', recreate=False):
71 71 if recreate:
72 72 tvfs.tryunlink(name)
73 73 rlog = revlog.revlog(tvfs, name)
74 74 return rlog
75 75
76 76 def appendrev(rlog, text, tr, isext=False, isdelta=True):
77 77 '''Append a revision. If isext is True, set the EXTSTORED flag so flag
78 78 processor will be used (and rawtext is different from text). If isdelta is
79 79 True, force the revision to be a delta, otherwise it's full text.
80 80 '''
81 81 nextrev = len(rlog)
82 82 p1 = rlog.node(nextrev - 1)
83 83 p2 = node.nullid
84 84 if isext:
85 85 flags = revlog.REVIDX_EXTSTORED
86 86 else:
87 87 flags = revlog.REVIDX_DEFAULT_FLAGS
88 88 # Change storedeltachains temporarily, to override revlog's delta decision
89 89 rlog._storedeltachains = isdelta
90 90 try:
91 91 rlog.addrevision(text, tr, nextrev, p1, p2, flags=flags)
92 92 return nextrev
93 93 except Exception as ex:
94 94 abort('rev %d: failed to append: %s' % (nextrev, ex))
95 95 finally:
96 96 # Restore storedeltachains. It is always True, see revlog.__init__
97 97 rlog._storedeltachains = True
98 98
99 99 def addgroupcopy(rlog, tr, destname=b'_destrevlog.i', optimaldelta=True):
100 100 '''Copy revlog to destname using revlog.addgroup. Return the copied revlog.
101 101
102 102 This emulates push or pull. They use changegroup. Changegroup requires
103 103 repo to work. We don't have a repo, so a dummy changegroup is used.
104 104
105 105 If optimaldelta is True, use optimized delta parent, so the destination
106 106 revlog could probably reuse it. Otherwise it builds sub-optimal delta, and
107 107 the destination revlog needs more work to use it.
108 108
109 109 This exercises some revlog.addgroup (and revlog._addrevision(text=None))
110 110 code path, which is not covered by "appendrev" alone.
111 111 '''
112 112 class dummychangegroup(object):
113 113 @staticmethod
114 114 def deltachunk(pnode):
115 115 pnode = pnode or node.nullid
116 116 parentrev = rlog.rev(pnode)
117 117 r = parentrev + 1
118 118 if r >= len(rlog):
119 119 return {}
120 120 if optimaldelta:
121 121 deltaparent = parentrev
122 122 else:
123 123 # suboptimal deltaparent
124 124 deltaparent = min(0, parentrev)
125 125 if not rlog.candelta(deltaparent, r):
126 126 deltaparent = -1
127 127 return {b'node': rlog.node(r), b'p1': pnode, b'p2': node.nullid,
128 128 b'cs': rlog.node(rlog.linkrev(r)), b'flags': rlog.flags(r),
129 129 b'deltabase': rlog.node(deltaparent),
130 130 b'delta': rlog.revdiff(deltaparent, r)}
131 131
132 132 def deltaiter(self):
133 133 chain = None
134 134 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
135 135 node = chunkdata[b'node']
136 136 p1 = chunkdata[b'p1']
137 137 p2 = chunkdata[b'p2']
138 138 cs = chunkdata[b'cs']
139 139 deltabase = chunkdata[b'deltabase']
140 140 delta = chunkdata[b'delta']
141 141 flags = chunkdata[b'flags']
142 142
143 143 chain = node
144 144
145 145 yield (node, p1, p2, cs, deltabase, delta, flags)
146 146
147 147 def linkmap(lnode):
148 148 return rlog.rev(lnode)
149 149
150 150 dlog = newrevlog(destname, recreate=True)
151 151 dummydeltas = dummychangegroup().deltaiter()
152 152 dlog.addgroup(dummydeltas, linkmap, tr)
153 153 return dlog
154 154
155 155 def lowlevelcopy(rlog, tr, destname=b'_destrevlog.i'):
156 156 '''Like addgroupcopy, but use the low level revlog._addrevision directly.
157 157
158 158 It exercises some code paths that are hard to reach easily otherwise.
159 159 '''
160 160 dlog = newrevlog(destname, recreate=True)
161 161 for r in rlog:
162 162 p1 = rlog.node(r - 1)
163 163 p2 = node.nullid
164 164 if r == 0 or (rlog.flags(r) & revlog.REVIDX_EXTSTORED):
165 text = rlog.revision(r, raw=True)
165 text = rlog.rawdata(r)
166 166 cachedelta = None
167 167 else:
168 168 # deltaparent cannot have EXTSTORED flag.
169 169 deltaparent = max([-1] +
170 170 [p for p in range(r)
171 171 if rlog.flags(p) & revlog.REVIDX_EXTSTORED == 0])
172 172 text = None
173 173 cachedelta = (deltaparent, rlog.revdiff(deltaparent, r))
174 174 flags = rlog.flags(r)
175 175 ifh = dfh = None
176 176 try:
177 177 ifh = dlog.opener(dlog.indexfile, b'a+')
178 178 if not dlog._inline:
179 179 dfh = dlog.opener(dlog.datafile, b'a+')
180 180 dlog._addrevision(rlog.node(r), text, tr, r, p1, p2, flags,
181 181 cachedelta, ifh, dfh)
182 182 finally:
183 183 if dfh is not None:
184 184 dfh.close()
185 185 if ifh is not None:
186 186 ifh.close()
187 187 return dlog
188 188
189 189 # Utilities to generate revisions for testing
190 190
191 191 def genbits(n):
192 192 '''Given a number n, generate (2 ** (n * 2) + 1) numbers in range(2 ** n).
193 193 i.e. the generated numbers have a width of n bits.
194 194
195 195 The combination of two adjacent numbers will cover all possible cases.
196 196 That is to say, given any x, y where both x, and y are in range(2 ** n),
197 197 there is an x followed immediately by y in the generated sequence.
198 198 '''
199 199 m = 2 ** n
200 200
201 201 # Gray Code. See https://en.wikipedia.org/wiki/Gray_code
202 202 gray = lambda x: x ^ (x >> 1)
203 203 reversegray = dict((gray(i), i) for i in range(m))
204 204
205 205 # Generate (n * 2) bit gray code, yield lower n bits as X, and look for
206 206 # the next unused gray code where higher n bits equal to X.
207 207
208 208 # For gray codes whose higher bits are X, a[X] of them have been used.
209 209 a = [0] * m
210 210
211 211 # Iterate from 0.
212 212 x = 0
213 213 yield x
214 214 for i in range(m * m):
215 215 x = reversegray[x]
216 216 y = gray(a[x] + x * m) & (m - 1)
217 217 assert a[x] < m
218 218 a[x] += 1
219 219 x = y
220 220 yield x
221 221
222 222 def gentext(rev):
223 223 '''Given a revision number, generate dummy text'''
224 224 return b''.join(b'%d\n' % j for j in range(-1, rev % 5))
225 225
226 226 def writecases(rlog, tr):
227 227 '''Write some revisions interested to the test.
228 228
229 229 The test is interested in 3 properties of a revision:
230 230
231 231 - Is it a delta or a full text? (isdelta)
232 232 This is to catch some delta application issues.
233 233 - Does it have a flag of EXTSTORED? (isext)
234 234 This is to catch some flag processor issues. Especially when
235 235 interacted with revlog deltas.
236 236 - Is its text empty? (isempty)
237 237 This is less important. It is intended to try to catch some careless
238 238 checks like "if text" instead of "if text is None". Note: if flag
239 239 processor is involved, raw text may be not empty.
240 240
241 241 Write 65 revisions. So that all combinations of the above flags for
242 242 adjacent revisions are covered. That is to say,
243 243
244 244 len(set(
245 245 (r.delta, r.ext, r.empty, (r+1).delta, (r+1).ext, (r+1).empty)
246 246 for r in range(len(rlog) - 1)
247 247 )) is 64.
248 248
249 249 Where "r.delta", "r.ext", and "r.empty" are booleans matching properties
250 250 mentioned above.
251 251
252 252 Return expected [(text, rawtext)].
253 253 '''
254 254 result = []
255 255 for i, x in enumerate(genbits(3)):
256 256 isdelta, isext, isempty = bool(x & 1), bool(x & 2), bool(x & 4)
257 257 if isempty:
258 258 text = b''
259 259 else:
260 260 text = gentext(i)
261 261 rev = appendrev(rlog, text, tr, isext=isext, isdelta=isdelta)
262 262
263 263 # Verify text, rawtext, and rawsize
264 264 if isext:
265 265 rawtext = writeprocessor(None, text)[0]
266 266 else:
267 267 rawtext = text
268 268 if rlog.rawsize(rev) != len(rawtext):
269 269 abort('rev %d: wrong rawsize' % rev)
270 270 if rlog.revision(rev, raw=False) != text:
271 271 abort('rev %d: wrong text' % rev)
272 if rlog.revision(rev, raw=True) != rawtext:
272 if rlog.rawdata(rev) != rawtext:
273 273 abort('rev %d: wrong rawtext' % rev)
274 274 result.append((text, rawtext))
275 275
276 276 # Verify flags like isdelta, isext work as expected
277 277 # isdelta can be overridden to False if this or p1 has isext set
278 278 if bool(rlog.deltaparent(rev) > -1) and not isdelta:
279 279 abort('rev %d: isdelta is unexpected' % rev)
280 280 if bool(rlog.flags(rev)) != isext:
281 281 abort('rev %d: isext is ineffective' % rev)
282 282 return result
283 283
284 284 # Main test and checking
285 285
286 286 def checkrevlog(rlog, expected):
287 287 '''Check if revlog has expected contents. expected is [(text, rawtext)]'''
288 288 # Test using different access orders. This could expose some issues
289 289 # depending on revlog caching (see revlog._cache).
290 290 for r0 in range(len(rlog) - 1):
291 291 r1 = r0 + 1
292 292 for revorder in [[r0, r1], [r1, r0]]:
293 293 for raworder in [[True], [False], [True, False], [False, True]]:
294 294 nlog = newrevlog()
295 295 for rev in revorder:
296 296 for raw in raworder:
297 t = nlog.revision(rev, raw=raw)
297 if raw:
298 t = nlog.rawdata(rev)
299 else:
300 t = nlog.revision(rev)
298 301 if t != expected[rev][int(raw)]:
299 302 abort('rev %d: corrupted %stext'
300 303 % (rev, raw and 'raw' or ''))
301 304
302 305 slicingdata = [
303 306 ([0, 1, 2, 3, 55, 56, 58, 59, 60],
304 307 [[0, 1], [2], [58], [59, 60]],
305 308 10),
306 309 ([0, 1, 2, 3, 55, 56, 58, 59, 60],
307 310 [[0, 1], [2], [58], [59, 60]],
308 311 10),
309 312 ([-1, 0, 1, 2, 3, 55, 56, 58, 59, 60],
310 313 [[-1, 0, 1], [2], [58], [59, 60]],
311 314 10),
312 315 ]
313 316
314 317 def slicingtest(rlog):
315 318 oldmin = rlog._srmingapsize
316 319 try:
317 320 # the test revlog is small, we remove the floor under which we
318 321 # slicing is diregarded.
319 322 rlog._srmingapsize = 0
320 323 for item in slicingdata:
321 324 chain, expected, target = item
322 325 result = deltas.slicechunk(rlog, chain, targetsize=target)
323 326 result = list(result)
324 327 if result != expected:
325 328 print('slicing differ:')
326 329 print(' chain: %s' % chain)
327 330 print(' target: %s' % target)
328 331 print(' expected: %s' % expected)
329 332 print(' result: %s' % result)
330 333 finally:
331 334 rlog._srmingapsize = oldmin
332 335
333 336 def md5sum(s):
334 337 return hashlib.md5(s).digest()
335 338
336 339 def _maketext(*coord):
337 340 """create piece of text according to range of integers
338 341
339 342 The test returned use a md5sum of the integer to make it less
340 343 compressible"""
341 344 pieces = []
342 345 for start, size in coord:
343 346 num = range(start, start + size)
344 347 p = [md5sum(b'%d' % r) for r in num]
345 348 pieces.append(b'\n'.join(p))
346 349 return b'\n'.join(pieces) + b'\n'
347 350
348 351 data = [
349 352 _maketext((0, 120), (456, 60)),
350 353 _maketext((0, 120), (345, 60)),
351 354 _maketext((0, 120), (734, 60)),
352 355 _maketext((0, 120), (734, 60), (923, 45)),
353 356 _maketext((0, 120), (734, 60), (234, 45)),
354 357 _maketext((0, 120), (734, 60), (564, 45)),
355 358 _maketext((0, 120), (734, 60), (361, 45)),
356 359 _maketext((0, 120), (734, 60), (489, 45)),
357 360 _maketext((0, 120), (123, 60)),
358 361 _maketext((0, 120), (145, 60)),
359 362 _maketext((0, 120), (104, 60)),
360 363 _maketext((0, 120), (430, 60)),
361 364 _maketext((0, 120), (430, 60), (923, 45)),
362 365 _maketext((0, 120), (430, 60), (234, 45)),
363 366 _maketext((0, 120), (430, 60), (564, 45)),
364 367 _maketext((0, 120), (430, 60), (361, 45)),
365 368 _maketext((0, 120), (430, 60), (489, 45)),
366 369 _maketext((0, 120), (249, 60)),
367 370 _maketext((0, 120), (832, 60)),
368 371 _maketext((0, 120), (891, 60)),
369 372 _maketext((0, 120), (543, 60)),
370 373 _maketext((0, 120), (120, 60)),
371 374 _maketext((0, 120), (60, 60), (768, 30)),
372 375 _maketext((0, 120), (60, 60), (260, 30)),
373 376 _maketext((0, 120), (60, 60), (450, 30)),
374 377 _maketext((0, 120), (60, 60), (361, 30)),
375 378 _maketext((0, 120), (60, 60), (886, 30)),
376 379 _maketext((0, 120), (60, 60), (116, 30)),
377 380 _maketext((0, 120), (60, 60), (567, 30), (629, 40)),
378 381 _maketext((0, 120), (60, 60), (569, 30), (745, 40)),
379 382 _maketext((0, 120), (60, 60), (777, 30), (700, 40)),
380 383 _maketext((0, 120), (60, 60), (618, 30), (398, 40), (158, 10)),
381 384 ]
382 385
383 386 def makesnapshot(tr):
384 387 rl = newrevlog(name=b'_snaprevlog3.i', recreate=True)
385 388 for i in data:
386 389 appendrev(rl, i, tr)
387 390 return rl
388 391
389 392 snapshots = [-1, 0, 6, 8, 11, 17, 19, 21, 25, 30]
390 393 def issnapshottest(rlog):
391 394 result = []
392 395 if rlog.issnapshot(-1):
393 396 result.append(-1)
394 397 for rev in rlog:
395 398 if rlog.issnapshot(rev):
396 399 result.append(rev)
397 400 if snapshots != result:
398 401 print('snapshot differ:')
399 402 print(' expected: %s' % snapshots)
400 403 print(' got: %s' % result)
401 404
402 405 snapshotmapall = {0: [6, 8, 11, 17, 19, 25], 8: [21], -1: [0, 30]}
403 406 snapshotmap15 = {0: [17, 19, 25], 8: [21], -1: [30]}
404 407 def findsnapshottest(rlog):
405 408 resultall = collections.defaultdict(list)
406 409 deltas._findsnapshots(rlog, resultall, 0)
407 410 resultall = dict(resultall.items())
408 411 if resultall != snapshotmapall:
409 412 print('snapshot map differ:')
410 413 print(' expected: %s' % snapshotmapall)
411 414 print(' got: %s' % resultall)
412 415 result15 = collections.defaultdict(list)
413 416 deltas._findsnapshots(rlog, result15, 15)
414 417 result15 = dict(result15.items())
415 418 if result15 != snapshotmap15:
416 419 print('snapshot map differ:')
417 420 print(' expected: %s' % snapshotmap15)
418 421 print(' got: %s' % result15)
419 422
420 423 def maintest():
421 424 with newtransaction() as tr:
422 425 rl = newrevlog(recreate=True)
423 426 expected = writecases(rl, tr)
424 427 checkrevlog(rl, expected)
425 428 print('local test passed')
426 429 # Copy via revlog.addgroup
427 430 rl1 = addgroupcopy(rl, tr)
428 431 checkrevlog(rl1, expected)
429 432 rl2 = addgroupcopy(rl, tr, optimaldelta=False)
430 433 checkrevlog(rl2, expected)
431 434 print('addgroupcopy test passed')
432 435 # Copy via revlog.clone
433 436 rl3 = newrevlog(name=b'_destrevlog3.i', recreate=True)
434 437 rl.clone(tr, rl3)
435 438 checkrevlog(rl3, expected)
436 439 print('clone test passed')
437 440 # Copy via low-level revlog._addrevision
438 441 rl4 = lowlevelcopy(rl, tr)
439 442 checkrevlog(rl4, expected)
440 443 print('lowlevelcopy test passed')
441 444 slicingtest(rl)
442 445 print('slicing test passed')
443 446 rl5 = makesnapshot(tr)
444 447 issnapshottest(rl5)
445 448 print('issnapshot test passed')
446 449 findsnapshottest(rl5)
447 450 print('findsnapshot test passed')
448 451
449 452 try:
450 453 maintest()
451 454 except Exception as ex:
452 455 abort('crashed: %s' % ex)
General Comments 0
You need to be logged in to leave comments. Login now