test-manifest.py
471 lines
| 16.3 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 | ||
|
r24214 | |||
|
r28929 | from mercurial import ( | ||
manifest as manifestmod, | ||||
match as matchmod, | ||||
) | ||||
|
r24214 | |||
|
r24569 | EMTPY_MANIFEST = '' | ||
|
r24572 | EMTPY_MANIFEST_V2 = '\0\n' | ||
|
r24569 | |||
|
r24214 | HASH_1 = '1' * 40 | ||
|
r24570 | BIN_HASH_1 = binascii.unhexlify(HASH_1) | ||
|
r24214 | HASH_2 = 'f' * 40 | ||
|
r24570 | BIN_HASH_2 = binascii.unhexlify(HASH_2) | ||
|
r24214 | HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe' | ||
|
r24570 | BIN_HASH_3 = binascii.unhexlify(HASH_3) | ||
|
r24214 | A_SHORT_MANIFEST = ( | ||
'bar/baz/qux.py\0%(hash2)s%(flag2)s\n' | ||||
'foo\0%(hash1)s%(flag1)s\n' | ||||
) % {'hash1': HASH_1, | ||||
'flag1': '', | ||||
'hash2': HASH_2, | ||||
'flag2': 'l', | ||||
} | ||||
|
r24572 | # Same data as A_SHORT_MANIFEST | ||
A_SHORT_MANIFEST_V2 = ( | ||||
'\0\n' | ||||
'\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n' | ||||
'\x00foo\0%(flag1)s\n%(hash1)s\n' | ||||
) % {'hash1': BIN_HASH_1, | ||||
'flag1': '', | ||||
'hash2': BIN_HASH_2, | ||||
'flag2': 'l', | ||||
} | ||||
# Same data as A_SHORT_MANIFEST | ||||
A_METADATA_MANIFEST = ( | ||||
'\0foo\0bar\n' | ||||
'\x00bar/baz/qux.py\0%(flag2)s\0foo\0bar\n%(hash2)s\n' # flag and metadata | ||||
'\x00foo\0%(flag1)s\0foo\n%(hash1)s\n' # no flag, but metadata | ||||
) % {'hash1': BIN_HASH_1, | ||||
'flag1': '', | ||||
'hash2': BIN_HASH_2, | ||||
'flag2': 'l', | ||||
} | ||||
A_STEM_COMPRESSED_MANIFEST = ( | ||||
'\0\n' | ||||
'\x00bar/baz/qux.py\0%(flag2)s\n%(hash2)s\n' | ||||
'\x04qux/foo.py\0%(flag1)s\n%(hash1)s\n' # simple case of 4 stem chars | ||||
'\x0az.py\0%(flag1)s\n%(hash1)s\n' # tricky newline = 10 stem characters | ||||
'\x00%(verylongdir)sx/x\0\n%(hash1)s\n' | ||||
'\xffx/y\0\n%(hash2)s\n' # more than 255 stem chars | ||||
) % {'hash1': BIN_HASH_1, | ||||
'flag1': '', | ||||
'hash2': BIN_HASH_2, | ||||
'flag2': 'l', | ||||
'verylongdir': 255 * 'x', | ||||
} | ||||
|
r24549 | A_DEEPER_MANIFEST = ( | ||
'a/b/c/bar.py\0%(hash3)s%(flag1)s\n' | ||||
'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n' | ||||
'a/b/c/foo.py\0%(hash3)s%(flag1)s\n' | ||||
'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n' | ||||
'a/b/d/baz.py\0%(hash3)s%(flag1)s\n' | ||||
'a/b/d/qux.py\0%(hash1)s%(flag2)s\n' | ||||
'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n' | ||||
'a/b/dog.py\0%(hash3)s%(flag1)s\n' | ||||
'a/b/fish.py\0%(hash2)s%(flag1)s\n' | ||||
'a/c/london.py\0%(hash3)s%(flag2)s\n' | ||||
'a/c/paper.txt\0%(hash2)s%(flag2)s\n' | ||||
'a/c/paris.py\0%(hash2)s%(flag1)s\n' | ||||
'a/d/apple.py\0%(hash3)s%(flag1)s\n' | ||||
'a/d/pizza.py\0%(hash3)s%(flag2)s\n' | ||||
'a/green.py\0%(hash1)s%(flag2)s\n' | ||||
'a/purple.py\0%(hash2)s%(flag1)s\n' | ||||
'app.py\0%(hash3)s%(flag1)s\n' | ||||
'readme.txt\0%(hash2)s%(flag1)s\n' | ||||
) % {'hash1': HASH_1, | ||||
'flag1': '', | ||||
'hash2': HASH_2, | ||||
'flag2': 'l', | ||||
'hash3': HASH_3, | ||||
} | ||||
|
r24214 | HUGE_MANIFEST_ENTRIES = 200001 | ||
A_HUGE_MANIFEST = ''.join(sorted( | ||||
'file%d\0%s%s\n' % (i, h, f) for i, h, f in | ||||
itertools.izip(xrange(200001), | ||||
itertools.cycle((HASH_1, HASH_2)), | ||||
itertools.cycle(('', 'x', 'l'))))) | ||||
|
r24655 | class basemanifesttests(object): | ||
|
r24654 | def parsemanifest(self, text): | ||
|
r24655 | raise NotImplementedError('parsemanifest not implemented by test case') | ||
|
r24214 | |||
def assertIn(self, thing, container, msg=None): | ||||
# assertIn new in 2.7, use it if available, otherwise polyfill | ||||
sup = getattr(unittest.TestCase, 'assertIn', False) | ||||
if sup: | ||||
return sup(self, thing, container, msg=msg) | ||||
if not msg: | ||||
msg = 'Expected %r in %r' % (thing, container) | ||||
self.assert_(thing in container, msg) | ||||
def testEmptyManifest(self): | ||||
|
r24654 | m = self.parsemanifest(EMTPY_MANIFEST) | ||
|
r24214 | self.assertEqual(0, len(m)) | ||
|
r24466 | self.assertEqual([], list(m)) | ||
|
r24214 | |||
|
r24572 | def testEmptyManifestv2(self): | ||
|
r24654 | m = self.parsemanifest(EMTPY_MANIFEST_V2) | ||
|
r24572 | self.assertEqual(0, len(m)) | ||
self.assertEqual([], list(m)) | ||||
|
r24214 | def testManifest(self): | ||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24466 | self.assertEqual(['bar/baz/qux.py', 'foo'], list(m)) | ||
|
r24570 | self.assertEqual(BIN_HASH_2, m['bar/baz/qux.py']) | ||
|
r24466 | self.assertEqual('l', m.flags('bar/baz/qux.py')) | ||
|
r24570 | self.assertEqual(BIN_HASH_1, m['foo']) | ||
|
r24466 | self.assertEqual('', m.flags('foo')) | ||
|
r24214 | self.assertRaises(KeyError, lambda : m['wat']) | ||
|
r24572 | def testParseManifestV2(self): | ||
|
r24654 | m1 = self.parsemanifest(A_SHORT_MANIFEST) | ||
m2 = self.parsemanifest(A_SHORT_MANIFEST_V2) | ||||
|
r24572 | # Should have same content as A_SHORT_MANIFEST | ||
self.assertEqual(m1.text(), m2.text()) | ||||
def testParseManifestMetadata(self): | ||||
# Metadata is for future-proofing and should be accepted but ignored | ||||
|
r24654 | m = self.parsemanifest(A_METADATA_MANIFEST) | ||
|
r24572 | self.assertEqual(A_SHORT_MANIFEST, m.text()) | ||
def testParseManifestStemCompression(self): | ||||
|
r24654 | m = self.parsemanifest(A_STEM_COMPRESSED_MANIFEST) | ||
|
r24572 | self.assertIn('bar/baz/qux.py', m) | ||
self.assertIn('bar/qux/foo.py', m) | ||||
self.assertIn('bar/qux/foz.py', m) | ||||
self.assertIn(256 * 'x' + '/x', m) | ||||
self.assertIn(256 * 'x' + '/y', m) | ||||
|
r24573 | self.assertEqual(A_STEM_COMPRESSED_MANIFEST, m.text(usemanifestv2=True)) | ||
def testTextV2(self): | ||||
|
r24654 | m1 = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24573 | v2text = m1.text(usemanifestv2=True) | ||
self.assertEqual(A_SHORT_MANIFEST_V2, v2text) | ||||
|
r24572 | |||
|
r24214 | def testSetItem(self): | ||
|
r24570 | want = BIN_HASH_1 | ||
|
r24214 | |||
|
r24654 | m = self.parsemanifest(EMTPY_MANIFEST) | ||
|
r24214 | m['a'] = want | ||
self.assertIn('a', m) | ||||
self.assertEqual(want, m['a']) | ||||
self.assertEqual('a\0' + HASH_1 + '\n', m.text()) | ||||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24214 | m['a'] = want | ||
self.assertEqual(want, m['a']) | ||||
self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST, | ||||
m.text()) | ||||
|
r24465 | |||
|
r24466 | def testSetFlag(self): | ||
want = 'x' | ||||
|
r24654 | m = self.parsemanifest(EMTPY_MANIFEST) | ||
|
r24466 | # first add a file; a file-less flag makes no sense | ||
|
r24570 | m['a'] = BIN_HASH_1 | ||
|
r24466 | m.setflag('a', want) | ||
self.assertEqual(want, m.flags('a')) | ||||
self.assertEqual('a\0' + HASH_1 + want + '\n', m.text()) | ||||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24466 | # first add a file; a file-less flag makes no sense | ||
|
r24570 | m['a'] = BIN_HASH_1 | ||
|
r24466 | m.setflag('a', want) | ||
self.assertEqual(want, m.flags('a')) | ||||
self.assertEqual('a\0' + HASH_1 + want + '\n' + A_SHORT_MANIFEST, | ||||
m.text()) | ||||
|
r24465 | def testCopy(self): | ||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r27637 | m['a'] = BIN_HASH_1 | ||
|
r24214 | m2 = m.copy() | ||
del m | ||||
del m2 # make sure we don't double free() anything | ||||
def testCompaction(self): | ||||
unhex = binascii.unhexlify | ||||
h1, h2 = unhex(HASH_1), unhex(HASH_2) | ||||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24466 | m['alpha'] = h1 | ||
m['beta'] = h2 | ||||
|
r24214 | del m['foo'] | ||
want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % ( | ||||
HASH_1, HASH_2, HASH_2) | ||||
self.assertEqual(want, m.text()) | ||||
self.assertEqual(3, len(m)) | ||||
|
r24466 | self.assertEqual(['alpha', 'bar/baz/qux.py', 'beta'], list(m)) | ||
self.assertEqual(h1, m['alpha']) | ||||
self.assertEqual(h2, m['bar/baz/qux.py']) | ||||
self.assertEqual(h2, m['beta']) | ||||
self.assertEqual('', m.flags('alpha')) | ||||
self.assertEqual('l', m.flags('bar/baz/qux.py')) | ||||
self.assertEqual('', m.flags('beta')) | ||||
|
r24214 | self.assertRaises(KeyError, lambda : m['foo']) | ||
def testSetGetNodeSuffix(self): | ||||
|
r24654 | clean = self.parsemanifest(A_SHORT_MANIFEST) | ||
m = self.parsemanifest(A_SHORT_MANIFEST) | ||||
|
r24466 | h = m['foo'] | ||
f = m.flags('foo') | ||||
want = h + 'a' | ||||
|
r24214 | # Merge code wants to set 21-byte fake hashes at times | ||
m['foo'] = want | ||||
self.assertEqual(want, m['foo']) | ||||
|
r24570 | self.assertEqual([('bar/baz/qux.py', BIN_HASH_2), | ||
('foo', BIN_HASH_1 + 'a')], | ||||
|
r24466 | list(m.iteritems())) | ||
|
r24214 | # Sometimes it even tries a 22-byte fake hash, but we can | ||
# return 21 and it'll work out | ||||
|
r24466 | m['foo'] = want + '+' | ||
|
r24214 | self.assertEqual(want, m['foo']) | ||
# make sure the suffix survives a copy | ||||
|
r24466 | match = matchmod.match('', '', ['re:foo']) | ||
m2 = m.matches(match) | ||||
|
r24214 | self.assertEqual(want, m2['foo']) | ||
self.assertEqual(1, len(m2)) | ||||
m2 = m.copy() | ||||
self.assertEqual(want, m2['foo']) | ||||
# suffix with iteration | ||||
|
r24570 | self.assertEqual([('bar/baz/qux.py', BIN_HASH_2), | ||
|
r24466 | ('foo', want)], | ||
list(m.iteritems())) | ||||
|
r24214 | # shows up in diff | ||
|
r24466 | self.assertEqual({'foo': ((want, f), (h, ''))}, m.diff(clean)) | ||
self.assertEqual({'foo': ((h, ''), (want, f))}, clean.diff(m)) | ||||
|
r24214 | |||
|
r24466 | def testMatchException(self): | ||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24466 | match = matchmod.match('', '', ['re:.*']) | ||
|
r24214 | def filt(path): | ||
if path == 'foo': | ||||
assert False | ||||
return True | ||||
|
r24466 | match.matchfn = filt | ||
self.assertRaises(AssertionError, m.matches, match) | ||||
|
r24214 | |||
def testRemoveItem(self): | ||||
|
r24654 | m = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24214 | del m['foo'] | ||
self.assertRaises(KeyError, lambda : m['foo']) | ||||
self.assertEqual(1, len(m)) | ||||
self.assertEqual(1, len(list(m))) | ||||
|
r24228 | # now restore and make sure everything works right | ||
|
r24466 | m['foo'] = 'a' * 20 | ||
|
r24228 | self.assertEqual(2, len(m)) | ||
self.assertEqual(2, len(list(m))) | ||||
|
r24214 | |||
def testManifestDiff(self): | ||||
MISSING = (None, '') | ||||
addl = 'z-only-in-left\0' + HASH_1 + '\n' | ||||
addr = 'z-only-in-right\0' + HASH_2 + 'x\n' | ||||
|
r24654 | left = self.parsemanifest( | ||
|
r24214 | A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl) | ||
|
r24654 | right = self.parsemanifest(A_SHORT_MANIFEST + addr) | ||
|
r24214 | want = { | ||
|
r24570 | 'foo': ((BIN_HASH_3, 'x'), | ||
(BIN_HASH_1, '')), | ||||
'z-only-in-left': ((BIN_HASH_1, ''), MISSING), | ||||
'z-only-in-right': (MISSING, (BIN_HASH_2, 'x')), | ||||
|
r24214 | } | ||
self.assertEqual(want, left.diff(right)) | ||||
want = { | ||||
|
r24570 | 'bar/baz/qux.py': (MISSING, (BIN_HASH_2, 'l')), | ||
'foo': (MISSING, (BIN_HASH_3, 'x')), | ||||
'z-only-in-left': (MISSING, (BIN_HASH_1, '')), | ||||
|
r24214 | } | ||
|
r24654 | self.assertEqual(want, self.parsemanifest(EMTPY_MANIFEST).diff(left)) | ||
|
r24214 | |||
want = { | ||||
|
r24570 | 'bar/baz/qux.py': ((BIN_HASH_2, 'l'), MISSING), | ||
'foo': ((BIN_HASH_3, 'x'), MISSING), | ||||
'z-only-in-left': ((BIN_HASH_1, ''), MISSING), | ||||
|
r24214 | } | ||
|
r24654 | self.assertEqual(want, left.diff(self.parsemanifest(EMTPY_MANIFEST))) | ||
|
r24214 | copy = right.copy() | ||
del copy['z-only-in-right'] | ||||
del right['foo'] | ||||
want = { | ||||
|
r24570 | 'foo': (MISSING, (BIN_HASH_1, '')), | ||
'z-only-in-right': ((BIN_HASH_2, 'x'), MISSING), | ||||
|
r24214 | } | ||
self.assertEqual(want, right.diff(copy)) | ||||
|
r24654 | short = self.parsemanifest(A_SHORT_MANIFEST) | ||
|
r24214 | pruned = short.copy() | ||
del pruned['foo'] | ||||
want = { | ||||
|
r24570 | 'foo': ((BIN_HASH_1, ''), MISSING), | ||
|
r24214 | } | ||
self.assertEqual(want, short.diff(pruned)) | ||||
want = { | ||||
|
r24570 | 'foo': (MISSING, (BIN_HASH_1, '')), | ||
|
r24214 | } | ||
self.assertEqual(want, pruned.diff(short)) | ||||
want = { | ||||
'bar/baz/qux.py': None, | ||||
|
r24570 | 'foo': (MISSING, (BIN_HASH_1, '')), | ||
|
r24214 | } | ||
self.assertEqual(want, pruned.diff(short, True)) | ||||
def testReversedLines(self): | ||||
backwards = ''.join( | ||||
l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l) | ||||
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: | ||||
|
r24654 | self.parsemanifest(A_SHORT_MANIFEST + '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: | ||||
|
r24654 | self.parsemanifest('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): | ||
'''Tests matches() for a few specific files to make sure that both | ||||
the set of files as well as their flags and nodeids are correct in | ||||
the resulting manifest.''' | ||||
|
r24654 | m = self.parsemanifest(A_HUGE_MANIFEST) | ||
|
r24495 | |||
match = matchmod.match('/', '', | ||||
['file1', 'file200', 'file300'], exact=True) | ||||
m2 = m.matches(match) | ||||
|
r24225 | w = ('file1\0%sx\n' | ||
'file200\0%sl\n' | ||||
'file300\0%s\n') % (HASH_2, HASH_1, HASH_1) | ||||
self.assertEqual(w, m2.text()) | ||||
|
r24214 | |||
|
r24549 | def testMatchesNonexistentFile(self): | ||
'''Tests matches() for a small set of specific files, including one | ||||
nonexistent file to make sure in only matches against existing files. | ||||
''' | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
match = matchmod.match('/', '', | ||||
['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt', 'nonexistent'], | ||||
exact=True) | ||||
m2 = m.matches(match) | ||||
self.assertEqual( | ||||
['a/b/c/bar.txt', 'a/b/d/qux.py', 'readme.txt'], | ||||
m2.keys()) | ||||
def testMatchesNonexistentDirectory(self): | ||||
'''Tests matches() for a relpath match on a directory that doesn't | ||||
actually exist.''' | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
match = matchmod.match('/', '', ['a/f'], default='relpath') | ||||
m2 = m.matches(match) | ||||
self.assertEqual([], m2.keys()) | ||||
def testMatchesExactLarge(self): | ||||
'''Tests matches() for files matching a large list of exact files. | ||||
''' | ||||
|
r24654 | m = self.parsemanifest(A_HUGE_MANIFEST) | ||
|
r24549 | |||
flist = m.keys()[80:300] | ||||
match = matchmod.match('/', '', flist, exact=True) | ||||
m2 = m.matches(match) | ||||
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 | |||
match = matchmod.match('/', '', ['']) | ||||
m2 = m.matches(match) | ||||
self.assertEqual(m.keys(), m2.keys()) | ||||
def testMatchesDirectory(self): | ||||
'''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 | |||
match = matchmod.match('/', '', ['a/b'], default='relpath') | ||||
m2 = m.matches(match) | ||||
self.assertEqual([ | ||||
'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt', | ||||
'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py', | ||||
'a/b/fish.py'], m2.keys()) | ||||
def testMatchesExactPath(self): | ||||
'''Tests matches() on an exact match on a directory, which should | ||||
result in an empty manifest because you can't perform an exact match | ||||
against a directory.''' | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
match = matchmod.match('/', '', ['a/b'], exact=True) | ||||
m2 = m.matches(match) | ||||
self.assertEqual([], m2.keys()) | ||||
def testMatchesCwd(self): | ||||
'''Tests matches() on a relpath match with the current directory ('.') | ||||
when not in the root directory.''' | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
match = matchmod.match('/', 'a/b', ['.'], default='relpath') | ||||
m2 = m.matches(match) | ||||
self.assertEqual([ | ||||
'a/b/c/bar.py', 'a/b/c/bar.txt', 'a/b/c/foo.py', 'a/b/c/foo.txt', | ||||
'a/b/d/baz.py', 'a/b/d/qux.py', 'a/b/d/ten.txt', 'a/b/dog.py', | ||||
'a/b/fish.py'], m2.keys()) | ||||
def testMatchesWithPattern(self): | ||||
'''Tests matches() for files matching a pattern that reside | ||||
deeper than the specified directory.''' | ||||
|
r24654 | m = self.parsemanifest(A_DEEPER_MANIFEST) | ||
|
r24549 | |||
match = matchmod.match('/', '', ['a/b/*/*.txt']) | ||||
m2 = m.matches(match) | ||||
self.assertEqual( | ||||
['a/b/c/bar.txt', 'a/b/c/foo.txt', 'a/b/d/ten.txt'], | ||||
m2.keys()) | ||||
|
r24655 | class testmanifestdict(unittest.TestCase, basemanifesttests): | ||
def parsemanifest(self, text): | ||||
return manifestmod.manifestdict(text) | ||||
|
r24656 | class testtreemanifest(unittest.TestCase, basemanifesttests): | ||
def parsemanifest(self, text): | ||||
return manifestmod.treemanifest('', text) | ||||
|
r24214 | if __name__ == '__main__': | ||
silenttestrunner.main(__name__) | ||||