Mercurial > hg
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__) |