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