##// END OF EJS Templates
tests: port test-revlog-raw.py to Python 3...
Augie Fackler -
r37913:03a09579 default
parent child Browse files
Show More
@@ -1,320 +1,320 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 sys
6 6
7 7 from mercurial import (
8 8 encoding,
9 9 node,
10 10 revlog,
11 11 transaction,
12 12 vfs,
13 13 )
14 14
15 15 # TESTTMP is optional. This makes it convenient to run without run-tests.py
16 tvfs = vfs.vfs(encoding.environ.get('TESTTMP', b'/tmp'))
16 tvfs = vfs.vfs(encoding.environ.get(b'TESTTMP', b'/tmp'))
17 17
18 18 # Enable generaldelta otherwise revlog won't use delta as expected by the test
19 tvfs.options = {'generaldelta': True, 'revlogv1': True}
19 tvfs.options = {b'generaldelta': True, b'revlogv1': True}
20 20
21 21 # The test wants to control whether to use delta explicitly, based on
22 22 # "storedeltachains".
23 23 revlog.revlog._isgooddeltainfo = lambda self, d, textlen: self.storedeltachains
24 24
25 25 def abort(msg):
26 26 print('abort: %s' % msg)
27 27 # Return 0 so run-tests.py could compare the output.
28 28 sys.exit()
29 29
30 30 # Register a revlog processor for flag EXTSTORED.
31 31 #
32 32 # It simply prepends a fixed header, and replaces '1' to 'i'. So it has
33 33 # insertion and replacement, and may be interesting to test revlog's line-based
34 34 # deltas.
35 35 _extheader = b'E\n'
36 36
37 37 def readprocessor(self, rawtext):
38 38 # True: the returned text could be used to verify hash
39 39 text = rawtext[len(_extheader):].replace(b'i', b'1')
40 40 return text, True
41 41
42 42 def writeprocessor(self, text):
43 43 # False: the returned rawtext shouldn't be used to verify hash
44 44 rawtext = _extheader + text.replace(b'1', b'i')
45 45 return rawtext, False
46 46
47 47 def rawprocessor(self, rawtext):
48 48 # False: do not verify hash. Only the content returned by "readprocessor"
49 49 # can be used to verify hash.
50 50 return False
51 51
52 52 revlog.addflagprocessor(revlog.REVIDX_EXTSTORED,
53 53 (readprocessor, writeprocessor, rawprocessor))
54 54
55 55 # Utilities about reading and appending revlog
56 56
57 57 def newtransaction():
58 58 # A transaction is required to write revlogs
59 59 report = lambda msg: None
60 60 return transaction.transaction(report, tvfs, {'plain': tvfs}, b'journal')
61 61
62 62 def newrevlog(name=b'_testrevlog.i', recreate=False):
63 63 if recreate:
64 64 tvfs.tryunlink(name)
65 65 rlog = revlog.revlog(tvfs, name)
66 66 return rlog
67 67
68 68 def appendrev(rlog, text, tr, isext=False, isdelta=True):
69 69 '''Append a revision. If isext is True, set the EXTSTORED flag so flag
70 70 processor will be used (and rawtext is different from text). If isdelta is
71 71 True, force the revision to be a delta, otherwise it's full text.
72 72 '''
73 73 nextrev = len(rlog)
74 74 p1 = rlog.node(nextrev - 1)
75 75 p2 = node.nullid
76 76 if isext:
77 77 flags = revlog.REVIDX_EXTSTORED
78 78 else:
79 79 flags = revlog.REVIDX_DEFAULT_FLAGS
80 80 # Change storedeltachains temporarily, to override revlog's delta decision
81 81 rlog.storedeltachains = isdelta
82 82 try:
83 83 rlog.addrevision(text, tr, nextrev, p1, p2, flags=flags)
84 84 return nextrev
85 85 except Exception as ex:
86 86 abort('rev %d: failed to append: %s' % (nextrev, ex))
87 87 finally:
88 88 # Restore storedeltachains. It is always True, see revlog.__init__
89 89 rlog.storedeltachains = True
90 90
91 91 def addgroupcopy(rlog, tr, destname=b'_destrevlog.i', optimaldelta=True):
92 92 '''Copy revlog to destname using revlog.addgroup. Return the copied revlog.
93 93
94 94 This emulates push or pull. They use changegroup. Changegroup requires
95 95 repo to work. We don't have a repo, so a dummy changegroup is used.
96 96
97 97 If optimaldelta is True, use optimized delta parent, so the destination
98 98 revlog could probably reuse it. Otherwise it builds sub-optimal delta, and
99 99 the destination revlog needs more work to use it.
100 100
101 101 This exercises some revlog.addgroup (and revlog._addrevision(text=None))
102 102 code path, which is not covered by "appendrev" alone.
103 103 '''
104 104 class dummychangegroup(object):
105 105 @staticmethod
106 106 def deltachunk(pnode):
107 107 pnode = pnode or node.nullid
108 108 parentrev = rlog.rev(pnode)
109 109 r = parentrev + 1
110 110 if r >= len(rlog):
111 111 return {}
112 112 if optimaldelta:
113 113 deltaparent = parentrev
114 114 else:
115 115 # suboptimal deltaparent
116 116 deltaparent = min(0, parentrev)
117 117 if not rlog.candelta(deltaparent, r):
118 118 deltaparent = -1
119 return {'node': rlog.node(r), 'p1': pnode, 'p2': node.nullid,
120 'cs': rlog.node(rlog.linkrev(r)), 'flags': rlog.flags(r),
121 'deltabase': rlog.node(deltaparent),
122 'delta': rlog.revdiff(deltaparent, r)}
119 return {b'node': rlog.node(r), b'p1': pnode, b'p2': node.nullid,
120 b'cs': rlog.node(rlog.linkrev(r)), b'flags': rlog.flags(r),
121 b'deltabase': rlog.node(deltaparent),
122 b'delta': rlog.revdiff(deltaparent, r)}
123 123
124 124 def deltaiter(self):
125 125 chain = None
126 126 for chunkdata in iter(lambda: self.deltachunk(chain), {}):
127 node = chunkdata['node']
128 p1 = chunkdata['p1']
129 p2 = chunkdata['p2']
130 cs = chunkdata['cs']
131 deltabase = chunkdata['deltabase']
132 delta = chunkdata['delta']
133 flags = chunkdata['flags']
127 node = chunkdata[b'node']
128 p1 = chunkdata[b'p1']
129 p2 = chunkdata[b'p2']
130 cs = chunkdata[b'cs']
131 deltabase = chunkdata[b'deltabase']
132 delta = chunkdata[b'delta']
133 flags = chunkdata[b'flags']
134 134
135 135 chain = node
136 136
137 137 yield (node, p1, p2, cs, deltabase, delta, flags)
138 138
139 139 def linkmap(lnode):
140 140 return rlog.rev(lnode)
141 141
142 142 dlog = newrevlog(destname, recreate=True)
143 143 dummydeltas = dummychangegroup().deltaiter()
144 144 dlog.addgroup(dummydeltas, linkmap, tr)
145 145 return dlog
146 146
147 147 def lowlevelcopy(rlog, tr, destname=b'_destrevlog.i'):
148 148 '''Like addgroupcopy, but use the low level revlog._addrevision directly.
149 149
150 150 It exercises some code paths that are hard to reach easily otherwise.
151 151 '''
152 152 dlog = newrevlog(destname, recreate=True)
153 153 for r in rlog:
154 154 p1 = rlog.node(r - 1)
155 155 p2 = node.nullid
156 156 if r == 0 or (rlog.flags(r) & revlog.REVIDX_EXTSTORED):
157 157 text = rlog.revision(r, raw=True)
158 158 cachedelta = None
159 159 else:
160 160 # deltaparent cannot have EXTSTORED flag.
161 161 deltaparent = max([-1] +
162 162 [p for p in range(r)
163 163 if rlog.flags(p) & revlog.REVIDX_EXTSTORED == 0])
164 164 text = None
165 165 cachedelta = (deltaparent, rlog.revdiff(deltaparent, r))
166 166 flags = rlog.flags(r)
167 167 ifh = dfh = None
168 168 try:
169 ifh = dlog.opener(dlog.indexfile, 'a+')
169 ifh = dlog.opener(dlog.indexfile, b'a+')
170 170 if not dlog._inline:
171 dfh = dlog.opener(dlog.datafile, 'a+')
171 dfh = dlog.opener(dlog.datafile, b'a+')
172 172 dlog._addrevision(rlog.node(r), text, tr, r, p1, p2, flags,
173 173 cachedelta, ifh, dfh)
174 174 finally:
175 175 if dfh is not None:
176 176 dfh.close()
177 177 if ifh is not None:
178 178 ifh.close()
179 179 return dlog
180 180
181 181 # Utilities to generate revisions for testing
182 182
183 183 def genbits(n):
184 184 '''Given a number n, generate (2 ** (n * 2) + 1) numbers in range(2 ** n).
185 185 i.e. the generated numbers have a width of n bits.
186 186
187 187 The combination of two adjacent numbers will cover all possible cases.
188 188 That is to say, given any x, y where both x, and y are in range(2 ** n),
189 189 there is an x followed immediately by y in the generated sequence.
190 190 '''
191 191 m = 2 ** n
192 192
193 193 # Gray Code. See https://en.wikipedia.org/wiki/Gray_code
194 194 gray = lambda x: x ^ (x >> 1)
195 195 reversegray = dict((gray(i), i) for i in range(m))
196 196
197 197 # Generate (n * 2) bit gray code, yield lower n bits as X, and look for
198 198 # the next unused gray code where higher n bits equal to X.
199 199
200 200 # For gray codes whose higher bits are X, a[X] of them have been used.
201 201 a = [0] * m
202 202
203 203 # Iterate from 0.
204 204 x = 0
205 205 yield x
206 206 for i in range(m * m):
207 207 x = reversegray[x]
208 208 y = gray(a[x] + x * m) & (m - 1)
209 209 assert a[x] < m
210 210 a[x] += 1
211 211 x = y
212 212 yield x
213 213
214 214 def gentext(rev):
215 215 '''Given a revision number, generate dummy text'''
216 216 return b''.join(b'%d\n' % j for j in range(-1, rev % 5))
217 217
218 218 def writecases(rlog, tr):
219 219 '''Write some revisions interested to the test.
220 220
221 221 The test is interested in 3 properties of a revision:
222 222
223 223 - Is it a delta or a full text? (isdelta)
224 224 This is to catch some delta application issues.
225 225 - Does it have a flag of EXTSTORED? (isext)
226 226 This is to catch some flag processor issues. Especially when
227 227 interacted with revlog deltas.
228 228 - Is its text empty? (isempty)
229 229 This is less important. It is intended to try to catch some careless
230 230 checks like "if text" instead of "if text is None". Note: if flag
231 231 processor is involved, raw text may be not empty.
232 232
233 233 Write 65 revisions. So that all combinations of the above flags for
234 234 adjacent revisions are covered. That is to say,
235 235
236 236 len(set(
237 237 (r.delta, r.ext, r.empty, (r+1).delta, (r+1).ext, (r+1).empty)
238 238 for r in range(len(rlog) - 1)
239 239 )) is 64.
240 240
241 241 Where "r.delta", "r.ext", and "r.empty" are booleans matching properties
242 242 mentioned above.
243 243
244 244 Return expected [(text, rawtext)].
245 245 '''
246 246 result = []
247 247 for i, x in enumerate(genbits(3)):
248 248 isdelta, isext, isempty = bool(x & 1), bool(x & 2), bool(x & 4)
249 249 if isempty:
250 250 text = b''
251 251 else:
252 252 text = gentext(i)
253 253 rev = appendrev(rlog, text, tr, isext=isext, isdelta=isdelta)
254 254
255 255 # Verify text, rawtext, and rawsize
256 256 if isext:
257 257 rawtext = writeprocessor(None, text)[0]
258 258 else:
259 259 rawtext = text
260 260 if rlog.rawsize(rev) != len(rawtext):
261 261 abort('rev %d: wrong rawsize' % rev)
262 262 if rlog.revision(rev, raw=False) != text:
263 263 abort('rev %d: wrong text' % rev)
264 264 if rlog.revision(rev, raw=True) != rawtext:
265 265 abort('rev %d: wrong rawtext' % rev)
266 266 result.append((text, rawtext))
267 267
268 268 # Verify flags like isdelta, isext work as expected
269 269 # isdelta can be overridden to False if this or p1 has isext set
270 270 if bool(rlog.deltaparent(rev) > -1) and not isdelta:
271 271 abort('rev %d: isdelta is unexpected' % rev)
272 272 if bool(rlog.flags(rev)) != isext:
273 273 abort('rev %d: isext is ineffective' % rev)
274 274 return result
275 275
276 276 # Main test and checking
277 277
278 278 def checkrevlog(rlog, expected):
279 279 '''Check if revlog has expected contents. expected is [(text, rawtext)]'''
280 280 # Test using different access orders. This could expose some issues
281 281 # depending on revlog caching (see revlog._cache).
282 282 for r0 in range(len(rlog) - 1):
283 283 r1 = r0 + 1
284 284 for revorder in [[r0, r1], [r1, r0]]:
285 285 for raworder in [[True], [False], [True, False], [False, True]]:
286 286 nlog = newrevlog()
287 287 for rev in revorder:
288 288 for raw in raworder:
289 289 t = nlog.revision(rev, raw=raw)
290 290 if t != expected[rev][int(raw)]:
291 291 abort('rev %d: corrupted %stext'
292 292 % (rev, raw and 'raw' or ''))
293 293
294 294 def maintest():
295 295 expected = rl = None
296 296 with newtransaction() as tr:
297 297 rl = newrevlog(recreate=True)
298 298 expected = writecases(rl, tr)
299 299 checkrevlog(rl, expected)
300 300 print('local test passed')
301 301 # Copy via revlog.addgroup
302 302 rl1 = addgroupcopy(rl, tr)
303 303 checkrevlog(rl1, expected)
304 304 rl2 = addgroupcopy(rl, tr, optimaldelta=False)
305 305 checkrevlog(rl2, expected)
306 306 print('addgroupcopy test passed')
307 307 # Copy via revlog.clone
308 rl3 = newrevlog(name='_destrevlog3.i', recreate=True)
308 rl3 = newrevlog(name=b'_destrevlog3.i', recreate=True)
309 309 rl.clone(tr, rl3)
310 310 checkrevlog(rl3, expected)
311 311 print('clone test passed')
312 312 # Copy via low-level revlog._addrevision
313 313 rl4 = lowlevelcopy(rl, tr)
314 314 checkrevlog(rl4, expected)
315 315 print('lowlevelcopy test passed')
316 316
317 317 try:
318 318 maintest()
319 319 except Exception as ex:
320 320 abort('crashed: %s' % ex)
General Comments 0
You need to be logged in to leave comments. Login now