test-manifest.py
460 lines
| 15.2 KiB
| text/x-python
|
PythonLexer
/ tests / test-manifest.py
|
r28929 | from __future__ import absolute_import | ||
|
r24214 | |||
|
r28929 | import binascii | ||
import itertools | ||||
|
r24214 | import silenttestrunner | ||
|
r28929 | import unittest | ||
|
r40635 | import zlib | ||
|
r24214 | |||
|
r47538 | from mercurial.node import sha1nodeconstants | ||
|
r28929 | from mercurial import ( | ||
manifest as manifestmod, | ||||
match as matchmod, | ||||
|
r44416 | util, | ||
|
r28929 | ) | ||
|
r24214 | |||
|
r32551 | EMTPY_MANIFEST = b'' | ||
|
r24569 | |||
|
r32551 | HASH_1 = b'1' * 40 | ||
|
r24570 | BIN_HASH_1 = binascii.unhexlify(HASH_1) | ||
|
r32551 | HASH_2 = b'f' * 40 | ||
|
r24570 | BIN_HASH_2 = binascii.unhexlify(HASH_2) | ||
|
r32551 | HASH_3 = b'1234567890abcdef0987654321deadbeef0fcafe' | ||
|
r24570 | BIN_HASH_3 = binascii.unhexlify(HASH_3) | ||
|
r24214 | A_SHORT_MANIFEST = ( | ||
|
r43346 | b'bar/baz/qux.py\0%(hash2)s%(flag2)s\n' b'foo\0%(hash1)s%(flag1)s\n' | ||
|
r46554 | ) % { | ||
b'hash1': HASH_1, | ||||
b'flag1': b'', | ||||
b'hash2': HASH_2, | ||||
b'flag2': b'l', | ||||
} | ||||
|
r24214 | |||
|
r24549 | A_DEEPER_MANIFEST = ( | ||
|
r32551 | b'a/b/c/bar.py\0%(hash3)s%(flag1)s\n' | ||
b'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n' | ||||
b'a/b/c/foo.py\0%(hash3)s%(flag1)s\n' | ||||
b'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n' | ||||
b'a/b/d/baz.py\0%(hash3)s%(flag1)s\n' | ||||
b'a/b/d/qux.py\0%(hash1)s%(flag2)s\n' | ||||
b'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n' | ||||
b'a/b/dog.py\0%(hash3)s%(flag1)s\n' | ||||
b'a/b/fish.py\0%(hash2)s%(flag1)s\n' | ||||
b'a/c/london.py\0%(hash3)s%(flag2)s\n' | ||||
b'a/c/paper.txt\0%(hash2)s%(flag2)s\n' | ||||
b'a/c/paris.py\0%(hash2)s%(flag1)s\n' | ||||
b'a/d/apple.py\0%(hash3)s%(flag1)s\n' | ||||
b'a/d/pizza.py\0%(hash3)s%(flag2)s\n' | ||||
b'a/green.py\0%(hash1)s%(flag2)s\n' | ||||
b'a/purple.py\0%(hash2)s%(flag1)s\n' | ||||
b'app.py\0%(hash3)s%(flag1)s\n' | ||||
b'readme.txt\0%(hash2)s%(flag1)s\n' | ||||
|
r43346 | ) % { | ||
b'hash1': HASH_1, | ||||
b'flag1': b'', | ||||
b'hash2': HASH_2, | ||||
b'flag2': b'l', | ||||
b'hash3': HASH_3, | ||||
} | ||||
|
r24549 | |||
|
r24214 | HUGE_MANIFEST_ENTRIES = 200001 | ||
|
r32534 | izip = getattr(itertools, 'izip', zip) | ||
if 'xrange' not in globals(): | ||||
xrange = range | ||||
|
r43346 | A_HUGE_MANIFEST = b''.join( | ||
sorted( | ||||
b'file%d\0%s%s\n' % (i, h, f) | ||||
for i, h, f in izip( | ||||
xrange(200001), | ||||
itertools.cycle((HASH_1, HASH_2)), | ||||
itertools.cycle((b'', b'x', b'l')), | ||||
) | ||||
) | ||||
) | ||||
|
r24214 | |||
|
r24655 | class basemanifesttests(object): | ||
|
r24654 | def parsemanifest(self, text): | ||
|
r24655 | raise NotImplementedError('parsemanifest not implemented by test case') | ||
|
r24214 | |||
def testEmptyManifest(self): | ||||
|
r24654 | m = self.parsemanifest(EMTPY_MANIFEST) | ||
|
r24214 | self.assertEqual(0, len(m)) | ||
|
r24466 | self.assertEqual([], list(m)) | ||
|
r24214 | |||
def testManifest(self): | ||||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r32551 | self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m)) | ||
self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py']) | ||||
self.assertEqual(b'l', m.flags(b'bar/baz/qux.py')) | ||||
self.assertEqual(BIN_HASH_1, m[b'foo']) | ||||
self.assertEqual(b'', m.flags(b'foo')) | ||||
|
r32279 | with self.assertRaises(KeyError): | ||
|
r32551 | m[b'wat'] | ||
|
r24214 | |||
|
r45201 | def testManifestLongHashes(self): | ||
m = self.parsemanifest(b'a\0' + b'f' * 64 + b'\n') | ||||
self.assertEqual(binascii.unhexlify(b'f' * 64), m[b'a']) | ||||
|
r24214 | def testSetItem(self): | ||
|
r24570 | want = BIN_HASH_1 | ||
|
r24214 | |||
|
r24654 | m = self.parsemanifest(EMTPY_MANIFEST) | ||
|
r32551 | m[b'a'] = want | ||
self.assertIn(b'a', m) | ||||
self.assertEqual(want, m[b'a']) | ||||
self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text()) | ||||
|
r24214 | |||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r32551 | m[b'a'] = want | ||
self.assertEqual(want, m[b'a']) | ||||
|
r43346 | self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST, m.text()) | ||
|
r24465 | |||
|
r24466 | def testSetFlag(self): | ||
|
r32551 | want = b'x' | ||
|
r24466 | |||
|
r24654 | m = self.parsemanifest(EMTPY_MANIFEST) | ||
|
r24466 | # first add a file; a file-less flag makes no sense | ||
|
r32551 | m[b'a'] = BIN_HASH_1 | ||
m.setflag(b'a', want) | ||||
self.assertEqual(want, m.flags(b'a')) | ||||
self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text()) | ||||
|
r24466 | |||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24466 | # first add a file; a file-less flag makes no sense | ||
|
r32551 | m[b'a'] = BIN_HASH_1 | ||
m.setflag(b'a', want) | ||||
self.assertEqual(want, m.flags(b'a')) | ||||
|
r43346 | self.assertEqual( | ||
b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST, m.text() | ||||
) | ||||
|
r24466 | |||
|
r24465 | def testCopy(self): | ||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r32551 | m[b'a'] = BIN_HASH_1 | ||
|
r24214 | m2 = m.copy() | ||
del m | ||||
|
r43346 | del m2 # make sure we don't double free() anything | ||
|
r24214 | |||
def testCompaction(self): | ||||
unhex = binascii.unhexlify | ||||
h1, h2 = unhex(HASH_1), unhex(HASH_2) | ||||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r32551 | m[b'alpha'] = h1 | ||
m[b'beta'] = h2 | ||||
del m[b'foo'] | ||||
want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % ( | ||||
|
r43346 | HASH_1, | ||
HASH_2, | ||||
HASH_2, | ||||
) | ||||
|
r24214 | self.assertEqual(want, m.text()) | ||
self.assertEqual(3, len(m)) | ||||
|
r32551 | self.assertEqual([b'alpha', b'bar/baz/qux.py', b'beta'], list(m)) | ||
self.assertEqual(h1, m[b'alpha']) | ||||
self.assertEqual(h2, m[b'bar/baz/qux.py']) | ||||
self.assertEqual(h2, m[b'beta']) | ||||
self.assertEqual(b'', m.flags(b'alpha')) | ||||
self.assertEqual(b'l', m.flags(b'bar/baz/qux.py')) | ||||
self.assertEqual(b'', m.flags(b'beta')) | ||||
|
r32279 | with self.assertRaises(KeyError): | ||
|
r32551 | m[b'foo'] | ||
|
r24214 | |||
|
r24466 | def testMatchException(self): | ||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r44416 | match = matchmod.match(util.localpath(b'/repo'), b'', [b're:.*']) | ||
|
r43346 | |||
|
r24214 | def filt(path): | ||
|
r32551 | if path == b'foo': | ||
|
r24214 | assert False | ||
return True | ||||
|
r43346 | |||
|
r24466 | match.matchfn = filt | ||
|
r32279 | with self.assertRaises(AssertionError): | ||
|
r44826 | m._matches(match) | ||
|
r24214 | |||
def testRemoveItem(self): | ||||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r32551 | del m[b'foo'] | ||
|
r32279 | with self.assertRaises(KeyError): | ||
|
r32551 | m[b'foo'] | ||
|
r24214 | self.assertEqual(1, len(m)) | ||
self.assertEqual(1, len(list(m))) | ||||
|
r24228 | # now restore and make sure everything works right | ||
|
r32551 | m[b'foo'] = b'a' * 20 | ||
|
r24228 | self.assertEqual(2, len(m)) | ||
self.assertEqual(2, len(list(m))) | ||||
|
r24214 | |||
def testManifestDiff(self): | ||||
|
r32551 | MISSING = (None, b'') | ||
addl = b'z-only-in-left\0' + HASH_1 + b'\n' | ||||
addr = b'z-only-in-right\0' + HASH_2 + b'x\n' | ||||
|
r24654 | left = self.parsemanifest( | ||
|
r43346 | A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl | ||
) | ||||
|
r24654 | right = self.parsemanifest(A_SHORT_MANIFEST + addr) | ||
|
r24214 | want = { | ||
|
r43346 | b'foo': ((BIN_HASH_3, b'x'), (BIN_HASH_1, b'')), | ||
|
r32551 | b'z-only-in-left': ((BIN_HASH_1, b''), MISSING), | ||
b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')), | ||||
|
r43346 | } | ||
|
r24214 | self.assertEqual(want, left.diff(right)) | ||
want = { | ||||
|
r32551 | b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')), | ||
b'foo': (MISSING, (BIN_HASH_3, b'x')), | ||||
b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')), | ||||
|
r43346 | } | ||
|
r24654 | self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left)) | ||
|
r24214 | |||
want = { | ||||
|
r32551 | b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING), | ||
b'foo': ((BIN_HASH_3, b'x'), MISSING), | ||||
b'z-only-in-left': ((BIN_HASH_1, b''), MISSING), | ||||
|
r43346 | } | ||
|
r24654 | self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST))) | ||
|
r24214 | copy = right.copy() | ||
|
r32551 | del copy[b'z-only-in-right'] | ||
del right[b'foo'] | ||||
|
r24214 | want = { | ||
|
r32551 | b'foo': (MISSING, (BIN_HASH_1, b'')), | ||
b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING), | ||||
|
r43346 | } | ||
|
r24214 | self.assertEqual(want, right.diff(copy)) | ||
|
r24654 | short = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24214 | pruned = short.copy() | ||
|
r32551 | del pruned[b'foo'] | ||
|
r24214 | want = { | ||
|
r32551 | b'foo': ((BIN_HASH_1, b''), MISSING), | ||
|
r43346 | } | ||
|
r24214 | self.assertEqual(want, short.diff(pruned)) | ||
want = { | ||||
|
r32551 | b'foo': (MISSING, (BIN_HASH_1, b'')), | ||
|
r43346 | } | ||
|
r24214 | self.assertEqual(want, pruned.diff(short)) | ||
want = { | ||||
|
r32551 | b'bar/baz/qux.py': None, | ||
b'foo': (MISSING, (BIN_HASH_1, b'')), | ||||
|
r43346 | } | ||
|
r31255 | self.assertEqual(want, pruned.diff(short, clean=True)) | ||
|
r24214 | |||
def testReversedLines(self): | ||||
|
r32551 | backwards = b''.join( | ||
|
r43346 | l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l | ||
) | ||||
|
r24214 | try: | ||
|
r24654 | self.parsemanifest(backwards) | ||
|
r24214 | self.fail('Should have raised ValueError') | ||
|
r25660 | except ValueError as v: | ||
|
r24214 | self.assertIn('Manifest lines not in sorted order.', str(v)) | ||
def testNoTerminalNewline(self): | ||||
try: | ||||
|
r32551 | self.parsemanifest(A_SHORT_MANIFEST + b'wat') | ||
|
r24214 | self.fail('Should have raised ValueError') | ||
|
r25660 | except ValueError as v: | ||
|
r24214 | self.assertIn('Manifest did not end in a newline.', str(v)) | ||
def testNoNewLineAtAll(self): | ||||
try: | ||||
|
r32551 | self.parsemanifest(b'wat') | ||
|
r24214 | self.fail('Should have raised ValueError') | ||
|
r25660 | except ValueError as v: | ||
|
r24214 | self.assertIn('Manifest did not end in a newline.', str(v)) | ||
def testHugeManifest(self): | ||||
|
r24654 | m = self.parsemanifest(A_HUGE_MANIFEST) | ||
|
r24214 | self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m)) | ||
self.assertEqual(len(m), len(list(m))) | ||||
|
r24549 | def testMatchesMetadata(self): | ||
|
r46554 | """Tests matches() for a few specific files to make sure that both | ||
|
r24549 | the set of files as well as their flags and nodeids are correct in | ||
|
r46554 | the resulting manifest.""" | ||
|
r24654 | m = self.parsemanifest(A_HUGE_MANIFEST) | ||
|
r24495 | |||
|
r41825 | match = matchmod.exact([b'file1', b'file200', b'file300']) | ||
|
r44826 | m2 = m._matches(match) | ||
|
r24495 | |||
|
r43346 | w = (b'file1\0%sx\n' b'file200\0%sl\n' b'file300\0%s\n') % ( | ||
HASH_2, | ||||
HASH_1, | ||||
HASH_1, | ||||
) | ||||
|
r24225 | self.assertEqual(w, m2.text()) | ||
|
r24214 | |||
|
r24549 | def testMatchesNonexistentFile(self): | ||
|
r46554 | """Tests matches() for a small set of specific files, including one | ||
|
r24549 | nonexistent file to make sure in only matches against existing files. | ||
|
r46554 | """ | ||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
|
r43346 | match = matchmod.exact( | ||
[b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt', b'nonexistent'] | ||||
) | ||||
|
r44826 | m2 = m._matches(match) | ||
|
r24549 | |||
self.assertEqual( | ||||
|
r43346 | [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt'], m2.keys() | ||
) | ||||
|
r24549 | |||
def testMatchesNonexistentDirectory(self): | ||||
|
r46554 | """Tests matches() for a relpath match on a directory that doesn't | ||
actually exist.""" | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
|
r44416 | match = matchmod.match( | ||
util.localpath(b'/repo'), b'', [b'a/f'], default=b'relpath' | ||||
) | ||||
|
r44826 | m2 = m._matches(match) | ||
|
r24549 | |||
self.assertEqual([], m2.keys()) | ||||
def testMatchesExactLarge(self): | ||||
|
r46554 | """Tests matches() for files matching a large list of exact files.""" | ||
|
r24654 | m = self.parsemanifest(A_HUGE_MANIFEST) | ||
|
r24549 | |||
flist = m.keys()[80:300] | ||||
|
r41825 | match = matchmod.exact(flist) | ||
|
r44826 | m2 = m._matches(match) | ||
|
r24549 | |||
self.assertEqual(flist, m2.keys()) | ||||
def testMatchesFull(self): | ||||
'''Tests matches() for what should be a full match.''' | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
|
r44416 | match = matchmod.match(util.localpath(b'/repo'), b'', [b'']) | ||
|
r44826 | m2 = m._matches(match) | ||
|
r24549 | |||
self.assertEqual(m.keys(), m2.keys()) | ||||
def testMatchesDirectory(self): | ||||
|
r46554 | """Tests matches() on a relpath match on a directory, which should | ||
match against all files within said directory.""" | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
|
r44416 | match = matchmod.match( | ||
util.localpath(b'/repo'), b'', [b'a/b'], default=b'relpath' | ||||
) | ||||
|
r44826 | m2 = m._matches(match) | ||
|
r24549 | |||
|
r43346 | self.assertEqual( | ||
[ | ||||
b'a/b/c/bar.py', | ||||
b'a/b/c/bar.txt', | ||||
b'a/b/c/foo.py', | ||||
b'a/b/c/foo.txt', | ||||
b'a/b/d/baz.py', | ||||
b'a/b/d/qux.py', | ||||
b'a/b/d/ten.txt', | ||||
b'a/b/dog.py', | ||||
b'a/b/fish.py', | ||||
], | ||||
m2.keys(), | ||||
) | ||||
|
r24549 | |||
def testMatchesExactPath(self): | ||||
|
r46554 | """Tests matches() on an exact match on a directory, which should | ||
|
r24549 | result in an empty manifest because you can't perform an exact match | ||
|
r46554 | against a directory.""" | ||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
|
r41825 | match = matchmod.exact([b'a/b']) | ||
|
r44826 | m2 = m._matches(match) | ||
|
r24549 | |||
self.assertEqual([], m2.keys()) | ||||
def testMatchesCwd(self): | ||||
|
r46554 | """Tests matches() on a relpath match with the current directory ('.') | ||
when not in the root directory.""" | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
|
r44416 | match = matchmod.match( | ||
util.localpath(b'/repo'), b'a/b', [b'.'], default=b'relpath' | ||||
) | ||||
|
r44826 | m2 = m._matches(match) | ||
|
r24549 | |||
|
r43346 | self.assertEqual( | ||
[ | ||||
b'a/b/c/bar.py', | ||||
b'a/b/c/bar.txt', | ||||
b'a/b/c/foo.py', | ||||
b'a/b/c/foo.txt', | ||||
b'a/b/d/baz.py', | ||||
b'a/b/d/qux.py', | ||||
b'a/b/d/ten.txt', | ||||
b'a/b/dog.py', | ||||
b'a/b/fish.py', | ||||
], | ||||
m2.keys(), | ||||
) | ||||
|
r24549 | |||
def testMatchesWithPattern(self): | ||||
|
r46554 | """Tests matches() for files matching a pattern that reside | ||
deeper than the specified directory.""" | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
|
r44416 | match = matchmod.match(util.localpath(b'/repo'), b'', [b'a/b/*/*.txt']) | ||
|
r44826 | m2 = m._matches(match) | ||
|
r24549 | |||
self.assertEqual( | ||||
|
r43346 | [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'], m2.keys() | ||
) | ||||
|
r24549 | |||
|
r24655 | class testmanifestdict(unittest.TestCase, basemanifesttests): | ||
def parsemanifest(self, text): | ||||
return manifestmod.manifestdict(text) | ||||
|
r40635 | def testObviouslyBogusManifest(self): | ||
# This is a 163k manifest that came from oss-fuzz. It was a | ||||
# timeout there, but when run normally it doesn't seem to | ||||
# present any particular slowness. | ||||
data = zlib.decompress( | ||||
|
r40663 | b'x\x9c\xed\xce;\n\x83\x00\x10\x04\xd0\x8deNa\x93~\xf1\x03\xc9q\xf4' | ||
b'\x14\xeaU\xbdB\xda\xd4\xe6Cj\xc1FA\xde+\x86\xe9f\xa2\xfci\xbb\xfb' | ||||
b'\xa3\xef\xea\xba\xca\x7fk\x86q\x9a\xc6\xc8\xcc&\xb3\xcf\xf8\xb8|#' | ||||
b'\x8a9\x00\xd8\xe6v\xf4\x01N\xe1\n\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' | ||||
b'\x00\x00\xc0\x8aey\x1d}\x01\xd8\xe0\xb9\xf3\xde\x1b\xcf\x17' | ||||
|
r43346 | b'\xac\xbe' | ||
) | ||||
|
r40635 | with self.assertRaises(ValueError): | ||
self.parsemanifest(data) | ||||
|
r43346 | |||
|
r24656 | class testtreemanifest(unittest.TestCase, basemanifesttests): | ||
def parsemanifest(self, text): | ||||
|
r47538 | return manifestmod.treemanifest(sha1nodeconstants, b'', text) | ||
|
r24656 | |||
|
r31876 | def testWalkSubtrees(self): | ||
m = self.parsemanifest(A_DEEPER_MANIFEST) | ||||
dirs = [s._dir for s in m.walksubtrees()] | ||||
self.assertEqual( | ||||
|
r43346 | sorted( | ||
[b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/'] | ||||
), | ||||
sorted(dirs), | ||||
|
r31876 | ) | ||
|
r44416 | match = matchmod.match(util.localpath(b'/repo'), b'', [b'path:a/b/']) | ||
|
r31876 | dirs = [s._dir for s in m.walksubtrees(matcher=match)] | ||
|
r43346 | self.assertEqual(sorted([b'a/b/', b'a/b/c/', b'a/b/d/']), sorted(dirs)) | ||
|
r31876 | |||
|
r24214 | if __name__ == '__main__': | ||
silenttestrunner.main(__name__) | ||||