##// END OF EJS Templates
test-manifest: add some test coverage for treemanifest...
Drew Gottlieb -
r24656:29c238e4 default
parent child Browse files
Show More
@@ -1,464 +1,468 b''
1 1 import binascii
2 2 import unittest
3 3 import itertools
4 4
5 5 import silenttestrunner
6 6
7 7 from mercurial import manifest as manifestmod
8 8 from mercurial import match as matchmod
9 9
10 10 EMTPY_MANIFEST = ''
11 11 EMTPY_MANIFEST_V2 = '\0\n'
12 12
13 13 HASH_1 = '1' * 40
14 14 BIN_HASH_1 = binascii.unhexlify(HASH_1)
15 15 HASH_2 = 'f' * 40
16 16 BIN_HASH_2 = binascii.unhexlify(HASH_2)
17 17 HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
18 18 BIN_HASH_3 = binascii.unhexlify(HASH_3)
19 19 A_SHORT_MANIFEST = (
20 20 'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
21 21 'foo\0%(hash1)s%(flag1)s\n'
22 22 ) % {'hash1': HASH_1,
23 23 'flag1': '',
24 24 'hash2': HASH_2,
25 25 'flag2': 'l',
26 26 }
27 27
28 28 # Same data as A_SHORT_MANIFEST
29 29 A_SHORT_MANIFEST_V2 = (
30 30 '\0\n'
31 31 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
32 32 '\x00foo\0%(flag1)s\n%(hash1)s\n'
33 33 ) % {'hash1': BIN_HASH_1,
34 34 'flag1': '',
35 35 'hash2': BIN_HASH_2,
36 36 'flag2': 'l',
37 37 }
38 38
39 39 # Same data as A_SHORT_MANIFEST
40 40 A_METADATA_MANIFEST = (
41 41 '\0foo\0bar\n'
42 42 '\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata
43 43 '\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata
44 44 ) % {'hash1': BIN_HASH_1,
45 45 'flag1': '',
46 46 'hash2': BIN_HASH_2,
47 47 'flag2': 'l',
48 48 }
49 49
50 50 A_STEM_COMPRESSED_MANIFEST = (
51 51 '\0\n'
52 52 '\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n'
53 53 '\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars
54 54 '\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters
55 55 '\x00%(verylongdir)sx/x\0\n%(hash1)s\n'
56 56 '\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars
57 57 ) % {'hash1': BIN_HASH_1,
58 58 'flag1': '',
59 59 'hash2': BIN_HASH_2,
60 60 'flag2': 'l',
61 61 'verylongdir': 255 * 'x',
62 62 }
63 63
64 64 A_DEEPER_MANIFEST = (
65 65 'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
66 66 'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
67 67 'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
68 68 'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
69 69 'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
70 70 'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
71 71 'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
72 72 'a/b/dog.py\0%(hash3)s%(flag1)s\n'
73 73 'a/b/fish.py\0%(hash2)s%(flag1)s\n'
74 74 'a/c/london.py\0%(hash3)s%(flag2)s\n'
75 75 'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
76 76 'a/c/paris.py\0%(hash2)s%(flag1)s\n'
77 77 'a/d/apple.py\0%(hash3)s%(flag1)s\n'
78 78 'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
79 79 'a/green.py\0%(hash1)s%(flag2)s\n'
80 80 'a/purple.py\0%(hash2)s%(flag1)s\n'
81 81 'app.py\0%(hash3)s%(flag1)s\n'
82 82 'readme.txt\0%(hash2)s%(flag1)s\n'
83 83 ) % {'hash1': HASH_1,
84 84 'flag1': '',
85 85 'hash2': HASH_2,
86 86 'flag2': 'l',
87 87 'hash3': HASH_3,
88 88 }
89 89
90 90 HUGE_MANIFEST_ENTRIES = 200001
91 91
92 92 A_HUGE_MANIFEST = ''.join(sorted(
93 93 'file%d\0%s%s\n' % (i, h, f) for i, h, f in
94 94 itertools.izip(xrange(200001),
95 95 itertools.cycle((HASH_1, HASH_2)),
96 96 itertools.cycle(('', 'x', 'l')))))
97 97
98 98 class basemanifesttests(object):
99 99 def parsemanifest(self, text):
100 100 raise NotImplementedError('parsemanifest not implemented by test case')
101 101
102 102 def assertIn(self, thing, container, msg=None):
103 103 # assertIn new in 2.7, use it if available, otherwise polyfill
104 104 sup = getattr(unittest.TestCase, 'assertIn', False)
105 105 if sup:
106 106 return sup(self, thing, container, msg=msg)
107 107 if not msg:
108 108 msg = 'Expected %r in %r' % (thing, container)
109 109 self.assert_(thing in container, msg)
110 110
111 111 def testEmptyManifest(self):
112 112 m = self.parsemanifest(EMTPY_MANIFEST)
113 113 self.assertEqual(0, len(m))
114 114 self.assertEqual([], list(m))
115 115
116 116 def testEmptyManifestv2(self):
117 117 m = self.parsemanifest(EMTPY_MANIFEST_V2)
118 118 self.assertEqual(0, len(m))
119 119 self.assertEqual([], list(m))
120 120
121 121 def testManifest(self):
122 122 m = self.parsemanifest(A_SHORT_MANIFEST)
123 123 self.assertEqual(['bar/baz/qux.py', 'foo'], list(m))
124 124 self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py'])
125 125 self.assertEqual('l', m.flags('bar/baz/qux.py'))
126 126 self.assertEqual(BIN_HASH_1, m['foo'])
127 127 self.assertEqual('', m.flags('foo'))
128 128 self.assertRaises(KeyError, lambda : m['wat'])
129 129
130 130 def testParseManifestV2(self):
131 131 m1 = self.parsemanifest(A_SHORT_MANIFEST)
132 132 m2 = self.parsemanifest(A_SHORT_MANIFEST_V2)
133 133 # Should have same content as A_SHORT_MANIFEST
134 134 self.assertEqual(m1.text(), m2.text())
135 135
136 136 def testParseManifestMetadata(self):
137 137 # Metadata is for future-proofing and should be accepted but ignored
138 138 m = self.parsemanifest(A_METADATA_MANIFEST)
139 139 self.assertEqual(A_SHORT_MANIFEST, m.text())
140 140
141 141 def testParseManifestStemCompression(self):
142 142 m = self.parsemanifest(A_STEM_COMPRESSED_MANIFEST)
143 143 self.assertIn('bar/baz/qux.py', m)
144 144 self.assertIn('bar/qux/foo.py', m)
145 145 self.assertIn('bar/qux/foz.py', m)
146 146 self.assertIn(256 * 'x' + '/x', m)
147 147 self.assertIn(256 * 'x' + '/y', m)
148 148 self.assertEqual(A_STEM_COMPRESSED_MANIFEST, m.text(usemanifestv2=True))
149 149
150 150 def testTextV2(self):
151 151 m1 = self.parsemanifest(A_SHORT_MANIFEST)
152 152 v2text = m1.text(usemanifestv2=True)
153 153 self.assertEqual(A_SHORT_MANIFEST_V2, v2text)
154 154
155 155 def testSetItem(self):
156 156 want = BIN_HASH_1
157 157
158 158 m = self.parsemanifest(EMTPY_MANIFEST)
159 159 m['a'] = want
160 160 self.assertIn('a', m)
161 161 self.assertEqual(want, m['a'])
162 162 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
163 163
164 164 m = self.parsemanifest(A_SHORT_MANIFEST)
165 165 m['a'] = want
166 166 self.assertEqual(want, m['a'])
167 167 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
168 168 m.text())
169 169
170 170 def testSetFlag(self):
171 171 want = 'x'
172 172
173 173 m = self.parsemanifest(EMTPY_MANIFEST)
174 174 # first add a file; a file-less flag makes no sense
175 175 m['a'] = BIN_HASH_1
176 176 m.setflag('a', want)
177 177 self.assertEqual(want, m.flags('a'))
178 178 self.assertEqual('a\0' + HASH_1 + want + '\n', m.text())
179 179
180 180 m = self.parsemanifest(A_SHORT_MANIFEST)
181 181 # first add a file; a file-less flag makes no sense
182 182 m['a'] = BIN_HASH_1
183 183 m.setflag('a', want)
184 184 self.assertEqual(want, m.flags('a'))
185 185 self.assertEqual('a\0' + HASH_1 + want + '\n' + A_SHORT_MANIFEST,
186 186 m.text())
187 187
188 188 def testCopy(self):
189 189 m = self.parsemanifest(A_SHORT_MANIFEST)
190 190 m['a'] = BIN_HASH_1
191 191 m2 = m.copy()
192 192 del m
193 193 del m2 # make sure we don't double free() anything
194 194
195 195 def testCompaction(self):
196 196 unhex = binascii.unhexlify
197 197 h1, h2 = unhex(HASH_1), unhex(HASH_2)
198 198 m = self.parsemanifest(A_SHORT_MANIFEST)
199 199 m['alpha'] = h1
200 200 m['beta'] = h2
201 201 del m['foo']
202 202 want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
203 203 HASH_1, HASH_2, HASH_2)
204 204 self.assertEqual(want, m.text())
205 205 self.assertEqual(3, len(m))
206 206 self.assertEqual(['alpha', 'bar/baz/qux.py', 'beta'], list(m))
207 207 self.assertEqual(h1, m['alpha'])
208 208 self.assertEqual(h2, m['bar/baz/qux.py'])
209 209 self.assertEqual(h2, m['beta'])
210 210 self.assertEqual('', m.flags('alpha'))
211 211 self.assertEqual('l', m.flags('bar/baz/qux.py'))
212 212 self.assertEqual('', m.flags('beta'))
213 213 self.assertRaises(KeyError, lambda : m['foo'])
214 214
215 215 def testSetGetNodeSuffix(self):
216 216 clean = self.parsemanifest(A_SHORT_MANIFEST)
217 217 m = self.parsemanifest(A_SHORT_MANIFEST)
218 218 h = m['foo']
219 219 f = m.flags('foo')
220 220 want = h + 'a'
221 221 # Merge code wants to set 21-byte fake hashes at times
222 222 m['foo'] = want
223 223 self.assertEqual(want, m['foo'])
224 224 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
225 225 ('foo', BIN_HASH_1 + 'a')],
226 226 list(m.iteritems()))
227 227 # Sometimes it even tries a 22-byte fake hash, but we can
228 228 # return 21 and it'll work out
229 229 m['foo'] = want + '+'
230 230 self.assertEqual(want, m['foo'])
231 231 # make sure the suffix survives a copy
232 232 match = matchmod.match('', '', ['re:foo'])
233 233 m2 = m.matches(match)
234 234 self.assertEqual(want, m2['foo'])
235 235 self.assertEqual(1, len(m2))
236 236 m2 = m.copy()
237 237 self.assertEqual(want, m2['foo'])
238 238 # suffix with iteration
239 239 self.assertEqual([('bar/baz/qux.py', BIN_HASH_2),
240 240 ('foo', want)],
241 241 list(m.iteritems()))
242 242
243 243 # shows up in diff
244 244 self.assertEqual({'foo': ((want, f), (h, ''))}, m.diff(clean))
245 245 self.assertEqual({'foo': ((h, ''), (want, f))}, clean.diff(m))
246 246
247 247 def testMatchException(self):
248 248 m = self.parsemanifest(A_SHORT_MANIFEST)
249 249 match = matchmod.match('', '', ['re:.*'])
250 250 def filt(path):
251 251 if path == 'foo':
252 252 assert False
253 253 return True
254 254 match.matchfn = filt
255 255 self.assertRaises(AssertionError, m.matches, match)
256 256
257 257 def testRemoveItem(self):
258 258 m = self.parsemanifest(A_SHORT_MANIFEST)
259 259 del m['foo']
260 260 self.assertRaises(KeyError, lambda : m['foo'])
261 261 self.assertEqual(1, len(m))
262 262 self.assertEqual(1, len(list(m)))
263 263 # now restore and make sure everything works right
264 264 m['foo'] = 'a' * 20
265 265 self.assertEqual(2, len(m))
266 266 self.assertEqual(2, len(list(m)))
267 267
268 268 def testManifestDiff(self):
269 269 MISSING = (None, '')
270 270 addl = 'z-only-in-left\0' + HASH_1 + '\n'
271 271 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
272 272 left = self.parsemanifest(
273 273 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
274 274 right = self.parsemanifest(A_SHORT_MANIFEST + addr)
275 275 want = {
276 276 'foo': ((BIN_HASH_3, 'x'),
277 277 (BIN_HASH_1, '')),
278 278 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
279 279 'z-only-in-right': (MISSING, (BIN_HASH_2, 'x')),
280 280 }
281 281 self.assertEqual(want, left.diff(right))
282 282
283 283 want = {
284 284 'bar/baz/qux.py': (MISSING, (BIN_HASH_2, 'l')),
285 285 'foo': (MISSING, (BIN_HASH_3, 'x')),
286 286 'z-only-in-left': (MISSING, (BIN_HASH_1, '')),
287 287 }
288 288 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left))
289 289
290 290 want = {
291 291 'bar/baz/qux.py': ((BIN_HASH_2, 'l'), MISSING),
292 292 'foo': ((BIN_HASH_3, 'x'), MISSING),
293 293 'z-only-in-left': ((BIN_HASH_1, ''), MISSING),
294 294 }
295 295 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST)))
296 296 copy = right.copy()
297 297 del copy['z-only-in-right']
298 298 del right['foo']
299 299 want = {
300 300 'foo': (MISSING, (BIN_HASH_1, '')),
301 301 'z-only-in-right': ((BIN_HASH_2, 'x'), MISSING),
302 302 }
303 303 self.assertEqual(want, right.diff(copy))
304 304
305 305 short = self.parsemanifest(A_SHORT_MANIFEST)
306 306 pruned = short.copy()
307 307 del pruned['foo']
308 308 want = {
309 309 'foo': ((BIN_HASH_1, ''), MISSING),
310 310 }
311 311 self.assertEqual(want, short.diff(pruned))
312 312 want = {
313 313 'foo': (MISSING, (BIN_HASH_1, '')),
314 314 }
315 315 self.assertEqual(want, pruned.diff(short))
316 316 want = {
317 317 'bar/baz/qux.py': None,
318 318 'foo': (MISSING, (BIN_HASH_1, '')),
319 319 }
320 320 self.assertEqual(want, pruned.diff(short, True))
321 321
322 322 def testReversedLines(self):
323 323 backwards = ''.join(
324 324 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
325 325 try:
326 326 self.parsemanifest(backwards)
327 327 self.fail('Should have raised ValueError')
328 328 except ValueError, v:
329 329 self.assertIn('Manifest lines not in sorted order.', str(v))
330 330
331 331 def testNoTerminalNewline(self):
332 332 try:
333 333 self.parsemanifest(A_SHORT_MANIFEST + 'wat')
334 334 self.fail('Should have raised ValueError')
335 335 except ValueError, v:
336 336 self.assertIn('Manifest did not end in a newline.', str(v))
337 337
338 338 def testNoNewLineAtAll(self):
339 339 try:
340 340 self.parsemanifest('wat')
341 341 self.fail('Should have raised ValueError')
342 342 except ValueError, v:
343 343 self.assertIn('Manifest did not end in a newline.', str(v))
344 344
345 345 def testHugeManifest(self):
346 346 m = self.parsemanifest(A_HUGE_MANIFEST)
347 347 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
348 348 self.assertEqual(len(m), len(list(m)))
349 349
350 350 def testMatchesMetadata(self):
351 351 '''Tests matches() for a few specific files to make sure that both
352 352 the set of files as well as their flags and nodeids are correct in
353 353 the resulting manifest.'''
354 354 m = self.parsemanifest(A_HUGE_MANIFEST)
355 355
356 356 match = matchmod.match('/', '',
357 357 ['file1', 'file200', 'file300'], exact=True)
358 358 m2 = m.matches(match)
359 359
360 360 w = ('file1\0%sx\n'
361 361 'file200\0%sl\n'
362 362 'file300\0%s\n') % (HASH_2, HASH_1, HASH_1)
363 363 self.assertEqual(w, m2.text())
364 364
365 365 def testMatchesNonexistentFile(self):
366 366 '''Tests matches() for a small set of specific files, including one
367 367 nonexistent file to make sure in only matches against existing files.
368 368 '''
369 369 m = self.parsemanifest(A_DEEPER_MANIFEST)
370 370
371 371 match = matchmod.match('/', '',
372 372 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt', 'nonexistent'],
373 373 exact=True)
374 374 m2 = m.matches(match)
375 375
376 376 self.assertEqual(
377 377 ['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt'],
378 378 m2.keys())
379 379
380 380 def testMatchesNonexistentDirectory(self):
381 381 '''Tests matches() for a relpath match on a directory that doesn't
382 382 actually exist.'''
383 383 m = self.parsemanifest(A_DEEPER_MANIFEST)
384 384
385 385 match = matchmod.match('/', '', ['a/f'], default='relpath')
386 386 m2 = m.matches(match)
387 387
388 388 self.assertEqual([], m2.keys())
389 389
390 390 def testMatchesExactLarge(self):
391 391 '''Tests matches() for files matching a large list of exact files.
392 392 '''
393 393 m = self.parsemanifest(A_HUGE_MANIFEST)
394 394
395 395 flist = m.keys()[80:300]
396 396 match = matchmod.match('/', '', flist, exact=True)
397 397 m2 = m.matches(match)
398 398
399 399 self.assertEqual(flist, m2.keys())
400 400
401 401 def testMatchesFull(self):
402 402 '''Tests matches() for what should be a full match.'''
403 403 m = self.parsemanifest(A_DEEPER_MANIFEST)
404 404
405 405 match = matchmod.match('/', '', [''])
406 406 m2 = m.matches(match)
407 407
408 408 self.assertEqual(m.keys(), m2.keys())
409 409
410 410 def testMatchesDirectory(self):
411 411 '''Tests matches() on a relpath match on a directory, which should
412 412 match against all files within said directory.'''
413 413 m = self.parsemanifest(A_DEEPER_MANIFEST)
414 414
415 415 match = matchmod.match('/', '', ['a/b'], default='relpath')
416 416 m2 = m.matches(match)
417 417
418 418 self.assertEqual([
419 419 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
420 420 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
421 421 'a/b/fish.py'], m2.keys())
422 422
423 423 def testMatchesExactPath(self):
424 424 '''Tests matches() on an exact match on a directory, which should
425 425 result in an empty manifest because you can't perform an exact match
426 426 against a directory.'''
427 427 m = self.parsemanifest(A_DEEPER_MANIFEST)
428 428
429 429 match = matchmod.match('/', '', ['a/b'], exact=True)
430 430 m2 = m.matches(match)
431 431
432 432 self.assertEqual([], m2.keys())
433 433
434 434 def testMatchesCwd(self):
435 435 '''Tests matches() on a relpath match with the current directory ('.')
436 436 when not in the root directory.'''
437 437 m = self.parsemanifest(A_DEEPER_MANIFEST)
438 438
439 439 match = matchmod.match('/', 'a/b', ['.'], default='relpath')
440 440 m2 = m.matches(match)
441 441
442 442 self.assertEqual([
443 443 'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt',
444 444 'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py',
445 445 'a/b/fish.py'], m2.keys())
446 446
447 447 def testMatchesWithPattern(self):
448 448 '''Tests matches() for files matching a pattern that reside
449 449 deeper than the specified directory.'''
450 450 m = self.parsemanifest(A_DEEPER_MANIFEST)
451 451
452 452 match = matchmod.match('/', '', ['a/b/*/*.txt'])
453 453 m2 = m.matches(match)
454 454
455 455 self.assertEqual(
456 456 ['a/b/c/bar.txt', 'a/b/c/foo.txt', 'a/b/d/ten.txt'],
457 457 m2.keys())
458 458
459 459 class testmanifestdict(unittest.TestCase, basemanifesttests):
460 460 def parsemanifest(self, text):
461 461 return manifestmod.manifestdict(text)
462 462
463 class testtreemanifest(unittest.TestCase, basemanifesttests):
464 def parsemanifest(self, text):
465 return manifestmod.treemanifest('', text)
466
463 467 if __name__ == '__main__':
464 468 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now