79 class basemanifesttests(object): |
79 class basemanifesttests(object): |
80 def parsemanifest(self, text): |
80 def parsemanifest(self, text): |
81 raise NotImplementedError('parsemanifest not implemented by test case') |
81 raise NotImplementedError('parsemanifest not implemented by test case') |
82 |
82 |
83 def testEmptyManifest(self): |
83 def testEmptyManifest(self): |
84 m = self.parsemanifest(EMTPY_MANIFEST) |
84 m = self.parsemanifest(20, EMTPY_MANIFEST) |
85 self.assertEqual(0, len(m)) |
85 self.assertEqual(0, len(m)) |
86 self.assertEqual([], list(m)) |
86 self.assertEqual([], list(m)) |
87 |
87 |
88 def testManifest(self): |
88 def testManifest(self): |
89 m = self.parsemanifest(A_SHORT_MANIFEST) |
89 m = self.parsemanifest(20, A_SHORT_MANIFEST) |
90 self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m)) |
90 self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m)) |
91 self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py']) |
91 self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py']) |
92 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py')) |
92 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py')) |
93 self.assertEqual(BIN_HASH_1, m[b'foo']) |
93 self.assertEqual(BIN_HASH_1, m[b'foo']) |
94 self.assertEqual(b'', m.flags(b'foo')) |
94 self.assertEqual(b'', m.flags(b'foo')) |
95 with self.assertRaises(KeyError): |
95 with self.assertRaises(KeyError): |
96 m[b'wat'] |
96 m[b'wat'] |
97 |
97 |
98 def testManifestLongHashes(self): |
|
99 m = self.parsemanifest(b'a\0' + b'f' * 64 + b'\n') |
|
100 self.assertEqual(binascii.unhexlify(b'f' * 64), m[b'a']) |
|
101 |
|
102 def testSetItem(self): |
98 def testSetItem(self): |
103 want = BIN_HASH_1 |
99 want = BIN_HASH_1 |
104 |
100 |
105 m = self.parsemanifest(EMTPY_MANIFEST) |
101 m = self.parsemanifest(20, EMTPY_MANIFEST) |
106 m[b'a'] = want |
102 m[b'a'] = want |
107 self.assertIn(b'a', m) |
103 self.assertIn(b'a', m) |
108 self.assertEqual(want, m[b'a']) |
104 self.assertEqual(want, m[b'a']) |
109 self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text()) |
105 self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text()) |
110 |
106 |
111 m = self.parsemanifest(A_SHORT_MANIFEST) |
107 m = self.parsemanifest(20, A_SHORT_MANIFEST) |
112 m[b'a'] = want |
108 m[b'a'] = want |
113 self.assertEqual(want, m[b'a']) |
109 self.assertEqual(want, m[b'a']) |
114 self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST, m.text()) |
110 self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST, m.text()) |
115 |
111 |
116 def testSetFlag(self): |
112 def testSetFlag(self): |
117 want = b'x' |
113 want = b'x' |
118 |
114 |
119 m = self.parsemanifest(EMTPY_MANIFEST) |
115 m = self.parsemanifest(20, EMTPY_MANIFEST) |
120 # first add a file; a file-less flag makes no sense |
116 # first add a file; a file-less flag makes no sense |
121 m[b'a'] = BIN_HASH_1 |
117 m[b'a'] = BIN_HASH_1 |
122 m.setflag(b'a', want) |
118 m.setflag(b'a', want) |
123 self.assertEqual(want, m.flags(b'a')) |
119 self.assertEqual(want, m.flags(b'a')) |
124 self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text()) |
120 self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text()) |
125 |
121 |
126 m = self.parsemanifest(A_SHORT_MANIFEST) |
122 m = self.parsemanifest(20, A_SHORT_MANIFEST) |
127 # first add a file; a file-less flag makes no sense |
123 # first add a file; a file-less flag makes no sense |
128 m[b'a'] = BIN_HASH_1 |
124 m[b'a'] = BIN_HASH_1 |
129 m.setflag(b'a', want) |
125 m.setflag(b'a', want) |
130 self.assertEqual(want, m.flags(b'a')) |
126 self.assertEqual(want, m.flags(b'a')) |
131 self.assertEqual( |
127 self.assertEqual( |
132 b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST, m.text() |
128 b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST, m.text() |
133 ) |
129 ) |
134 |
130 |
135 def testCopy(self): |
131 def testCopy(self): |
136 m = self.parsemanifest(A_SHORT_MANIFEST) |
132 m = self.parsemanifest(20, A_SHORT_MANIFEST) |
137 m[b'a'] = BIN_HASH_1 |
133 m[b'a'] = BIN_HASH_1 |
138 m2 = m.copy() |
134 m2 = m.copy() |
139 del m |
135 del m |
140 del m2 # make sure we don't double free() anything |
136 del m2 # make sure we don't double free() anything |
141 |
137 |
142 def testCompaction(self): |
138 def testCompaction(self): |
143 unhex = binascii.unhexlify |
139 unhex = binascii.unhexlify |
144 h1, h2 = unhex(HASH_1), unhex(HASH_2) |
140 h1, h2 = unhex(HASH_1), unhex(HASH_2) |
145 m = self.parsemanifest(A_SHORT_MANIFEST) |
141 m = self.parsemanifest(20, A_SHORT_MANIFEST) |
146 m[b'alpha'] = h1 |
142 m[b'alpha'] = h1 |
147 m[b'beta'] = h2 |
143 m[b'beta'] = h2 |
148 del m[b'foo'] |
144 del m[b'foo'] |
149 want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % ( |
145 want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % ( |
150 HASH_1, |
146 HASH_1, |
191 def testManifestDiff(self): |
187 def testManifestDiff(self): |
192 MISSING = (None, b'') |
188 MISSING = (None, b'') |
193 addl = b'z-only-in-left\0' + HASH_1 + b'\n' |
189 addl = b'z-only-in-left\0' + HASH_1 + b'\n' |
194 addr = b'z-only-in-right\0' + HASH_2 + b'x\n' |
190 addr = b'z-only-in-right\0' + HASH_2 + b'x\n' |
195 left = self.parsemanifest( |
191 left = self.parsemanifest( |
196 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl |
192 20, A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl |
197 ) |
193 ) |
198 right = self.parsemanifest(A_SHORT_MANIFEST + addr) |
194 right = self.parsemanifest(20, A_SHORT_MANIFEST + addr) |
199 want = { |
195 want = { |
200 b'foo': ((BIN_HASH_3, b'x'), (BIN_HASH_1, b'')), |
196 b'foo': ((BIN_HASH_3, b'x'), (BIN_HASH_1, b'')), |
201 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING), |
197 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING), |
202 b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')), |
198 b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')), |
203 } |
199 } |
206 want = { |
202 want = { |
207 b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')), |
203 b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')), |
208 b'foo': (MISSING, (BIN_HASH_3, b'x')), |
204 b'foo': (MISSING, (BIN_HASH_3, b'x')), |
209 b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')), |
205 b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')), |
210 } |
206 } |
211 self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left)) |
207 self.assertEqual( |
|
208 want, self.parsemanifest(20, EMTPY_MANIFEST).diff(left) |
|
209 ) |
212 |
210 |
213 want = { |
211 want = { |
214 b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING), |
212 b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING), |
215 b'foo': ((BIN_HASH_3, b'x'), MISSING), |
213 b'foo': ((BIN_HASH_3, b'x'), MISSING), |
216 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING), |
214 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING), |
217 } |
215 } |
218 self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST))) |
216 self.assertEqual( |
|
217 want, left.diff(self.parsemanifest(20, EMTPY_MANIFEST)) |
|
218 ) |
219 copy = right.copy() |
219 copy = right.copy() |
220 del copy[b'z-only-in-right'] |
220 del copy[b'z-only-in-right'] |
221 del right[b'foo'] |
221 del right[b'foo'] |
222 want = { |
222 want = { |
223 b'foo': (MISSING, (BIN_HASH_1, b'')), |
223 b'foo': (MISSING, (BIN_HASH_1, b'')), |
224 b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING), |
224 b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING), |
225 } |
225 } |
226 self.assertEqual(want, right.diff(copy)) |
226 self.assertEqual(want, right.diff(copy)) |
227 |
227 |
228 short = self.parsemanifest(A_SHORT_MANIFEST) |
228 short = self.parsemanifest(20, A_SHORT_MANIFEST) |
229 pruned = short.copy() |
229 pruned = short.copy() |
230 del pruned[b'foo'] |
230 del pruned[b'foo'] |
231 want = { |
231 want = { |
232 b'foo': ((BIN_HASH_1, b''), MISSING), |
232 b'foo': ((BIN_HASH_1, b''), MISSING), |
233 } |
233 } |
245 def testReversedLines(self): |
245 def testReversedLines(self): |
246 backwards = b''.join( |
246 backwards = b''.join( |
247 l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l |
247 l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l |
248 ) |
248 ) |
249 try: |
249 try: |
250 self.parsemanifest(backwards) |
250 self.parsemanifest(20, backwards) |
251 self.fail('Should have raised ValueError') |
251 self.fail('Should have raised ValueError') |
252 except ValueError as v: |
252 except ValueError as v: |
253 self.assertIn('Manifest lines not in sorted order.', str(v)) |
253 self.assertIn('Manifest lines not in sorted order.', str(v)) |
254 |
254 |
255 def testNoTerminalNewline(self): |
255 def testNoTerminalNewline(self): |
256 try: |
256 try: |
257 self.parsemanifest(A_SHORT_MANIFEST + b'wat') |
257 self.parsemanifest(20, A_SHORT_MANIFEST + b'wat') |
258 self.fail('Should have raised ValueError') |
258 self.fail('Should have raised ValueError') |
259 except ValueError as v: |
259 except ValueError as v: |
260 self.assertIn('Manifest did not end in a newline.', str(v)) |
260 self.assertIn('Manifest did not end in a newline.', str(v)) |
261 |
261 |
262 def testNoNewLineAtAll(self): |
262 def testNoNewLineAtAll(self): |
263 try: |
263 try: |
264 self.parsemanifest(b'wat') |
264 self.parsemanifest(20, b'wat') |
265 self.fail('Should have raised ValueError') |
265 self.fail('Should have raised ValueError') |
266 except ValueError as v: |
266 except ValueError as v: |
267 self.assertIn('Manifest did not end in a newline.', str(v)) |
267 self.assertIn('Manifest did not end in a newline.', str(v)) |
268 |
268 |
269 def testHugeManifest(self): |
269 def testHugeManifest(self): |
270 m = self.parsemanifest(A_HUGE_MANIFEST) |
270 m = self.parsemanifest(20, A_HUGE_MANIFEST) |
271 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m)) |
271 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m)) |
272 self.assertEqual(len(m), len(list(m))) |
272 self.assertEqual(len(m), len(list(m))) |
273 |
273 |
274 def testMatchesMetadata(self): |
274 def testMatchesMetadata(self): |
275 """Tests matches() for a few specific files to make sure that both |
275 """Tests matches() for a few specific files to make sure that both |
276 the set of files as well as their flags and nodeids are correct in |
276 the set of files as well as their flags and nodeids are correct in |
277 the resulting manifest.""" |
277 the resulting manifest.""" |
278 m = self.parsemanifest(A_HUGE_MANIFEST) |
278 m = self.parsemanifest(20, A_HUGE_MANIFEST) |
279 |
279 |
280 match = matchmod.exact([b'file1', b'file200', b'file300']) |
280 match = matchmod.exact([b'file1', b'file200', b'file300']) |
281 m2 = m._matches(match) |
281 m2 = m._matches(match) |
282 |
282 |
283 w = (b'file1\0%sx\n' b'file200\0%sl\n' b'file300\0%s\n') % ( |
283 w = (b'file1\0%sx\n' b'file200\0%sl\n' b'file300\0%s\n') % ( |
303 ) |
303 ) |
304 |
304 |
305 def testMatchesNonexistentDirectory(self): |
305 def testMatchesNonexistentDirectory(self): |
306 """Tests matches() for a relpath match on a directory that doesn't |
306 """Tests matches() for a relpath match on a directory that doesn't |
307 actually exist.""" |
307 actually exist.""" |
308 m = self.parsemanifest(A_DEEPER_MANIFEST) |
308 m = self.parsemanifest(20, A_DEEPER_MANIFEST) |
309 |
309 |
310 match = matchmod.match( |
310 match = matchmod.match( |
311 util.localpath(b'/repo'), b'', [b'a/f'], default=b'relpath' |
311 util.localpath(b'/repo'), b'', [b'a/f'], default=b'relpath' |
312 ) |
312 ) |
313 m2 = m._matches(match) |
313 m2 = m._matches(match) |
314 |
314 |
315 self.assertEqual([], m2.keys()) |
315 self.assertEqual([], m2.keys()) |
316 |
316 |
317 def testMatchesExactLarge(self): |
317 def testMatchesExactLarge(self): |
318 """Tests matches() for files matching a large list of exact files.""" |
318 """Tests matches() for files matching a large list of exact files.""" |
319 m = self.parsemanifest(A_HUGE_MANIFEST) |
319 m = self.parsemanifest(20, A_HUGE_MANIFEST) |
320 |
320 |
321 flist = m.keys()[80:300] |
321 flist = m.keys()[80:300] |
322 match = matchmod.exact(flist) |
322 match = matchmod.exact(flist) |
323 m2 = m._matches(match) |
323 m2 = m._matches(match) |
324 |
324 |
325 self.assertEqual(flist, m2.keys()) |
325 self.assertEqual(flist, m2.keys()) |
326 |
326 |
327 def testMatchesFull(self): |
327 def testMatchesFull(self): |
328 '''Tests matches() for what should be a full match.''' |
328 '''Tests matches() for what should be a full match.''' |
329 m = self.parsemanifest(A_DEEPER_MANIFEST) |
329 m = self.parsemanifest(20, A_DEEPER_MANIFEST) |
330 |
330 |
331 match = matchmod.match(util.localpath(b'/repo'), b'', [b'']) |
331 match = matchmod.match(util.localpath(b'/repo'), b'', [b'']) |
332 m2 = m._matches(match) |
332 m2 = m._matches(match) |
333 |
333 |
334 self.assertEqual(m.keys(), m2.keys()) |
334 self.assertEqual(m.keys(), m2.keys()) |
335 |
335 |
336 def testMatchesDirectory(self): |
336 def testMatchesDirectory(self): |
337 """Tests matches() on a relpath match on a directory, which should |
337 """Tests matches() on a relpath match on a directory, which should |
338 match against all files within said directory.""" |
338 match against all files within said directory.""" |
339 m = self.parsemanifest(A_DEEPER_MANIFEST) |
339 m = self.parsemanifest(20, A_DEEPER_MANIFEST) |
340 |
340 |
341 match = matchmod.match( |
341 match = matchmod.match( |
342 util.localpath(b'/repo'), b'', [b'a/b'], default=b'relpath' |
342 util.localpath(b'/repo'), b'', [b'a/b'], default=b'relpath' |
343 ) |
343 ) |
344 m2 = m._matches(match) |
344 m2 = m._matches(match) |
360 |
360 |
361 def testMatchesExactPath(self): |
361 def testMatchesExactPath(self): |
362 """Tests matches() on an exact match on a directory, which should |
362 """Tests matches() on an exact match on a directory, which should |
363 result in an empty manifest because you can't perform an exact match |
363 result in an empty manifest because you can't perform an exact match |
364 against a directory.""" |
364 against a directory.""" |
365 m = self.parsemanifest(A_DEEPER_MANIFEST) |
365 m = self.parsemanifest(20, A_DEEPER_MANIFEST) |
366 |
366 |
367 match = matchmod.exact([b'a/b']) |
367 match = matchmod.exact([b'a/b']) |
368 m2 = m._matches(match) |
368 m2 = m._matches(match) |
369 |
369 |
370 self.assertEqual([], m2.keys()) |
370 self.assertEqual([], m2.keys()) |
371 |
371 |
372 def testMatchesCwd(self): |
372 def testMatchesCwd(self): |
373 """Tests matches() on a relpath match with the current directory ('.') |
373 """Tests matches() on a relpath match with the current directory ('.') |
374 when not in the root directory.""" |
374 when not in the root directory.""" |
375 m = self.parsemanifest(A_DEEPER_MANIFEST) |
375 m = self.parsemanifest(20, A_DEEPER_MANIFEST) |
376 |
376 |
377 match = matchmod.match( |
377 match = matchmod.match( |
378 util.localpath(b'/repo'), b'a/b', [b'.'], default=b'relpath' |
378 util.localpath(b'/repo'), b'a/b', [b'.'], default=b'relpath' |
379 ) |
379 ) |
380 m2 = m._matches(match) |
380 m2 = m._matches(match) |
395 ) |
395 ) |
396 |
396 |
397 def testMatchesWithPattern(self): |
397 def testMatchesWithPattern(self): |
398 """Tests matches() for files matching a pattern that reside |
398 """Tests matches() for files matching a pattern that reside |
399 deeper than the specified directory.""" |
399 deeper than the specified directory.""" |
400 m = self.parsemanifest(A_DEEPER_MANIFEST) |
400 m = self.parsemanifest(20, A_DEEPER_MANIFEST) |
401 |
401 |
402 match = matchmod.match(util.localpath(b'/repo'), b'', [b'a/b/*/*.txt']) |
402 match = matchmod.match(util.localpath(b'/repo'), b'', [b'a/b/*/*.txt']) |
403 m2 = m._matches(match) |
403 m2 = m._matches(match) |
404 |
404 |
405 self.assertEqual( |
405 self.assertEqual( |
406 [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'], m2.keys() |
406 [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'], m2.keys() |
407 ) |
407 ) |
408 |
408 |
409 |
409 |
410 class testmanifestdict(unittest.TestCase, basemanifesttests): |
410 class testmanifestdict(unittest.TestCase, basemanifesttests): |
411 def parsemanifest(self, text): |
411 def parsemanifest(self, nodelen, text): |
412 return manifestmod.manifestdict(text) |
412 return manifestmod.manifestdict(nodelen, text) |
|
413 |
|
414 def testManifestLongHashes(self): |
|
415 m = self.parsemanifest(32, b'a\0' + b'f' * 64 + b'\n') |
|
416 self.assertEqual(binascii.unhexlify(b'f' * 64), m[b'a']) |
413 |
417 |
414 def testObviouslyBogusManifest(self): |
418 def testObviouslyBogusManifest(self): |
415 # This is a 163k manifest that came from oss-fuzz. It was a |
419 # This is a 163k manifest that came from oss-fuzz. It was a |
416 # timeout there, but when run normally it doesn't seem to |
420 # timeout there, but when run normally it doesn't seem to |
417 # present any particular slowness. |
421 # present any particular slowness. |
431 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
435 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' |
432 b'\x00\x00\xc0\x8aey\x1d}\x01\xd8\xe0\xb9\xf3\xde\x1b\xcf\x17' |
436 b'\x00\x00\xc0\x8aey\x1d}\x01\xd8\xe0\xb9\xf3\xde\x1b\xcf\x17' |
433 b'\xac\xbe' |
437 b'\xac\xbe' |
434 ) |
438 ) |
435 with self.assertRaises(ValueError): |
439 with self.assertRaises(ValueError): |
436 self.parsemanifest(data) |
440 self.parsemanifest(20, data) |
437 |
441 |
438 |
442 |
439 class testtreemanifest(unittest.TestCase, basemanifesttests): |
443 class testtreemanifest(unittest.TestCase, basemanifesttests): |
440 def parsemanifest(self, text): |
444 def parsemanifest(self, nodelen, text): |
441 return manifestmod.treemanifest(sha1nodeconstants, b'', text) |
445 return manifestmod.treemanifest(sha1nodeconstants, b'', text) |
442 |
446 |
443 def testWalkSubtrees(self): |
447 def testWalkSubtrees(self): |
444 m = self.parsemanifest(A_DEEPER_MANIFEST) |
448 m = self.parsemanifest(20, A_DEEPER_MANIFEST) |
445 |
449 |
446 dirs = [s._dir for s in m.walksubtrees()] |
450 dirs = [s._dir for s in m.walksubtrees()] |
447 self.assertEqual( |
451 self.assertEqual( |
448 sorted( |
452 sorted( |
449 [b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/'] |
453 [b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/'] |