##// END OF EJS Templates
test-revlog-raw: remove duplicated option
Jun Wu -
r31764:15707e58 default
parent child Browse files
Show More
@@ -1,293 +1,293 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('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, 'revlogv1': True}
19 tvfs.options = {'generaldelta': True, '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._isgooddelta = lambda self, d, textlen: self.storedeltachains
23 revlog.revlog._isgooddelta = 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 return {'node': rlog.node(r), 'p1': pnode, 'p2': node.nullid,
117 return {'node': rlog.node(r), 'p1': pnode, 'p2': node.nullid,
118 'cs': rlog.node(rlog.linkrev(r)), 'flags': rlog.flags(r),
118 'cs': rlog.node(rlog.linkrev(r)), 'flags': rlog.flags(r),
119 'deltabase': rlog.node(deltaparent),
119 'deltabase': rlog.node(deltaparent),
120 'delta': rlog.revdiff(deltaparent, r)}
120 'delta': rlog.revdiff(deltaparent, r)}
121
121
122 def linkmap(lnode):
122 def linkmap(lnode):
123 return rlog.rev(lnode)
123 return rlog.rev(lnode)
124
124
125 dlog = newrevlog(destname, recreate=True)
125 dlog = newrevlog(destname, recreate=True)
126 dlog.addgroup(dummychangegroup(), linkmap, tr)
126 dlog.addgroup(dummychangegroup(), linkmap, tr)
127 return dlog
127 return dlog
128
128
129 def lowlevelcopy(rlog, tr, destname=b'_destrevlog.i'):
129 def lowlevelcopy(rlog, tr, destname=b'_destrevlog.i'):
130 '''Like addgroupcopy, but use the low level revlog._addrevision directly.
130 '''Like addgroupcopy, but use the low level revlog._addrevision directly.
131
131
132 It exercises some code paths that are hard to reach easily otherwise.
132 It exercises some code paths that are hard to reach easily otherwise.
133 '''
133 '''
134 dlog = newrevlog(destname, recreate=True)
134 dlog = newrevlog(destname, recreate=True)
135 for r in rlog:
135 for r in rlog:
136 p1 = rlog.node(r - 1)
136 p1 = rlog.node(r - 1)
137 p2 = node.nullid
137 p2 = node.nullid
138 if r == 0:
138 if r == 0:
139 text = rlog.revision(r, raw=True)
139 text = rlog.revision(r, raw=True)
140 cachedelta = None
140 cachedelta = None
141 else:
141 else:
142 # deltaparent is more interesting if it has the EXTSTORED flag.
142 # deltaparent is more interesting if it has the EXTSTORED flag.
143 deltaparent = max([0] + [p for p in range(r - 2) if rlog.flags(p)])
143 deltaparent = max([0] + [p for p in range(r - 2) if rlog.flags(p)])
144 text = None
144 text = None
145 cachedelta = (deltaparent, rlog.revdiff(deltaparent, r))
145 cachedelta = (deltaparent, rlog.revdiff(deltaparent, r))
146 flags = rlog.flags(r)
146 flags = rlog.flags(r)
147 ifh = dlog.opener(dlog.indexfile, 'a+')
147 ifh = dlog.opener(dlog.indexfile, 'a+')
148 dfh = None
148 dfh = None
149 if not dlog._inline:
149 if not dlog._inline:
150 dfh = dlog.opener(dlog.datafile, 'a+')
150 dfh = dlog.opener(dlog.datafile, 'a+')
151 dlog._addrevision(rlog.node(r), text, tr, r, p1, p2, flags, cachedelta,
151 dlog._addrevision(rlog.node(r), text, tr, r, p1, p2, flags, cachedelta,
152 ifh, dfh)
152 ifh, dfh)
153 return dlog
153 return dlog
154
154
155 # Utilities to generate revisions for testing
155 # Utilities to generate revisions for testing
156
156
157 def genbits(n):
157 def genbits(n):
158 '''Given a number n, generate (2 ** (n * 2) + 1) numbers in range(2 ** n).
158 '''Given a number n, generate (2 ** (n * 2) + 1) numbers in range(2 ** n).
159 i.e. the generated numbers have a width of n bits.
159 i.e. the generated numbers have a width of n bits.
160
160
161 The combination of two adjacent numbers will cover all possible cases.
161 The combination of two adjacent numbers will cover all possible cases.
162 That is to say, given any x, y where both x, and y are in range(2 ** n),
162 That is to say, given any x, y where both x, and y are in range(2 ** n),
163 there is an x followed immediately by y in the generated sequence.
163 there is an x followed immediately by y in the generated sequence.
164 '''
164 '''
165 m = 2 ** n
165 m = 2 ** n
166
166
167 # Gray Code. See https://en.wikipedia.org/wiki/Gray_code
167 # Gray Code. See https://en.wikipedia.org/wiki/Gray_code
168 gray = lambda x: x ^ (x >> 1)
168 gray = lambda x: x ^ (x >> 1)
169 reversegray = dict((gray(i), i) for i in range(m))
169 reversegray = dict((gray(i), i) for i in range(m))
170
170
171 # Generate (n * 2) bit gray code, yield lower n bits as X, and look for
171 # Generate (n * 2) bit gray code, yield lower n bits as X, and look for
172 # the next unused gray code where higher n bits equal to X.
172 # the next unused gray code where higher n bits equal to X.
173
173
174 # For gray codes whose higher bits are X, a[X] of them have been used.
174 # For gray codes whose higher bits are X, a[X] of them have been used.
175 a = [0] * m
175 a = [0] * m
176
176
177 # Iterate from 0.
177 # Iterate from 0.
178 x = 0
178 x = 0
179 yield x
179 yield x
180 for i in range(m * m):
180 for i in range(m * m):
181 x = reversegray[x]
181 x = reversegray[x]
182 y = gray(a[x] + x * m) & (m - 1)
182 y = gray(a[x] + x * m) & (m - 1)
183 assert a[x] < m
183 assert a[x] < m
184 a[x] += 1
184 a[x] += 1
185 x = y
185 x = y
186 yield x
186 yield x
187
187
188 def gentext(rev):
188 def gentext(rev):
189 '''Given a revision number, generate dummy text'''
189 '''Given a revision number, generate dummy text'''
190 return b''.join(b'%d\n' % j for j in range(-1, rev % 5))
190 return b''.join(b'%d\n' % j for j in range(-1, rev % 5))
191
191
192 def writecases(rlog, tr):
192 def writecases(rlog, tr):
193 '''Write some revisions interested to the test.
193 '''Write some revisions interested to the test.
194
194
195 The test is interested in 3 properties of a revision:
195 The test is interested in 3 properties of a revision:
196
196
197 - Is it a delta or a full text? (isdelta)
197 - Is it a delta or a full text? (isdelta)
198 This is to catch some delta application issues.
198 This is to catch some delta application issues.
199 - Does it have a flag of EXTSTORED? (isext)
199 - Does it have a flag of EXTSTORED? (isext)
200 This is to catch some flag processor issues. Especially when
200 This is to catch some flag processor issues. Especially when
201 interacted with revlog deltas.
201 interacted with revlog deltas.
202 - Is its text empty? (isempty)
202 - Is its text empty? (isempty)
203 This is less important. It is intended to try to catch some careless
203 This is less important. It is intended to try to catch some careless
204 checks like "if text" instead of "if text is None". Note: if flag
204 checks like "if text" instead of "if text is None". Note: if flag
205 processor is involved, raw text may be not empty.
205 processor is involved, raw text may be not empty.
206
206
207 Write 65 revisions. So that all combinations of the above flags for
207 Write 65 revisions. So that all combinations of the above flags for
208 adjacent revisions are covered. That is to say,
208 adjacent revisions are covered. That is to say,
209
209
210 len(set(
210 len(set(
211 (r.delta, r.ext, r.empty, (r+1).delta, (r+1).ext, (r+1).empty)
211 (r.delta, r.ext, r.empty, (r+1).delta, (r+1).ext, (r+1).empty)
212 for r in range(len(rlog) - 1)
212 for r in range(len(rlog) - 1)
213 )) is 64.
213 )) is 64.
214
214
215 Where "r.delta", "r.ext", and "r.empty" are booleans matching properties
215 Where "r.delta", "r.ext", and "r.empty" are booleans matching properties
216 mentioned above.
216 mentioned above.
217
217
218 Return expected [(text, rawtext)].
218 Return expected [(text, rawtext)].
219 '''
219 '''
220 result = []
220 result = []
221 for i, x in enumerate(genbits(3)):
221 for i, x in enumerate(genbits(3)):
222 isdelta, isext, isempty = bool(x & 1), bool(x & 2), bool(x & 4)
222 isdelta, isext, isempty = bool(x & 1), bool(x & 2), bool(x & 4)
223 if isempty:
223 if isempty:
224 text = b''
224 text = b''
225 else:
225 else:
226 text = gentext(i)
226 text = gentext(i)
227 rev = appendrev(rlog, text, tr, isext=isext, isdelta=isdelta)
227 rev = appendrev(rlog, text, tr, isext=isext, isdelta=isdelta)
228
228
229 # Verify text, rawtext, and rawsize
229 # Verify text, rawtext, and rawsize
230 if isext:
230 if isext:
231 rawtext = writeprocessor(None, text)[0]
231 rawtext = writeprocessor(None, text)[0]
232 else:
232 else:
233 rawtext = text
233 rawtext = text
234 if rlog.rawsize(rev) != len(rawtext):
234 if rlog.rawsize(rev) != len(rawtext):
235 abort('rev %d: wrong rawsize' % rev)
235 abort('rev %d: wrong rawsize' % rev)
236 if rlog.revision(rev, raw=False) != text:
236 if rlog.revision(rev, raw=False) != text:
237 abort('rev %d: wrong text' % rev)
237 abort('rev %d: wrong text' % rev)
238 if rlog.revision(rev, raw=True) != rawtext:
238 if rlog.revision(rev, raw=True) != rawtext:
239 abort('rev %d: wrong rawtext' % rev)
239 abort('rev %d: wrong rawtext' % rev)
240 result.append((text, rawtext))
240 result.append((text, rawtext))
241
241
242 # Verify flags like isdelta, isext work as expected
242 # Verify flags like isdelta, isext work as expected
243 if bool(rlog.deltaparent(rev) > -1) != isdelta:
243 if bool(rlog.deltaparent(rev) > -1) != isdelta:
244 abort('rev %d: isdelta is ineffective' % rev)
244 abort('rev %d: isdelta is ineffective' % rev)
245 if bool(rlog.flags(rev)) != isext:
245 if bool(rlog.flags(rev)) != isext:
246 abort('rev %d: isext is ineffective' % rev)
246 abort('rev %d: isext is ineffective' % rev)
247 return result
247 return result
248
248
249 # Main test and checking
249 # Main test and checking
250
250
251 def checkrevlog(rlog, expected):
251 def checkrevlog(rlog, expected):
252 '''Check if revlog has expected contents. expected is [(text, rawtext)]'''
252 '''Check if revlog has expected contents. expected is [(text, rawtext)]'''
253 # Test using different access orders. This could expose some issues
253 # Test using different access orders. This could expose some issues
254 # depending on revlog caching (see revlog._cache).
254 # depending on revlog caching (see revlog._cache).
255 for r0 in range(len(rlog) - 1):
255 for r0 in range(len(rlog) - 1):
256 r1 = r0 + 1
256 r1 = r0 + 1
257 for revorder in [[r0, r1], [r1, r0]]:
257 for revorder in [[r0, r1], [r1, r0]]:
258 for raworder in [[True], [False], [True, False], [False, True]]:
258 for raworder in [[True], [False], [True, False], [False, True]]:
259 nlog = newrevlog()
259 nlog = newrevlog()
260 for rev in revorder:
260 for rev in revorder:
261 for raw in raworder:
261 for raw in raworder:
262 t = nlog.revision(rev, raw=raw)
262 t = nlog.revision(rev, raw=raw)
263 if t != expected[rev][int(raw)]:
263 if t != expected[rev][int(raw)]:
264 abort('rev %d: corrupted %stext'
264 abort('rev %d: corrupted %stext'
265 % (rev, raw and 'raw' or ''))
265 % (rev, raw and 'raw' or ''))
266
266
267 def maintest():
267 def maintest():
268 expected = rl = None
268 expected = rl = None
269 with newtransaction() as tr:
269 with newtransaction() as tr:
270 rl = newrevlog(recreate=True)
270 rl = newrevlog(recreate=True)
271 expected = writecases(rl, tr)
271 expected = writecases(rl, tr)
272 checkrevlog(rl, expected)
272 checkrevlog(rl, expected)
273 print('local test passed')
273 print('local test passed')
274 # Copy via revlog.addgroup
274 # Copy via revlog.addgroup
275 rl1 = addgroupcopy(rl, tr)
275 rl1 = addgroupcopy(rl, tr)
276 checkrevlog(rl1, expected)
276 checkrevlog(rl1, expected)
277 rl2 = addgroupcopy(rl, tr, optimaldelta=False)
277 rl2 = addgroupcopy(rl, tr, optimaldelta=False)
278 checkrevlog(rl2, expected)
278 checkrevlog(rl2, expected)
279 print('addgroupcopy test passed')
279 print('addgroupcopy test passed')
280 # Copy via revlog.clone
280 # Copy via revlog.clone
281 rl3 = newrevlog(name='_destrevlog3.i', recreate=True)
281 rl3 = newrevlog(name='_destrevlog3.i', recreate=True)
282 rl.clone(tr, rl3)
282 rl.clone(tr, rl3)
283 checkrevlog(rl3, expected)
283 checkrevlog(rl3, expected)
284 print('clone test passed')
284 print('clone test passed')
285 # Copy via low-level revlog._addrevision
285 # Copy via low-level revlog._addrevision
286 rl4 = lowlevelcopy(rl, tr)
286 rl4 = lowlevelcopy(rl, tr)
287 checkrevlog(rl4, expected)
287 checkrevlog(rl4, expected)
288 print('lowlevelcopy test passed')
288 print('lowlevelcopy test passed')
289
289
290 try:
290 try:
291 maintest()
291 maintest()
292 except Exception as ex:
292 except Exception as ex:
293 abort('crashed: %s' % ex)
293 abort('crashed: %s' % ex)
General Comments 0
You need to be logged in to leave comments. Login now