##// END OF EJS Templates
testing: add file storage tests for getstrippoint() and strip()...
Gregory Szorc -
r40086:8e136940 default
parent child Browse files
Show More
@@ -1,1039 +1,1156 b''
1 1 # storage.py - Testing of storage primitives.
2 2 #
3 3 # Copyright 2018 Gregory Szorc <gregory.szorc@gmail.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from __future__ import absolute_import
9 9
10 10 import unittest
11 11
12 12 from ..node import (
13 13 hex,
14 14 nullid,
15 15 nullrev,
16 16 )
17 17 from .. import (
18 18 error,
19 19 mdiff,
20 20 repository,
21 21 )
22 22 from ..utils import (
23 23 storageutil,
24 24 )
25 25
26 26 class basetestcase(unittest.TestCase):
27 27 if not getattr(unittest.TestCase, r'assertRaisesRegex', False):
28 28 assertRaisesRegex = (# camelcase-required
29 29 unittest.TestCase.assertRaisesRegexp)
30 30
31 31 class ifileindextests(basetestcase):
32 32 """Generic tests for the ifileindex interface.
33 33
34 34 All file storage backends for index data should conform to the tests in this
35 35 class.
36 36
37 37 Use ``makeifileindextests()`` to create an instance of this type.
38 38 """
39 39 def testempty(self):
40 40 f = self._makefilefn()
41 41 self.assertEqual(len(f), 0, 'new file store has 0 length by default')
42 42 self.assertEqual(list(f), [], 'iter yields nothing by default')
43 43
44 44 gen = iter(f)
45 45 with self.assertRaises(StopIteration):
46 46 next(gen)
47 47
48 48 # revs() should evaluate to an empty list.
49 49 self.assertEqual(list(f.revs()), [])
50 50
51 51 revs = iter(f.revs())
52 52 with self.assertRaises(StopIteration):
53 53 next(revs)
54 54
55 55 self.assertEqual(list(f.revs(start=20)), [])
56 56
57 57 # parents() and parentrevs() work with nullid/nullrev.
58 58 self.assertEqual(f.parents(nullid), (nullid, nullid))
59 59 self.assertEqual(f.parentrevs(nullrev), (nullrev, nullrev))
60 60
61 61 with self.assertRaises(error.LookupError):
62 62 f.parents(b'\x01' * 20)
63 63
64 64 for i in range(-5, 5):
65 65 if i == nullrev:
66 66 continue
67 67
68 68 with self.assertRaises(IndexError):
69 69 f.parentrevs(i)
70 70
71 71 # nullid/nullrev lookup always works.
72 72 self.assertEqual(f.rev(nullid), nullrev)
73 73 self.assertEqual(f.node(nullrev), nullid)
74 74
75 75 with self.assertRaises(error.LookupError):
76 76 f.rev(b'\x01' * 20)
77 77
78 78 for i in range(-5, 5):
79 79 if i == nullrev:
80 80 continue
81 81
82 82 with self.assertRaises(IndexError):
83 83 f.node(i)
84 84
85 85 self.assertEqual(f.lookup(nullid), nullid)
86 86 self.assertEqual(f.lookup(nullrev), nullid)
87 87 self.assertEqual(f.lookup(hex(nullid)), nullid)
88 88 self.assertEqual(f.lookup(b'%d' % nullrev), nullid)
89 89
90 90 with self.assertRaises(error.LookupError):
91 91 f.lookup(b'badvalue')
92 92
93 93 with self.assertRaises(error.LookupError):
94 94 f.lookup(hex(nullid)[0:12])
95 95
96 96 with self.assertRaises(error.LookupError):
97 97 f.lookup(b'-2')
98 98
99 99 with self.assertRaises(error.LookupError):
100 100 f.lookup(b'0')
101 101
102 102 with self.assertRaises(error.LookupError):
103 103 f.lookup(b'1')
104 104
105 105 with self.assertRaises(error.LookupError):
106 106 f.lookup(b'11111111111111111111111111111111111111')
107 107
108 108 for i in range(-5, 5):
109 109 if i == nullrev:
110 110 continue
111 111
112 112 with self.assertRaises(LookupError):
113 113 f.lookup(i)
114 114
115 115 self.assertEqual(f.linkrev(nullrev), nullrev)
116 116
117 117 for i in range(-5, 5):
118 118 if i == nullrev:
119 119 continue
120 120
121 121 with self.assertRaises(IndexError):
122 122 f.linkrev(i)
123 123
124 124 self.assertFalse(f.iscensored(nullrev))
125 125
126 126 for i in range(-5, 5):
127 127 if i == nullrev:
128 128 continue
129 129
130 130 with self.assertRaises(IndexError):
131 131 f.iscensored(i)
132 132
133 133 self.assertEqual(list(f.commonancestorsheads(nullid, nullid)), [])
134 134
135 135 with self.assertRaises(ValueError):
136 136 self.assertEqual(list(f.descendants([])), [])
137 137
138 138 self.assertEqual(list(f.descendants([nullrev])), [])
139 139
140 140 self.assertEqual(f.heads(), [nullid])
141 141 self.assertEqual(f.heads(nullid), [nullid])
142 142 self.assertEqual(f.heads(None, [nullid]), [nullid])
143 143 self.assertEqual(f.heads(nullid, [nullid]), [nullid])
144 144
145 145 self.assertEqual(f.children(nullid), [])
146 146
147 147 with self.assertRaises(error.LookupError):
148 148 f.children(b'\x01' * 20)
149 149
150 150 def testsinglerevision(self):
151 151 f = self._makefilefn()
152 152 with self._maketransactionfn() as tr:
153 153 node = f.add(b'initial', None, tr, 0, nullid, nullid)
154 154
155 155 self.assertEqual(len(f), 1)
156 156 self.assertEqual(list(f), [0])
157 157
158 158 gen = iter(f)
159 159 self.assertEqual(next(gen), 0)
160 160
161 161 with self.assertRaises(StopIteration):
162 162 next(gen)
163 163
164 164 self.assertEqual(list(f.revs()), [0])
165 165 self.assertEqual(list(f.revs(start=1)), [])
166 166 self.assertEqual(list(f.revs(start=0)), [0])
167 167 self.assertEqual(list(f.revs(stop=0)), [0])
168 168 self.assertEqual(list(f.revs(stop=1)), [0])
169 169 self.assertEqual(list(f.revs(1, 1)), [])
170 170 # TODO buggy
171 171 self.assertEqual(list(f.revs(1, 0)), [1, 0])
172 172 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
173 173
174 174 self.assertEqual(f.parents(node), (nullid, nullid))
175 175 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
176 176
177 177 with self.assertRaises(error.LookupError):
178 178 f.parents(b'\x01' * 20)
179 179
180 180 with self.assertRaises(IndexError):
181 181 f.parentrevs(1)
182 182
183 183 self.assertEqual(f.rev(node), 0)
184 184
185 185 with self.assertRaises(error.LookupError):
186 186 f.rev(b'\x01' * 20)
187 187
188 188 self.assertEqual(f.node(0), node)
189 189
190 190 with self.assertRaises(IndexError):
191 191 f.node(1)
192 192
193 193 self.assertEqual(f.lookup(node), node)
194 194 self.assertEqual(f.lookup(0), node)
195 195 self.assertEqual(f.lookup(-1), nullid)
196 196 self.assertEqual(f.lookup(b'0'), node)
197 197 self.assertEqual(f.lookup(hex(node)), node)
198 198
199 199 with self.assertRaises(error.LookupError):
200 200 f.lookup(hex(node)[0:12])
201 201
202 202 with self.assertRaises(error.LookupError):
203 203 f.lookup(-2)
204 204
205 205 with self.assertRaises(error.LookupError):
206 206 f.lookup(b'-2')
207 207
208 208 with self.assertRaises(error.LookupError):
209 209 f.lookup(1)
210 210
211 211 with self.assertRaises(error.LookupError):
212 212 f.lookup(b'1')
213 213
214 214 self.assertEqual(f.linkrev(0), 0)
215 215
216 216 with self.assertRaises(IndexError):
217 217 f.linkrev(1)
218 218
219 219 self.assertFalse(f.iscensored(0))
220 220
221 221 with self.assertRaises(IndexError):
222 222 f.iscensored(1)
223 223
224 224 self.assertEqual(list(f.descendants([0])), [])
225 225
226 226 self.assertEqual(f.heads(), [node])
227 227 self.assertEqual(f.heads(node), [node])
228 228 self.assertEqual(f.heads(stop=[node]), [node])
229 229
230 230 with self.assertRaises(error.LookupError):
231 231 f.heads(stop=[b'\x01' * 20])
232 232
233 233 self.assertEqual(f.children(node), [])
234 234
235 235 def testmultiplerevisions(self):
236 236 fulltext0 = b'x' * 1024
237 237 fulltext1 = fulltext0 + b'y'
238 238 fulltext2 = b'y' + fulltext0 + b'z'
239 239
240 240 f = self._makefilefn()
241 241 with self._maketransactionfn() as tr:
242 242 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
243 243 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
244 244 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
245 245
246 246 self.assertEqual(len(f), 3)
247 247 self.assertEqual(list(f), [0, 1, 2])
248 248
249 249 gen = iter(f)
250 250 self.assertEqual(next(gen), 0)
251 251 self.assertEqual(next(gen), 1)
252 252 self.assertEqual(next(gen), 2)
253 253
254 254 with self.assertRaises(StopIteration):
255 255 next(gen)
256 256
257 257 self.assertEqual(list(f.revs()), [0, 1, 2])
258 258 self.assertEqual(list(f.revs(0)), [0, 1, 2])
259 259 self.assertEqual(list(f.revs(1)), [1, 2])
260 260 self.assertEqual(list(f.revs(2)), [2])
261 261 self.assertEqual(list(f.revs(3)), [])
262 262 self.assertEqual(list(f.revs(stop=1)), [0, 1])
263 263 self.assertEqual(list(f.revs(stop=2)), [0, 1, 2])
264 264 self.assertEqual(list(f.revs(stop=3)), [0, 1, 2])
265 265 self.assertEqual(list(f.revs(2, 0)), [2, 1, 0])
266 266 self.assertEqual(list(f.revs(2, 1)), [2, 1])
267 267 # TODO this is wrong
268 268 self.assertEqual(list(f.revs(3, 2)), [3, 2])
269 269
270 270 self.assertEqual(f.parents(node0), (nullid, nullid))
271 271 self.assertEqual(f.parents(node1), (node0, nullid))
272 272 self.assertEqual(f.parents(node2), (node1, nullid))
273 273
274 274 self.assertEqual(f.parentrevs(0), (nullrev, nullrev))
275 275 self.assertEqual(f.parentrevs(1), (0, nullrev))
276 276 self.assertEqual(f.parentrevs(2), (1, nullrev))
277 277
278 278 self.assertEqual(f.rev(node0), 0)
279 279 self.assertEqual(f.rev(node1), 1)
280 280 self.assertEqual(f.rev(node2), 2)
281 281
282 282 with self.assertRaises(error.LookupError):
283 283 f.rev(b'\x01' * 20)
284 284
285 285 self.assertEqual(f.node(0), node0)
286 286 self.assertEqual(f.node(1), node1)
287 287 self.assertEqual(f.node(2), node2)
288 288
289 289 with self.assertRaises(IndexError):
290 290 f.node(3)
291 291
292 292 self.assertEqual(f.lookup(node0), node0)
293 293 self.assertEqual(f.lookup(0), node0)
294 294 self.assertEqual(f.lookup(b'0'), node0)
295 295 self.assertEqual(f.lookup(hex(node0)), node0)
296 296
297 297 self.assertEqual(f.lookup(node1), node1)
298 298 self.assertEqual(f.lookup(1), node1)
299 299 self.assertEqual(f.lookup(b'1'), node1)
300 300 self.assertEqual(f.lookup(hex(node1)), node1)
301 301
302 302 self.assertEqual(f.linkrev(0), 0)
303 303 self.assertEqual(f.linkrev(1), 1)
304 304 self.assertEqual(f.linkrev(2), 3)
305 305
306 306 with self.assertRaises(IndexError):
307 307 f.linkrev(3)
308 308
309 309 self.assertFalse(f.iscensored(0))
310 310 self.assertFalse(f.iscensored(1))
311 311 self.assertFalse(f.iscensored(2))
312 312
313 313 with self.assertRaises(IndexError):
314 314 f.iscensored(3)
315 315
316 316 self.assertEqual(f.commonancestorsheads(node1, nullid), [])
317 317 self.assertEqual(f.commonancestorsheads(node1, node0), [node0])
318 318 self.assertEqual(f.commonancestorsheads(node1, node1), [node1])
319 319 self.assertEqual(f.commonancestorsheads(node0, node1), [node0])
320 320 self.assertEqual(f.commonancestorsheads(node1, node2), [node1])
321 321 self.assertEqual(f.commonancestorsheads(node2, node1), [node1])
322 322
323 323 self.assertEqual(list(f.descendants([0])), [1, 2])
324 324 self.assertEqual(list(f.descendants([1])), [2])
325 325 self.assertEqual(list(f.descendants([0, 1])), [1, 2])
326 326
327 327 self.assertEqual(f.heads(), [node2])
328 328 self.assertEqual(f.heads(node0), [node2])
329 329 self.assertEqual(f.heads(node1), [node2])
330 330 self.assertEqual(f.heads(node2), [node2])
331 331
332 332 # TODO this behavior seems wonky. Is it correct? If so, the
333 333 # docstring for heads() should be updated to reflect desired
334 334 # behavior.
335 335 self.assertEqual(f.heads(stop=[node1]), [node1, node2])
336 336 self.assertEqual(f.heads(stop=[node0]), [node0, node2])
337 337 self.assertEqual(f.heads(stop=[node1, node2]), [node1, node2])
338 338
339 339 with self.assertRaises(error.LookupError):
340 340 f.heads(stop=[b'\x01' * 20])
341 341
342 342 self.assertEqual(f.children(node0), [node1])
343 343 self.assertEqual(f.children(node1), [node2])
344 344 self.assertEqual(f.children(node2), [])
345 345
346 346 def testmultipleheads(self):
347 347 f = self._makefilefn()
348 348
349 349 with self._maketransactionfn() as tr:
350 350 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
351 351 node1 = f.add(b'1', None, tr, 1, node0, nullid)
352 352 node2 = f.add(b'2', None, tr, 2, node1, nullid)
353 353 node3 = f.add(b'3', None, tr, 3, node0, nullid)
354 354 node4 = f.add(b'4', None, tr, 4, node3, nullid)
355 355 node5 = f.add(b'5', None, tr, 5, node0, nullid)
356 356
357 357 self.assertEqual(len(f), 6)
358 358
359 359 self.assertEqual(list(f.descendants([0])), [1, 2, 3, 4, 5])
360 360 self.assertEqual(list(f.descendants([1])), [2])
361 361 self.assertEqual(list(f.descendants([2])), [])
362 362 self.assertEqual(list(f.descendants([3])), [4])
363 363 self.assertEqual(list(f.descendants([0, 1])), [1, 2, 3, 4, 5])
364 364 self.assertEqual(list(f.descendants([1, 3])), [2, 4])
365 365
366 366 self.assertEqual(f.heads(), [node2, node4, node5])
367 367 self.assertEqual(f.heads(node0), [node2, node4, node5])
368 368 self.assertEqual(f.heads(node1), [node2])
369 369 self.assertEqual(f.heads(node2), [node2])
370 370 self.assertEqual(f.heads(node3), [node4])
371 371 self.assertEqual(f.heads(node4), [node4])
372 372 self.assertEqual(f.heads(node5), [node5])
373 373
374 374 # TODO this seems wrong.
375 375 self.assertEqual(f.heads(stop=[node0]), [node0, node2, node4, node5])
376 376 self.assertEqual(f.heads(stop=[node1]), [node1, node2, node4, node5])
377 377
378 378 self.assertEqual(f.children(node0), [node1, node3, node5])
379 379 self.assertEqual(f.children(node1), [node2])
380 380 self.assertEqual(f.children(node2), [])
381 381 self.assertEqual(f.children(node3), [node4])
382 382 self.assertEqual(f.children(node4), [])
383 383 self.assertEqual(f.children(node5), [])
384 384
385 385 class ifiledatatests(basetestcase):
386 386 """Generic tests for the ifiledata interface.
387 387
388 388 All file storage backends for data should conform to the tests in this
389 389 class.
390 390
391 391 Use ``makeifiledatatests()`` to create an instance of this type.
392 392 """
393 393 def testempty(self):
394 394 f = self._makefilefn()
395 395
396 396 self.assertEqual(f.storageinfo(), {})
397 397 self.assertEqual(f.storageinfo(revisionscount=True, trackedsize=True),
398 398 {'revisionscount': 0, 'trackedsize': 0})
399 399
400 400 self.assertEqual(f.size(nullrev), 0)
401 401
402 402 for i in range(-5, 5):
403 403 if i == nullrev:
404 404 continue
405 405
406 406 with self.assertRaises(IndexError):
407 407 f.size(i)
408 408
409 409 self.assertEqual(f.revision(nullid), b'')
410 410 self.assertEqual(f.revision(nullid, raw=True), b'')
411 411
412 412 with self.assertRaises(error.LookupError):
413 413 f.revision(b'\x01' * 20)
414 414
415 415 self.assertEqual(f.read(nullid), b'')
416 416
417 417 with self.assertRaises(error.LookupError):
418 418 f.read(b'\x01' * 20)
419 419
420 420 self.assertFalse(f.renamed(nullid))
421 421
422 422 with self.assertRaises(error.LookupError):
423 423 f.read(b'\x01' * 20)
424 424
425 425 self.assertTrue(f.cmp(nullid, b''))
426 426 self.assertTrue(f.cmp(nullid, b'foo'))
427 427
428 428 with self.assertRaises(error.LookupError):
429 429 f.cmp(b'\x01' * 20, b'irrelevant')
430 430
431 431 # Emitting empty list is an empty generator.
432 432 gen = f.emitrevisions([])
433 433 with self.assertRaises(StopIteration):
434 434 next(gen)
435 435
436 436 # Emitting null node yields nothing.
437 437 gen = f.emitrevisions([nullid])
438 438 with self.assertRaises(StopIteration):
439 439 next(gen)
440 440
441 441 # Requesting unknown node fails.
442 442 with self.assertRaises(error.LookupError):
443 443 list(f.emitrevisions([b'\x01' * 20]))
444 444
445 445 def testsinglerevision(self):
446 446 fulltext = b'initial'
447 447
448 448 f = self._makefilefn()
449 449 with self._maketransactionfn() as tr:
450 450 node = f.add(fulltext, None, tr, 0, nullid, nullid)
451 451
452 452 self.assertEqual(f.storageinfo(), {})
453 453 self.assertEqual(f.storageinfo(revisionscount=True, trackedsize=True),
454 454 {'revisionscount': 1, 'trackedsize': len(fulltext)})
455 455
456 456 self.assertEqual(f.size(0), len(fulltext))
457 457
458 458 with self.assertRaises(IndexError):
459 459 f.size(1)
460 460
461 461 self.assertEqual(f.revision(node), fulltext)
462 462 self.assertEqual(f.revision(node, raw=True), fulltext)
463 463
464 464 self.assertEqual(f.read(node), fulltext)
465 465
466 466 self.assertFalse(f.renamed(node))
467 467
468 468 self.assertFalse(f.cmp(node, fulltext))
469 469 self.assertTrue(f.cmp(node, fulltext + b'extra'))
470 470
471 471 # Emitting a single revision works.
472 472 gen = f.emitrevisions([node])
473 473 rev = next(gen)
474 474
475 475 self.assertEqual(rev.node, node)
476 476 self.assertEqual(rev.p1node, nullid)
477 477 self.assertEqual(rev.p2node, nullid)
478 478 self.assertIsNone(rev.linknode)
479 479 self.assertEqual(rev.basenode, nullid)
480 480 self.assertIsNone(rev.baserevisionsize)
481 481 self.assertIsNone(rev.revision)
482 482 self.assertIsNone(rev.delta)
483 483
484 484 with self.assertRaises(StopIteration):
485 485 next(gen)
486 486
487 487 # Requesting revision data works.
488 488 gen = f.emitrevisions([node], revisiondata=True)
489 489 rev = next(gen)
490 490
491 491 self.assertEqual(rev.node, node)
492 492 self.assertEqual(rev.p1node, nullid)
493 493 self.assertEqual(rev.p2node, nullid)
494 494 self.assertIsNone(rev.linknode)
495 495 self.assertEqual(rev.basenode, nullid)
496 496 self.assertIsNone(rev.baserevisionsize)
497 497 self.assertEqual(rev.revision, fulltext)
498 498 self.assertIsNone(rev.delta)
499 499
500 500 with self.assertRaises(StopIteration):
501 501 next(gen)
502 502
503 503 # Emitting an unknown node after a known revision results in error.
504 504 with self.assertRaises(error.LookupError):
505 505 list(f.emitrevisions([node, b'\x01' * 20]))
506 506
507 507 def testmultiplerevisions(self):
508 508 fulltext0 = b'x' * 1024
509 509 fulltext1 = fulltext0 + b'y'
510 510 fulltext2 = b'y' + fulltext0 + b'z'
511 511
512 512 f = self._makefilefn()
513 513 with self._maketransactionfn() as tr:
514 514 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
515 515 node1 = f.add(fulltext1, None, tr, 1, node0, nullid)
516 516 node2 = f.add(fulltext2, None, tr, 3, node1, nullid)
517 517
518 518 self.assertEqual(f.storageinfo(), {})
519 519 self.assertEqual(
520 520 f.storageinfo(revisionscount=True, trackedsize=True),
521 521 {
522 522 'revisionscount': 3,
523 523 'trackedsize': len(fulltext0) + len(fulltext1) + len(fulltext2),
524 524 })
525 525
526 526 self.assertEqual(f.size(0), len(fulltext0))
527 527 self.assertEqual(f.size(1), len(fulltext1))
528 528 self.assertEqual(f.size(2), len(fulltext2))
529 529
530 530 with self.assertRaises(IndexError):
531 531 f.size(3)
532 532
533 533 self.assertEqual(f.revision(node0), fulltext0)
534 534 self.assertEqual(f.revision(node0, raw=True), fulltext0)
535 535 self.assertEqual(f.revision(node1), fulltext1)
536 536 self.assertEqual(f.revision(node1, raw=True), fulltext1)
537 537 self.assertEqual(f.revision(node2), fulltext2)
538 538 self.assertEqual(f.revision(node2, raw=True), fulltext2)
539 539
540 540 with self.assertRaises(error.LookupError):
541 541 f.revision(b'\x01' * 20)
542 542
543 543 self.assertEqual(f.read(node0), fulltext0)
544 544 self.assertEqual(f.read(node1), fulltext1)
545 545 self.assertEqual(f.read(node2), fulltext2)
546 546
547 547 with self.assertRaises(error.LookupError):
548 548 f.read(b'\x01' * 20)
549 549
550 550 self.assertFalse(f.renamed(node0))
551 551 self.assertFalse(f.renamed(node1))
552 552 self.assertFalse(f.renamed(node2))
553 553
554 554 with self.assertRaises(error.LookupError):
555 555 f.renamed(b'\x01' * 20)
556 556
557 557 self.assertFalse(f.cmp(node0, fulltext0))
558 558 self.assertFalse(f.cmp(node1, fulltext1))
559 559 self.assertFalse(f.cmp(node2, fulltext2))
560 560
561 561 self.assertTrue(f.cmp(node1, fulltext0))
562 562 self.assertTrue(f.cmp(node2, fulltext1))
563 563
564 564 with self.assertRaises(error.LookupError):
565 565 f.cmp(b'\x01' * 20, b'irrelevant')
566 566
567 567 # Nodes should be emitted in order.
568 568 gen = f.emitrevisions([node0, node1, node2], revisiondata=True)
569 569
570 570 rev = next(gen)
571 571
572 572 self.assertEqual(rev.node, node0)
573 573 self.assertEqual(rev.p1node, nullid)
574 574 self.assertEqual(rev.p2node, nullid)
575 575 self.assertIsNone(rev.linknode)
576 576 self.assertEqual(rev.basenode, nullid)
577 577 self.assertIsNone(rev.baserevisionsize)
578 578 self.assertEqual(rev.revision, fulltext0)
579 579 self.assertIsNone(rev.delta)
580 580
581 581 rev = next(gen)
582 582
583 583 self.assertEqual(rev.node, node1)
584 584 self.assertEqual(rev.p1node, node0)
585 585 self.assertEqual(rev.p2node, nullid)
586 586 self.assertIsNone(rev.linknode)
587 587 self.assertEqual(rev.basenode, node0)
588 588 self.assertIsNone(rev.baserevisionsize)
589 589 self.assertIsNone(rev.revision)
590 590 self.assertEqual(rev.delta,
591 591 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
592 592 fulltext1)
593 593
594 594 rev = next(gen)
595 595
596 596 self.assertEqual(rev.node, node2)
597 597 self.assertEqual(rev.p1node, node1)
598 598 self.assertEqual(rev.p2node, nullid)
599 599 self.assertIsNone(rev.linknode)
600 600 self.assertEqual(rev.basenode, node1)
601 601 self.assertIsNone(rev.baserevisionsize)
602 602 self.assertIsNone(rev.revision)
603 603 self.assertEqual(rev.delta,
604 604 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
605 605 fulltext2)
606 606
607 607 with self.assertRaises(StopIteration):
608 608 next(gen)
609 609
610 610 # Request not in DAG order is reordered to be in DAG order.
611 611 gen = f.emitrevisions([node2, node1, node0], revisiondata=True)
612 612
613 613 rev = next(gen)
614 614
615 615 self.assertEqual(rev.node, node0)
616 616 self.assertEqual(rev.p1node, nullid)
617 617 self.assertEqual(rev.p2node, nullid)
618 618 self.assertIsNone(rev.linknode)
619 619 self.assertEqual(rev.basenode, nullid)
620 620 self.assertIsNone(rev.baserevisionsize)
621 621 self.assertEqual(rev.revision, fulltext0)
622 622 self.assertIsNone(rev.delta)
623 623
624 624 rev = next(gen)
625 625
626 626 self.assertEqual(rev.node, node1)
627 627 self.assertEqual(rev.p1node, node0)
628 628 self.assertEqual(rev.p2node, nullid)
629 629 self.assertIsNone(rev.linknode)
630 630 self.assertEqual(rev.basenode, node0)
631 631 self.assertIsNone(rev.baserevisionsize)
632 632 self.assertIsNone(rev.revision)
633 633 self.assertEqual(rev.delta,
634 634 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
635 635 fulltext1)
636 636
637 637 rev = next(gen)
638 638
639 639 self.assertEqual(rev.node, node2)
640 640 self.assertEqual(rev.p1node, node1)
641 641 self.assertEqual(rev.p2node, nullid)
642 642 self.assertIsNone(rev.linknode)
643 643 self.assertEqual(rev.basenode, node1)
644 644 self.assertIsNone(rev.baserevisionsize)
645 645 self.assertIsNone(rev.revision)
646 646 self.assertEqual(rev.delta,
647 647 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
648 648 fulltext2)
649 649
650 650 with self.assertRaises(StopIteration):
651 651 next(gen)
652 652
653 653 # Unrecognized nodesorder value raises ProgrammingError.
654 654 with self.assertRaises(error.ProgrammingError):
655 655 list(f.emitrevisions([], nodesorder='bad'))
656 656
657 657 # nodesorder=storage is recognized. But we can't test it thoroughly
658 658 # because behavior is storage-dependent.
659 659 res = list(f.emitrevisions([node2, node1, node0],
660 660 nodesorder='storage'))
661 661 self.assertEqual(len(res), 3)
662 662 self.assertEqual({o.node for o in res}, {node0, node1, node2})
663 663
664 664 # nodesorder=nodes forces the order.
665 665 gen = f.emitrevisions([node2, node0], nodesorder='nodes',
666 666 revisiondata=True)
667 667
668 668 rev = next(gen)
669 669 self.assertEqual(rev.node, node2)
670 670 self.assertEqual(rev.p1node, node1)
671 671 self.assertEqual(rev.p2node, nullid)
672 672 self.assertEqual(rev.basenode, nullid)
673 673 self.assertIsNone(rev.baserevisionsize)
674 674 self.assertEqual(rev.revision, fulltext2)
675 675 self.assertIsNone(rev.delta)
676 676
677 677 rev = next(gen)
678 678 self.assertEqual(rev.node, node0)
679 679 self.assertEqual(rev.p1node, nullid)
680 680 self.assertEqual(rev.p2node, nullid)
681 681 # Delta behavior is storage dependent, so we can't easily test it.
682 682
683 683 with self.assertRaises(StopIteration):
684 684 next(gen)
685 685
686 686 # assumehaveparentrevisions=False (the default) won't send a delta for
687 687 # the first revision.
688 688 gen = f.emitrevisions({node2, node1}, revisiondata=True)
689 689
690 690 rev = next(gen)
691 691 self.assertEqual(rev.node, node1)
692 692 self.assertEqual(rev.p1node, node0)
693 693 self.assertEqual(rev.p2node, nullid)
694 694 self.assertEqual(rev.basenode, nullid)
695 695 self.assertIsNone(rev.baserevisionsize)
696 696 self.assertEqual(rev.revision, fulltext1)
697 697 self.assertIsNone(rev.delta)
698 698
699 699 rev = next(gen)
700 700 self.assertEqual(rev.node, node2)
701 701 self.assertEqual(rev.p1node, node1)
702 702 self.assertEqual(rev.p2node, nullid)
703 703 self.assertEqual(rev.basenode, node1)
704 704 self.assertIsNone(rev.baserevisionsize)
705 705 self.assertIsNone(rev.revision)
706 706 self.assertEqual(rev.delta,
707 707 b'\x00\x00\x00\x00\x00\x00\x04\x01\x00\x00\x04\x02' +
708 708 fulltext2)
709 709
710 710 with self.assertRaises(StopIteration):
711 711 next(gen)
712 712
713 713 # assumehaveparentrevisions=True allows delta against initial revision.
714 714 gen = f.emitrevisions([node2, node1],
715 715 revisiondata=True, assumehaveparentrevisions=True)
716 716
717 717 rev = next(gen)
718 718 self.assertEqual(rev.node, node1)
719 719 self.assertEqual(rev.p1node, node0)
720 720 self.assertEqual(rev.p2node, nullid)
721 721 self.assertEqual(rev.basenode, node0)
722 722 self.assertIsNone(rev.baserevisionsize)
723 723 self.assertIsNone(rev.revision)
724 724 self.assertEqual(rev.delta,
725 725 b'\x00\x00\x00\x00\x00\x00\x04\x00\x00\x00\x04\x01' +
726 726 fulltext1)
727 727
728 728 # forceprevious=True forces a delta against the previous revision.
729 729 # Special case for initial revision.
730 730 gen = f.emitrevisions([node0], revisiondata=True, deltaprevious=True)
731 731
732 732 rev = next(gen)
733 733 self.assertEqual(rev.node, node0)
734 734 self.assertEqual(rev.p1node, nullid)
735 735 self.assertEqual(rev.p2node, nullid)
736 736 self.assertEqual(rev.basenode, nullid)
737 737 self.assertIsNone(rev.baserevisionsize)
738 738 self.assertIsNone(rev.revision)
739 739 self.assertEqual(rev.delta,
740 740 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' +
741 741 fulltext0)
742 742
743 743 with self.assertRaises(StopIteration):
744 744 next(gen)
745 745
746 746 gen = f.emitrevisions([node0, node2], revisiondata=True,
747 747 deltaprevious=True)
748 748
749 749 rev = next(gen)
750 750 self.assertEqual(rev.node, node0)
751 751 self.assertEqual(rev.p1node, nullid)
752 752 self.assertEqual(rev.p2node, nullid)
753 753 self.assertEqual(rev.basenode, nullid)
754 754 self.assertIsNone(rev.baserevisionsize)
755 755 self.assertIsNone(rev.revision)
756 756 self.assertEqual(rev.delta,
757 757 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x04\x00' +
758 758 fulltext0)
759 759
760 760 rev = next(gen)
761 761 self.assertEqual(rev.node, node2)
762 762 self.assertEqual(rev.p1node, node1)
763 763 self.assertEqual(rev.p2node, nullid)
764 764 self.assertEqual(rev.basenode, node0)
765 765
766 766 with self.assertRaises(StopIteration):
767 767 next(gen)
768 768
769 769 def testrenamed(self):
770 770 fulltext0 = b'foo'
771 771 fulltext1 = b'bar'
772 772 fulltext2 = b'baz'
773 773
774 774 meta1 = {
775 775 b'copy': b'source0',
776 776 b'copyrev': b'a' * 40,
777 777 }
778 778
779 779 meta2 = {
780 780 b'copy': b'source1',
781 781 b'copyrev': b'b' * 40,
782 782 }
783 783
784 784 stored1 = b''.join([
785 785 b'\x01\ncopy: source0\n',
786 786 b'copyrev: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\n\x01\n',
787 787 fulltext1,
788 788 ])
789 789
790 790 stored2 = b''.join([
791 791 b'\x01\ncopy: source1\n',
792 792 b'copyrev: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb\n\x01\n',
793 793 fulltext2,
794 794 ])
795 795
796 796 f = self._makefilefn()
797 797 with self._maketransactionfn() as tr:
798 798 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
799 799 node1 = f.add(fulltext1, meta1, tr, 1, node0, nullid)
800 800 node2 = f.add(fulltext2, meta2, tr, 2, nullid, nullid)
801 801
802 802 # Metadata header isn't recognized when parent isn't nullid.
803 803 self.assertEqual(f.size(1), len(stored1))
804 804 self.assertEqual(f.size(2), len(fulltext2))
805 805
806 806 self.assertEqual(f.revision(node1), stored1)
807 807 self.assertEqual(f.revision(node1, raw=True), stored1)
808 808 self.assertEqual(f.revision(node2), stored2)
809 809 self.assertEqual(f.revision(node2, raw=True), stored2)
810 810
811 811 self.assertEqual(f.read(node1), fulltext1)
812 812 self.assertEqual(f.read(node2), fulltext2)
813 813
814 814 # Returns False when first parent is set.
815 815 self.assertFalse(f.renamed(node1))
816 816 self.assertEqual(f.renamed(node2), (b'source1', b'\xbb' * 20))
817 817
818 818 self.assertTrue(f.cmp(node1, fulltext1))
819 819 self.assertTrue(f.cmp(node1, stored1))
820 820 self.assertFalse(f.cmp(node2, fulltext2))
821 821 self.assertTrue(f.cmp(node2, stored2))
822 822
823 823 def testmetadataprefix(self):
824 824 # Content with metadata prefix has extra prefix inserted in storage.
825 825 fulltext0 = b'\x01\nfoo'
826 826 stored0 = b'\x01\n\x01\n\x01\nfoo'
827 827
828 828 fulltext1 = b'\x01\nbar'
829 829 meta1 = {
830 830 b'copy': b'source0',
831 831 b'copyrev': b'b' * 40,
832 832 }
833 833 stored1 = b''.join([
834 834 b'\x01\ncopy: source0\n',
835 835 b'copyrev: %s\n' % (b'b' * 40),
836 836 b'\x01\n\x01\nbar',
837 837 ])
838 838
839 839 f = self._makefilefn()
840 840 with self._maketransactionfn() as tr:
841 841 node0 = f.add(fulltext0, {}, tr, 0, nullid, nullid)
842 842 node1 = f.add(fulltext1, meta1, tr, 1, nullid, nullid)
843 843
844 844 # TODO this is buggy.
845 845 self.assertEqual(f.size(0), len(fulltext0) + 4)
846 846
847 847 self.assertEqual(f.size(1), len(fulltext1))
848 848
849 849 self.assertEqual(f.revision(node0), stored0)
850 850 self.assertEqual(f.revision(node0, raw=True), stored0)
851 851
852 852 self.assertEqual(f.revision(node1), stored1)
853 853 self.assertEqual(f.revision(node1, raw=True), stored1)
854 854
855 855 self.assertEqual(f.read(node0), fulltext0)
856 856 self.assertEqual(f.read(node1), fulltext1)
857 857
858 858 self.assertFalse(f.cmp(node0, fulltext0))
859 859 self.assertTrue(f.cmp(node0, stored0))
860 860
861 861 self.assertFalse(f.cmp(node1, fulltext1))
862 862 self.assertTrue(f.cmp(node1, stored0))
863 863
864 864 def testcensored(self):
865 865 f = self._makefilefn()
866 866
867 867 stored1 = storageutil.packmeta({
868 868 b'censored': b'tombstone',
869 869 }, b'')
870 870
871 871 # TODO tests are incomplete because we need the node to be
872 872 # different due to presence of censor metadata. But we can't
873 873 # do this with addrevision().
874 874 with self._maketransactionfn() as tr:
875 875 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
876 876 f.addrevision(stored1, tr, 1, node0, nullid,
877 877 flags=repository.REVISION_FLAG_CENSORED)
878 878
879 879 self.assertTrue(f.iscensored(1))
880 880
881 881 self.assertEqual(f.revision(1), stored1)
882 882 self.assertEqual(f.revision(1, raw=True), stored1)
883 883
884 884 self.assertEqual(f.read(1), b'')
885 885
886 886 class ifilemutationtests(basetestcase):
887 887 """Generic tests for the ifilemutation interface.
888 888
889 889 All file storage backends that support writing should conform to this
890 890 interface.
891 891
892 892 Use ``makeifilemutationtests()`` to create an instance of this type.
893 893 """
894 894 def testaddnoop(self):
895 895 f = self._makefilefn()
896 896 with self._maketransactionfn() as tr:
897 897 node0 = f.add(b'foo', None, tr, 0, nullid, nullid)
898 898 node1 = f.add(b'foo', None, tr, 0, nullid, nullid)
899 899 # Varying by linkrev shouldn't impact hash.
900 900 node2 = f.add(b'foo', None, tr, 1, nullid, nullid)
901 901
902 902 self.assertEqual(node1, node0)
903 903 self.assertEqual(node2, node0)
904 904 self.assertEqual(len(f), 1)
905 905
906 906 def testaddrevisionbadnode(self):
907 907 f = self._makefilefn()
908 908 with self._maketransactionfn() as tr:
909 909 # Adding a revision with bad node value fails.
910 910 with self.assertRaises(error.StorageError):
911 911 f.addrevision(b'foo', tr, 0, nullid, nullid, node=b'\x01' * 20)
912 912
913 913 def testaddrevisionunknownflag(self):
914 914 f = self._makefilefn()
915 915 with self._maketransactionfn() as tr:
916 916 for i in range(15, 0, -1):
917 917 if (1 << i) & ~repository.REVISION_FLAGS_KNOWN:
918 918 flags = 1 << i
919 919 break
920 920
921 921 with self.assertRaises(error.StorageError):
922 922 f.addrevision(b'foo', tr, 0, nullid, nullid, flags=flags)
923 923
924 924 def testaddgroupsimple(self):
925 925 f = self._makefilefn()
926 926
927 927 callbackargs = []
928 928 def cb(*args, **kwargs):
929 929 callbackargs.append((args, kwargs))
930 930
931 931 def linkmapper(node):
932 932 return 0
933 933
934 934 with self._maketransactionfn() as tr:
935 935 nodes = f.addgroup([], None, tr, addrevisioncb=cb)
936 936
937 937 self.assertEqual(nodes, [])
938 938 self.assertEqual(callbackargs, [])
939 939 self.assertEqual(len(f), 0)
940 940
941 941 fulltext0 = b'foo'
942 942 delta0 = mdiff.trivialdiffheader(len(fulltext0)) + fulltext0
943 943
944 944 deltas = [
945 945 (b'\x01' * 20, nullid, nullid, nullid, nullid, delta0, 0),
946 946 ]
947 947
948 948 with self._maketransactionfn() as tr:
949 949 with self.assertRaises(error.StorageError):
950 950 f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
951 951
952 952 node0 = f.add(fulltext0, None, tr, 0, nullid, nullid)
953 953
954 954 f = self._makefilefn()
955 955
956 956 deltas = [
957 957 (node0, nullid, nullid, nullid, nullid, delta0, 0),
958 958 ]
959 959
960 960 with self._maketransactionfn() as tr:
961 961 nodes = f.addgroup(deltas, linkmapper, tr, addrevisioncb=cb)
962 962
963 963 self.assertEqual(nodes, [
964 964 b'\x49\xd8\xcb\xb1\x5c\xe2\x57\x92\x04\x47'
965 965 b'\x00\x6b\x46\x97\x8b\x7a\xf9\x80\xa9\x79'])
966 966
967 967 self.assertEqual(len(callbackargs), 1)
968 968 self.assertEqual(callbackargs[0][0][1], nodes[0])
969 969
970 970 self.assertEqual(list(f.revs()), [0])
971 971 self.assertEqual(f.rev(nodes[0]), 0)
972 972 self.assertEqual(f.node(0), nodes[0])
973 973
974 974 def testaddgroupmultiple(self):
975 975 f = self._makefilefn()
976 976
977 977 fulltexts = [
978 978 b'foo',
979 979 b'bar',
980 980 b'x' * 1024,
981 981 ]
982 982
983 983 nodes = []
984 984 with self._maketransactionfn() as tr:
985 985 for fulltext in fulltexts:
986 986 nodes.append(f.add(fulltext, None, tr, 0, nullid, nullid))
987 987
988 988 f = self._makefilefn()
989 989 deltas = []
990 990 for i, fulltext in enumerate(fulltexts):
991 991 delta = mdiff.trivialdiffheader(len(fulltext)) + fulltext
992 992
993 993 deltas.append((nodes[i], nullid, nullid, nullid, nullid, delta, 0))
994 994
995 995 with self._maketransactionfn() as tr:
996 996 self.assertEqual(f.addgroup(deltas, lambda x: 0, tr), nodes)
997 997
998 998 self.assertEqual(len(f), len(deltas))
999 999 self.assertEqual(list(f.revs()), [0, 1, 2])
1000 1000 self.assertEqual(f.rev(nodes[0]), 0)
1001 1001 self.assertEqual(f.rev(nodes[1]), 1)
1002 1002 self.assertEqual(f.rev(nodes[2]), 2)
1003 1003 self.assertEqual(f.node(0), nodes[0])
1004 1004 self.assertEqual(f.node(1), nodes[1])
1005 1005 self.assertEqual(f.node(2), nodes[2])
1006 1006
1007 def testgetstrippointnoparents(self):
1008 # N revisions where none have parents.
1009 f = self._makefilefn()
1010
1011 with self._maketransactionfn() as tr:
1012 for rev in range(10):
1013 f.add(b'%d' % rev, None, tr, rev, nullid, nullid)
1014
1015 for rev in range(10):
1016 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1017
1018 def testgetstrippointlinear(self):
1019 # N revisions in a linear chain.
1020 f = self._makefilefn()
1021
1022 with self._maketransactionfn() as tr:
1023 p1 = nullid
1024
1025 for rev in range(10):
1026 f.add(b'%d' % rev, None, tr, rev, p1, nullid)
1027
1028 for rev in range(10):
1029 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1030
1031 def testgetstrippointmultipleheads(self):
1032 f = self._makefilefn()
1033
1034 with self._maketransactionfn() as tr:
1035 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
1036 node1 = f.add(b'1', None, tr, 1, node0, nullid)
1037 f.add(b'2', None, tr, 2, node1, nullid)
1038 f.add(b'3', None, tr, 3, node0, nullid)
1039 f.add(b'4', None, tr, 4, node0, nullid)
1040
1041 for rev in range(5):
1042 self.assertEqual(f.getstrippoint(rev), (rev, set()))
1043
1044 def testgetstrippointearlierlinkrevs(self):
1045 f = self._makefilefn()
1046
1047 with self._maketransactionfn() as tr:
1048 node0 = f.add(b'0', None, tr, 0, nullid, nullid)
1049 f.add(b'1', None, tr, 10, node0, nullid)
1050 f.add(b'2', None, tr, 5, node0, nullid)
1051
1052 self.assertEqual(f.getstrippoint(0), (0, set()))
1053 self.assertEqual(f.getstrippoint(1), (1, set()))
1054 self.assertEqual(f.getstrippoint(2), (1, set()))
1055 self.assertEqual(f.getstrippoint(3), (1, set()))
1056 self.assertEqual(f.getstrippoint(4), (1, set()))
1057 self.assertEqual(f.getstrippoint(5), (1, set()))
1058 self.assertEqual(f.getstrippoint(6), (1, {2}))
1059 self.assertEqual(f.getstrippoint(7), (1, {2}))
1060 self.assertEqual(f.getstrippoint(8), (1, {2}))
1061 self.assertEqual(f.getstrippoint(9), (1, {2}))
1062 self.assertEqual(f.getstrippoint(10), (1, {2}))
1063 self.assertEqual(f.getstrippoint(11), (3, set()))
1064
1065 def teststripempty(self):
1066 f = self._makefilefn()
1067
1068 with self._maketransactionfn() as tr:
1069 f.strip(0, tr)
1070
1071 self.assertEqual(len(f), 0)
1072
1073 def teststripall(self):
1074 f = self._makefilefn()
1075
1076 with self._maketransactionfn() as tr:
1077 p1 = nullid
1078 for rev in range(10):
1079 p1 = f.add(b'%d' % rev, None, tr, rev, p1, nullid)
1080
1081 self.assertEqual(len(f), 10)
1082
1083 with self._maketransactionfn() as tr:
1084 f.strip(0, tr)
1085
1086 self.assertEqual(len(f), 0)
1087
1088 def teststrippartial(self):
1089 f = self._makefilefn()
1090
1091 with self._maketransactionfn() as tr:
1092 f.add(b'0', None, tr, 0, nullid, nullid)
1093 node1 = f.add(b'1', None, tr, 5, nullid, nullid)
1094 node2 = f.add(b'2', None, tr, 10, nullid, nullid)
1095
1096 self.assertEqual(len(f), 3)
1097
1098 with self._maketransactionfn() as tr:
1099 f.strip(11, tr)
1100
1101 self.assertEqual(len(f), 3)
1102
1103 with self._maketransactionfn() as tr:
1104 f.strip(10, tr)
1105
1106 self.assertEqual(len(f), 2)
1107
1108 with self.assertRaises(error.LookupError):
1109 f.rev(node2)
1110
1111 with self._maketransactionfn() as tr:
1112 f.strip(6, tr)
1113
1114 self.assertEqual(len(f), 2)
1115
1116 with self._maketransactionfn() as tr:
1117 f.strip(3, tr)
1118
1119 self.assertEqual(len(f), 1)
1120
1121 with self.assertRaises(error.LookupError):
1122 f.rev(node1)
1123
1007 1124 def makeifileindextests(makefilefn, maketransactionfn):
1008 1125 """Create a unittest.TestCase class suitable for testing file storage.
1009 1126
1010 1127 ``makefilefn`` is a callable which receives the test case as an
1011 1128 argument and returns an object implementing the ``ifilestorage`` interface.
1012 1129
1013 1130 ``maketransactionfn`` is a callable which receives the test case as an
1014 1131 argument and returns a transaction object.
1015 1132
1016 1133 Returns a type that is a ``unittest.TestCase`` that can be used for
1017 1134 testing the object implementing the file storage interface. Simply
1018 1135 assign the returned value to a module-level attribute and a test loader
1019 1136 should find and run it automatically.
1020 1137 """
1021 1138 d = {
1022 1139 r'_makefilefn': makefilefn,
1023 1140 r'_maketransactionfn': maketransactionfn,
1024 1141 }
1025 1142 return type(r'ifileindextests', (ifileindextests,), d)
1026 1143
1027 1144 def makeifiledatatests(makefilefn, maketransactionfn):
1028 1145 d = {
1029 1146 r'_makefilefn': makefilefn,
1030 1147 r'_maketransactionfn': maketransactionfn,
1031 1148 }
1032 1149 return type(r'ifiledatatests', (ifiledatatests,), d)
1033 1150
1034 1151 def makeifilemutationtests(makefilefn, maketransactionfn):
1035 1152 d = {
1036 1153 r'_makefilefn': makefilefn,
1037 1154 r'_maketransactionfn': maketransactionfn,
1038 1155 }
1039 1156 return type(r'ifilemutationtests', (ifilemutationtests,), d)
General Comments 0
You need to be logged in to leave comments. Login now