comparison tests/test-remotefilelog-histpack.py @ 40495:3a333a582d7b

remotefilelog: import pruned-down remotefilelog extension from hg-experimental This is remotefilelog as of my recent patches for compatibility with current tip of hg, minus support for old versions of Mercurial and some FB-specific features like their treemanifest extension and fetching linkrev data from a patched phabricator. The file extutil.py moved from hgext3rd to remotefilelog. This is not yet ready to be landed, consider it a preview for now. Planned changes include: * replace lz4 with zstd * rename some capabilities, requirements and wireproto commands to mark them as experimental * consolidate bits of shallowutil with related functions (eg readfile) I'm certainly open to other (small) changes, but my rough mission is to land this largely as-is so we can use it as a model of the functionality we need going forward for lazy-fetching of file contents from a server. # no-check-commit because of a few foo_bar functions Differential Revision: https://phab.mercurial-scm.org/D4782
author Augie Fackler <augie@google.com>
date Thu, 27 Sep 2018 13:03:19 -0400
parents
children 10c10da14c5d
comparison
equal deleted inserted replaced
40494:9aeb9e2d28a7 40495:3a333a582d7b
1 #!/usr/bin/env python
2 from __future__ import absolute_import
3
4 import hashlib
5 import os
6 import random
7 import shutil
8 import stat
9 import struct
10 import sys
11 import tempfile
12 import unittest
13
14 import silenttestrunner
15
16 from mercurial.node import nullid
17 from mercurial import (
18 ui as uimod,
19 )
20 # Load the local remotefilelog, not the system one
21 sys.path[0:0] = [os.path.join(os.path.dirname(__file__), '..')]
22 from hgext.remotefilelog import (
23 basepack,
24 historypack,
25 )
26
27 class histpacktests(unittest.TestCase):
28 def setUp(self):
29 self.tempdirs = []
30
31 def tearDown(self):
32 for d in self.tempdirs:
33 shutil.rmtree(d)
34
35 def makeTempDir(self):
36 tempdir = tempfile.mkdtemp()
37 self.tempdirs.append(tempdir)
38 return tempdir
39
40 def getHash(self, content):
41 return hashlib.sha1(content).digest()
42
43 def getFakeHash(self):
44 return ''.join(chr(random.randint(0, 255)) for _ in range(20))
45
46 def createPack(self, revisions=None):
47 """Creates and returns a historypack containing the specified revisions.
48
49 `revisions` is a list of tuples, where each tuple contains a filanem,
50 node, p1node, p2node, and linknode.
51 """
52 if revisions is None:
53 revisions = [("filename", self.getFakeHash(), nullid, nullid,
54 self.getFakeHash(), None)]
55
56 packdir = self.makeTempDir()
57 packer = historypack.mutablehistorypack(uimod.ui(), packdir,
58 version=1)
59
60 for filename, node, p1, p2, linknode, copyfrom in revisions:
61 packer.add(filename, node, p1, p2, linknode, copyfrom)
62
63 path = packer.close()
64 return historypack.historypack(path)
65
66 def testAddSingle(self):
67 """Test putting a single entry into a pack and reading it out.
68 """
69 filename = "foo"
70 node = self.getFakeHash()
71 p1 = self.getFakeHash()
72 p2 = self.getFakeHash()
73 linknode = self.getFakeHash()
74
75 revisions = [(filename, node, p1, p2, linknode, None)]
76 pack = self.createPack(revisions)
77
78 actual = pack.getancestors(filename, node)[node]
79 self.assertEquals(p1, actual[0])
80 self.assertEquals(p2, actual[1])
81 self.assertEquals(linknode, actual[2])
82
83 def testAddMultiple(self):
84 """Test putting multiple unrelated revisions into a pack and reading
85 them out.
86 """
87 revisions = []
88 for i in range(10):
89 filename = "foo-%s" % i
90 node = self.getFakeHash()
91 p1 = self.getFakeHash()
92 p2 = self.getFakeHash()
93 linknode = self.getFakeHash()
94 revisions.append((filename, node, p1, p2, linknode, None))
95
96 pack = self.createPack(revisions)
97
98 for filename, node, p1, p2, linknode, copyfrom in revisions:
99 actual = pack.getancestors(filename, node)[node]
100 self.assertEquals(p1, actual[0])
101 self.assertEquals(p2, actual[1])
102 self.assertEquals(linknode, actual[2])
103 self.assertEquals(copyfrom, actual[3])
104
105 def testAddAncestorChain(self):
106 """Test putting multiple revisions in into a pack and read the ancestor
107 chain.
108 """
109 revisions = []
110 filename = "foo"
111 lastnode = nullid
112 for i in range(10):
113 node = self.getFakeHash()
114 revisions.append((filename, node, lastnode, nullid, nullid, None))
115 lastnode = node
116
117 # revisions must be added in topological order, newest first
118 revisions = list(reversed(revisions))
119 pack = self.createPack(revisions)
120
121 # Test that the chain has all the entries
122 ancestors = pack.getancestors(revisions[0][0], revisions[0][1])
123 for filename, node, p1, p2, linknode, copyfrom in revisions:
124 ap1, ap2, alinknode, acopyfrom = ancestors[node]
125 self.assertEquals(ap1, p1)
126 self.assertEquals(ap2, p2)
127 self.assertEquals(alinknode, linknode)
128 self.assertEquals(acopyfrom, copyfrom)
129
130 def testPackMany(self):
131 """Pack many related and unrelated ancestors.
132 """
133 # Build a random pack file
134 allentries = {}
135 ancestorcounts = {}
136 revisions = []
137 random.seed(0)
138 for i in range(100):
139 filename = "filename-%s" % i
140 entries = []
141 p2 = nullid
142 linknode = nullid
143 for j in range(random.randint(1, 100)):
144 node = self.getFakeHash()
145 p1 = nullid
146 if len(entries) > 0:
147 p1 = entries[random.randint(0, len(entries) - 1)]
148 entries.append(node)
149 revisions.append((filename, node, p1, p2, linknode, None))
150 allentries[(filename, node)] = (p1, p2, linknode)
151 if p1 == nullid:
152 ancestorcounts[(filename, node)] = 1
153 else:
154 newcount = ancestorcounts[(filename, p1)] + 1
155 ancestorcounts[(filename, node)] = newcount
156
157 # Must add file entries in reverse topological order
158 revisions = list(reversed(revisions))
159 pack = self.createPack(revisions)
160
161 # Verify the pack contents
162 for (filename, node), (p1, p2, lastnode) in allentries.iteritems():
163 ancestors = pack.getancestors(filename, node)
164 self.assertEquals(ancestorcounts[(filename, node)],
165 len(ancestors))
166 for anode, (ap1, ap2, alinknode, copyfrom) in ancestors.iteritems():
167 ep1, ep2, elinknode = allentries[(filename, anode)]
168 self.assertEquals(ap1, ep1)
169 self.assertEquals(ap2, ep2)
170 self.assertEquals(alinknode, elinknode)
171 self.assertEquals(copyfrom, None)
172
173 def testGetNodeInfo(self):
174 revisions = []
175 filename = "foo"
176 lastnode = nullid
177 for i in range(10):
178 node = self.getFakeHash()
179 revisions.append((filename, node, lastnode, nullid, nullid, None))
180 lastnode = node
181
182 pack = self.createPack(revisions)
183
184 # Test that getnodeinfo returns the expected results
185 for filename, node, p1, p2, linknode, copyfrom in revisions:
186 ap1, ap2, alinknode, acopyfrom = pack.getnodeinfo(filename, node)
187 self.assertEquals(ap1, p1)
188 self.assertEquals(ap2, p2)
189 self.assertEquals(alinknode, linknode)
190 self.assertEquals(acopyfrom, copyfrom)
191
192 def testGetMissing(self):
193 """Test the getmissing() api.
194 """
195 revisions = []
196 filename = "foo"
197 for i in range(10):
198 node = self.getFakeHash()
199 p1 = self.getFakeHash()
200 p2 = self.getFakeHash()
201 linknode = self.getFakeHash()
202 revisions.append((filename, node, p1, p2, linknode, None))
203
204 pack = self.createPack(revisions)
205
206 missing = pack.getmissing([(filename, revisions[0][1])])
207 self.assertFalse(missing)
208
209 missing = pack.getmissing([(filename, revisions[0][1]),
210 (filename, revisions[1][1])])
211 self.assertFalse(missing)
212
213 fakenode = self.getFakeHash()
214 missing = pack.getmissing([(filename, revisions[0][1]),
215 (filename, fakenode)])
216 self.assertEquals(missing, [(filename, fakenode)])
217
218 # Test getmissing on a non-existant filename
219 missing = pack.getmissing([("bar", fakenode)])
220 self.assertEquals(missing, [("bar", fakenode)])
221
222 def testAddThrows(self):
223 pack = self.createPack()
224
225 try:
226 pack.add('filename', nullid, nullid, nullid, nullid, None)
227 self.assertTrue(False, "historypack.add should throw")
228 except RuntimeError:
229 pass
230
231 def testBadVersionThrows(self):
232 pack = self.createPack()
233 path = pack.path + '.histpack'
234 with open(path) as f:
235 raw = f.read()
236 raw = struct.pack('!B', 255) + raw[1:]
237 os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
238 with open(path, 'w+') as f:
239 f.write(raw)
240
241 try:
242 pack = historypack.historypack(pack.path)
243 self.assertTrue(False, "bad version number should have thrown")
244 except RuntimeError:
245 pass
246
247 def testLargePack(self):
248 """Test creating and reading from a large pack with over X entries.
249 This causes it to use a 2^16 fanout table instead."""
250 total = basepack.SMALLFANOUTCUTOFF + 1
251 revisions = []
252 for i in xrange(total):
253 filename = "foo-%s" % i
254 node = self.getFakeHash()
255 p1 = self.getFakeHash()
256 p2 = self.getFakeHash()
257 linknode = self.getFakeHash()
258 revisions.append((filename, node, p1, p2, linknode, None))
259
260 pack = self.createPack(revisions)
261 self.assertEquals(pack.params.fanoutprefix, basepack.LARGEFANOUTPREFIX)
262
263 for filename, node, p1, p2, linknode, copyfrom in revisions:
264 actual = pack.getancestors(filename, node)[node]
265 self.assertEquals(p1, actual[0])
266 self.assertEquals(p2, actual[1])
267 self.assertEquals(linknode, actual[2])
268 self.assertEquals(copyfrom, actual[3])
269 # TODO:
270 # histpack store:
271 # - repack two packs into one
272
273 if __name__ == '__main__':
274 silenttestrunner.main(__name__)