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