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