comparison tests/test-manifest.py @ 24214:a5f1bccd2996

manifest.c: new extension code to lazily parse manifests This lets us iterate manifests in order, but do a _lot_ less work in the common case when we only care about a few manifest entries. Many thanks to Mike Edgar for reviewing this in advance of it going out to the list, which caught many things I missed. This version of the patch includes C89 fixes from Sean Farley and many correctness/efficiency cleanups from Martin von Zweigbergk. Thanks to both!
author Augie Fackler <augie@google.com>
date Tue, 13 Jan 2015 14:31:38 -0800
parents
children 3e5c4af69808
comparison
equal deleted inserted replaced
24213:e0c1328df872 24214:a5f1bccd2996
1 import binascii
2 import unittest
3 import itertools
4
5 import silenttestrunner
6
7 from mercurial import parsers
8
9 HASH_1 = '1' * 40
10 HASH_2 = 'f' * 40
11 HASH_3 = '1234567890abcdef0987654321deadbeef0fcafe'
12 A_SHORT_MANIFEST = (
13 'bar/baz/qux.py\0%(hash2)s%(flag2)s\n'
14 'foo\0%(hash1)s%(flag1)s\n'
15 ) % {'hash1': HASH_1,
16 'flag1': '',
17 'hash2': HASH_2,
18 'flag2': 'l',
19 }
20
21 HUGE_MANIFEST_ENTRIES = 200001
22
23 A_HUGE_MANIFEST = ''.join(sorted(
24 'file%d\0%s%s\n' % (i, h, f) for i, h, f in
25 itertools.izip(xrange(200001),
26 itertools.cycle((HASH_1, HASH_2)),
27 itertools.cycle(('', 'x', 'l')))))
28
29 class testmanifest(unittest.TestCase):
30
31 def assertIn(self, thing, container, msg=None):
32 # assertIn new in 2.7, use it if available, otherwise polyfill
33 sup = getattr(unittest.TestCase, 'assertIn', False)
34 if sup:
35 return sup(self, thing, container, msg=msg)
36 if not msg:
37 msg = 'Expected %r in %r' % (thing, container)
38 self.assert_(thing in container, msg)
39
40 def testEmptyManifest(self):
41 m = parsers.lazymanifest('')
42 self.assertEqual(0, len(m))
43 self.assertEqual([], list(m))
44
45 def testManifest(self):
46 m = parsers.lazymanifest(A_SHORT_MANIFEST)
47 want = [
48 ('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
49 ('foo', binascii.unhexlify(HASH_1), ''),
50 ]
51 self.assertEqual(len(want), len(m))
52 self.assertEqual(want, list(m))
53 self.assertEqual((binascii.unhexlify(HASH_1), ''), m['foo'])
54 self.assertRaises(KeyError, lambda : m['wat'])
55 self.assertEqual((binascii.unhexlify(HASH_2), 'l'),
56 m['bar/baz/qux.py'])
57
58 def testSetItem(self):
59 want = binascii.unhexlify(HASH_1), ''
60
61 m = parsers.lazymanifest('')
62 m['a'] = want
63 self.assertIn('a', m)
64 self.assertEqual(want, m['a'])
65 self.assertEqual('a\0' + HASH_1 + '\n', m.text())
66
67 m = parsers.lazymanifest(A_SHORT_MANIFEST)
68 m['a'] = want
69 self.assertEqual(want, m['a'])
70 self.assertEqual('a\0' + HASH_1 + '\n' + A_SHORT_MANIFEST,
71 m.text())
72 m2 = m.copy()
73 del m
74 del m2 # make sure we don't double free() anything
75
76 def testCompaction(self):
77 unhex = binascii.unhexlify
78 h1, h2 = unhex(HASH_1), unhex(HASH_2)
79 m = parsers.lazymanifest(A_SHORT_MANIFEST)
80 m['alpha'] = h1, ''
81 m['beta'] = h2, ''
82 del m['foo']
83 want = 'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
84 HASH_1, HASH_2, HASH_2)
85 self.assertEqual(want, m.text())
86 self.assertEqual(3, len(m))
87 self.assertEqual((h1, ''), m['alpha'])
88 self.assertEqual((h2, ''), m['beta'])
89 self.assertRaises(KeyError, lambda : m['foo'])
90 w = [('alpha', h1, ''), ('bar/baz/qux.py', h2, 'l'), ('beta', h2, '')]
91 self.assertEqual(w, list(m))
92
93 def testSetGetNodeSuffix(self):
94 clean = parsers.lazymanifest(A_SHORT_MANIFEST)
95 m = parsers.lazymanifest(A_SHORT_MANIFEST)
96 h, f = m['foo']
97 want = h + 'a', f
98 # Merge code wants to set 21-byte fake hashes at times
99 m['foo'] = want
100 self.assertEqual(want, m['foo'])
101 self.assertEqual([('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
102 ('foo', binascii.unhexlify(HASH_1) + 'a', '')],
103 list(m))
104 # Sometimes it even tries a 22-byte fake hash, but we can
105 # return 21 and it'll work out
106 m['foo'] = want[0] + '+', f
107 self.assertEqual(want, m['foo'])
108 # make sure the suffix survives a copy
109 m2 = m.filtercopy(lambda x: x == 'foo')
110 self.assertEqual(want, m2['foo'])
111 self.assertEqual(1, len(m2))
112 self.assertEqual(('foo\0%s\n' % HASH_1), m2.text())
113 m2 = m.copy()
114 self.assertEqual(want, m2['foo'])
115 # suffix with iteration
116 self.assertEqual([('bar/baz/qux.py', binascii.unhexlify(HASH_2), 'l'),
117 ('foo', want[0], '')], list(m))
118 # shows up in diff
119 self.assertEqual({'foo': (want, (h, ''))}, m.diff(clean))
120 self.assertEqual({'foo': ((h, ''), want)}, clean.diff(m))
121
122 def testFilterCopyException(self):
123 m = parsers.lazymanifest(A_SHORT_MANIFEST)
124 def filt(path):
125 if path == 'foo':
126 assert False
127 return True
128 self.assertRaises(AssertionError, m.filtercopy, filt)
129
130 def testRemoveItem(self):
131 m = parsers.lazymanifest(A_SHORT_MANIFEST)
132 del m['foo']
133 self.assertRaises(KeyError, lambda : m['foo'])
134 self.assertEqual(1, len(m))
135 self.assertEqual(1, len(list(m)))
136
137 def testManifestDiff(self):
138 MISSING = (None, '')
139 addl = 'z-only-in-left\0' + HASH_1 + '\n'
140 addr = 'z-only-in-right\0' + HASH_2 + 'x\n'
141 left = parsers.lazymanifest(
142 A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + 'x') + addl)
143 right = parsers.lazymanifest(A_SHORT_MANIFEST + addr)
144 want = {
145 'foo': ((binascii.unhexlify(HASH_3), 'x'),
146 (binascii.unhexlify(HASH_1), '')),
147 'z-only-in-left': ((binascii.unhexlify(HASH_1), ''), MISSING),
148 'z-only-in-right': (MISSING, (binascii.unhexlify(HASH_2), 'x')),
149 }
150 self.assertEqual(want, left.diff(right))
151
152 want = {
153 'bar/baz/qux.py': (MISSING, (binascii.unhexlify(HASH_2), 'l')),
154 'foo': (MISSING, (binascii.unhexlify(HASH_3), 'x')),
155 'z-only-in-left': (MISSING, (binascii.unhexlify(HASH_1), '')),
156 }
157 self.assertEqual(want, parsers.lazymanifest('').diff(left))
158
159 want = {
160 'bar/baz/qux.py': ((binascii.unhexlify(HASH_2), 'l'), MISSING),
161 'foo': ((binascii.unhexlify(HASH_3), 'x'), MISSING),
162 'z-only-in-left': ((binascii.unhexlify(HASH_1), ''), MISSING),
163 }
164 self.assertEqual(want, left.diff(parsers.lazymanifest('')))
165 copy = right.copy()
166 del copy['z-only-in-right']
167 del right['foo']
168 want = {
169 'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
170 'z-only-in-right': ((binascii.unhexlify(HASH_2), 'x'), MISSING),
171 }
172 self.assertEqual(want, right.diff(copy))
173
174 short = parsers.lazymanifest(A_SHORT_MANIFEST)
175 pruned = short.copy()
176 del pruned['foo']
177 want = {
178 'foo': ((binascii.unhexlify(HASH_1), ''), MISSING),
179 }
180 self.assertEqual(want, short.diff(pruned))
181 want = {
182 'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
183 }
184 self.assertEqual(want, pruned.diff(short))
185 want = {
186 'bar/baz/qux.py': None,
187 'foo': (MISSING, (binascii.unhexlify(HASH_1), '')),
188 }
189 self.assertEqual(want, pruned.diff(short, True))
190
191 def testReversedLines(self):
192 backwards = ''.join(
193 l + '\n' for l in reversed(A_SHORT_MANIFEST.split('\n')) if l)
194 try:
195 parsers.lazymanifest(backwards)
196 self.fail('Should have raised ValueError')
197 except ValueError, v:
198 self.assertIn('Manifest lines not in sorted order.', str(v))
199
200 def testNoTerminalNewline(self):
201 try:
202 parsers.lazymanifest(A_SHORT_MANIFEST + 'wat')
203 self.fail('Should have raised ValueError')
204 except ValueError, v:
205 self.assertIn('Manifest did not end in a newline.', str(v))
206
207 def testNoNewLineAtAll(self):
208 try:
209 parsers.lazymanifest('wat')
210 self.fail('Should have raised ValueError')
211 except ValueError, v:
212 self.assertIn('Manifest did not end in a newline.', str(v))
213
214 def testHugeManifest(self):
215 m = parsers.lazymanifest(A_HUGE_MANIFEST)
216 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
217 self.assertEqual(len(m), len(list(m)))
218
219
220 if __name__ == '__main__':
221 silenttestrunner.main(__name__)