##// END OF EJS Templates
tests: various Python 3 ports for test-remotefilelog-datapack.py...
Gregory Szorc -
r41614:90b3898c default
parent child Browse files
Show More
@@ -1,379 +1,380
1 1 #!/usr/bin/env python
2 2 from __future__ import absolute_import, print_function
3 3
4 4 import hashlib
5 5 import os
6 6 import random
7 7 import shutil
8 8 import stat
9 9 import struct
10 10 import sys
11 11 import tempfile
12 12 import time
13 13 import unittest
14 14
15 15 import silenttestrunner
16 16
17 17 # Load the local remotefilelog, not the system one
18 18 sys.path[0:0] = [os.path.join(os.path.dirname(__file__), '..')]
19 19 from mercurial.node import nullid
20 20 from mercurial import (
21 21 pycompat,
22 22 ui as uimod,
23 23 )
24 24 from hgext.remotefilelog import (
25 25 basepack,
26 26 constants,
27 27 datapack,
28 28 )
29 29
30 30 class datapacktestsbase(object):
31 31 def __init__(self, datapackreader, paramsavailable):
32 32 self.datapackreader = datapackreader
33 33 self.paramsavailable = paramsavailable
34 34
35 35 def setUp(self):
36 36 self.tempdirs = []
37 37
38 38 def tearDown(self):
39 39 for d in self.tempdirs:
40 40 shutil.rmtree(d)
41 41
42 42 def makeTempDir(self):
43 tempdir = tempfile.mkdtemp()
43 tempdir = pycompat.bytestr(tempfile.mkdtemp())
44 44 self.tempdirs.append(tempdir)
45 45 return tempdir
46 46
47 47 def getHash(self, content):
48 48 return hashlib.sha1(content).digest()
49 49
50 50 def getFakeHash(self):
51 return ''.join(chr(random.randint(0, 255)) for _ in range(20))
51 return b''.join(pycompat.bytechr(random.randint(0, 255))
52 for _ in range(20))
52 53
53 54 def createPack(self, revisions=None, packdir=None):
54 55 if revisions is None:
55 56 revisions = [(b"filename", self.getFakeHash(), nullid, b"content")]
56 57
57 58 if packdir is None:
58 59 packdir = self.makeTempDir()
59 60
60 61 packer = datapack.mutabledatapack(uimod.ui(), packdir, version=2)
61 62
62 63 for args in revisions:
63 64 filename, node, base, content = args[0:4]
64 65 # meta is optional
65 66 meta = None
66 67 if len(args) > 4:
67 68 meta = args[4]
68 69 packer.add(filename, node, base, content, metadata=meta)
69 70
70 71 path = packer.close()
71 72 return self.datapackreader(path)
72 73
73 74 def _testAddSingle(self, content):
74 75 """Test putting a simple blob into a pack and reading it out.
75 76 """
76 77 filename = b"foo"
77 78 node = self.getHash(content)
78 79
79 80 revisions = [(filename, node, nullid, content)]
80 81 pack = self.createPack(revisions)
81 82 if self.paramsavailable:
82 83 self.assertEqual(pack.params.fanoutprefix,
83 84 basepack.SMALLFANOUTPREFIX)
84 85
85 86 chain = pack.getdeltachain(filename, node)
86 87 self.assertEqual(content, chain[0][4])
87 88
88 89 def testAddSingle(self):
89 90 self._testAddSingle(b'')
90 91
91 92 def testAddSingleEmpty(self):
92 93 self._testAddSingle(b'abcdef')
93 94
94 95 def testAddMultiple(self):
95 96 """Test putting multiple unrelated blobs into a pack and reading them
96 97 out.
97 98 """
98 99 revisions = []
99 100 for i in range(10):
100 101 filename = b"foo%d" % i
101 102 content = b"abcdef%d" % i
102 103 node = self.getHash(content)
103 104 revisions.append((filename, node, self.getFakeHash(), content))
104 105
105 106 pack = self.createPack(revisions)
106 107
107 108 for filename, node, base, content in revisions:
108 109 entry = pack.getdelta(filename, node)
109 110 self.assertEqual((content, filename, base, {}), entry)
110 111
111 112 chain = pack.getdeltachain(filename, node)
112 113 self.assertEqual(content, chain[0][4])
113 114
114 115 def testAddDeltas(self):
115 116 """Test putting multiple delta blobs into a pack and read the chain.
116 117 """
117 118 revisions = []
118 119 filename = b"foo"
119 120 lastnode = nullid
120 121 for i in range(10):
121 122 content = b"abcdef%d" % i
122 123 node = self.getHash(content)
123 124 revisions.append((filename, node, lastnode, content))
124 125 lastnode = node
125 126
126 127 pack = self.createPack(revisions)
127 128
128 129 entry = pack.getdelta(filename, revisions[0][1])
129 130 realvalue = (revisions[0][3], filename, revisions[0][2], {})
130 131 self.assertEqual(entry, realvalue)
131 132
132 133 # Test that the chain for the final entry has all the others
133 134 chain = pack.getdeltachain(filename, node)
134 135 for i in range(10):
135 136 content = b"abcdef%d" % i
136 137 self.assertEqual(content, chain[-i - 1][4])
137 138
138 139 def testPackMany(self):
139 140 """Pack many related and unrelated objects.
140 141 """
141 142 # Build a random pack file
142 143 revisions = []
143 144 blobs = {}
144 145 random.seed(0)
145 146 for i in range(100):
146 147 filename = b"filename-%d" % i
147 148 filerevs = []
148 149 for j in range(random.randint(1, 100)):
149 150 content = b"content-%d" % j
150 151 node = self.getHash(content)
151 152 lastnode = nullid
152 153 if len(filerevs) > 0:
153 154 lastnode = filerevs[random.randint(0, len(filerevs) - 1)]
154 155 filerevs.append(node)
155 156 blobs[(filename, node, lastnode)] = content
156 157 revisions.append((filename, node, lastnode, content))
157 158
158 159 pack = self.createPack(revisions)
159 160
160 161 # Verify the pack contents
161 162 for (filename, node, lastnode), content in sorted(blobs.items()):
162 163 chain = pack.getdeltachain(filename, node)
163 164 for entry in chain:
164 165 expectedcontent = blobs[(entry[0], entry[1], entry[3])]
165 166 self.assertEqual(entry[4], expectedcontent)
166 167
167 168 def testPackMetadata(self):
168 169 revisions = []
169 170 for i in range(100):
170 171 filename = b'%d.txt' % i
171 172 content = b'put-something-here \n' * i
172 173 node = self.getHash(content)
173 174 meta = {constants.METAKEYFLAG: i ** 4,
174 175 constants.METAKEYSIZE: len(content),
175 176 b'Z': b'random_string',
176 177 b'_': b'\0' * i}
177 178 revisions.append((filename, node, nullid, content, meta))
178 179 pack = self.createPack(revisions)
179 180 for name, node, x, content, origmeta in revisions:
180 181 parsedmeta = pack.getmeta(name, node)
181 182 # flag == 0 should be optimized out
182 183 if origmeta[constants.METAKEYFLAG] == 0:
183 184 del origmeta[constants.METAKEYFLAG]
184 185 self.assertEqual(parsedmeta, origmeta)
185 186
186 187 def testGetMissing(self):
187 188 """Test the getmissing() api.
188 189 """
189 190 revisions = []
190 191 filename = b"foo"
191 192 lastnode = nullid
192 193 for i in range(10):
193 194 content = b"abcdef%d" % i
194 195 node = self.getHash(content)
195 196 revisions.append((filename, node, lastnode, content))
196 197 lastnode = node
197 198
198 199 pack = self.createPack(revisions)
199 200
200 201 missing = pack.getmissing([(b"foo", revisions[0][1])])
201 202 self.assertFalse(missing)
202 203
203 204 missing = pack.getmissing([(b"foo", revisions[0][1]),
204 205 (b"foo", revisions[1][1])])
205 206 self.assertFalse(missing)
206 207
207 208 fakenode = self.getFakeHash()
208 209 missing = pack.getmissing([(b"foo", revisions[0][1]),
209 210 (b"foo", fakenode)])
210 211 self.assertEqual(missing, [(b"foo", fakenode)])
211 212
212 213 def testAddThrows(self):
213 214 pack = self.createPack()
214 215
215 216 try:
216 217 pack.add(b'filename', nullid, b'contents')
217 218 self.assertTrue(False, "datapack.add should throw")
218 219 except RuntimeError:
219 220 pass
220 221
221 222 def testBadVersionThrows(self):
222 223 pack = self.createPack()
223 224 path = pack.path + b'.datapack'
224 with open(path) as f:
225 with open(path, 'rb') as f:
225 226 raw = f.read()
226 227 raw = struct.pack('!B', 255) + raw[1:]
227 228 os.chmod(path, os.stat(path).st_mode | stat.S_IWRITE)
228 with open(path, 'w+') as f:
229 with open(path, 'wb+') as f:
229 230 f.write(raw)
230 231
231 232 try:
232 233 pack = self.datapackreader(pack.path)
233 234 self.assertTrue(False, "bad version number should have thrown")
234 235 except RuntimeError:
235 236 pass
236 237
237 238 def testMissingDeltabase(self):
238 239 fakenode = self.getFakeHash()
239 240 revisions = [(b"filename", fakenode, self.getFakeHash(), b"content")]
240 241 pack = self.createPack(revisions)
241 242 chain = pack.getdeltachain(b"filename", fakenode)
242 243 self.assertEqual(len(chain), 1)
243 244
244 245 def testLargePack(self):
245 246 """Test creating and reading from a large pack with over X entries.
246 247 This causes it to use a 2^16 fanout table instead."""
247 248 revisions = []
248 249 blobs = {}
249 250 total = basepack.SMALLFANOUTCUTOFF + 1
250 251 for i in pycompat.xrange(total):
251 252 filename = b"filename-%d" % i
252 253 content = filename
253 254 node = self.getHash(content)
254 255 blobs[(filename, node)] = content
255 256 revisions.append((filename, node, nullid, content))
256 257
257 258 pack = self.createPack(revisions)
258 259 if self.paramsavailable:
259 260 self.assertEqual(pack.params.fanoutprefix,
260 261 basepack.LARGEFANOUTPREFIX)
261 262
262 263 for (filename, node), content in blobs.items():
263 264 actualcontent = pack.getdeltachain(filename, node)[0][4]
264 265 self.assertEqual(actualcontent, content)
265 266
266 267 def testPacksCache(self):
267 268 """Test that we remember the most recent packs while fetching the delta
268 269 chain."""
269 270
270 271 packdir = self.makeTempDir()
271 272 deltachains = []
272 273
273 274 numpacks = 10
274 275 revisionsperpack = 100
275 276
276 277 for i in range(numpacks):
277 278 chain = []
278 279 revision = (b'%d' % i, self.getFakeHash(), nullid, b"content")
279 280
280 281 for _ in range(revisionsperpack):
281 282 chain.append(revision)
282 283 revision = (
283 284 b'%d' % i,
284 285 self.getFakeHash(),
285 286 revision[1],
286 287 self.getFakeHash()
287 288 )
288 289
289 290 self.createPack(chain, packdir)
290 291 deltachains.append(chain)
291 292
292 293 class testdatapackstore(datapack.datapackstore):
293 294 # Ensures that we are not keeping everything in the cache.
294 295 DEFAULTCACHESIZE = numpacks / 2
295 296
296 297 store = testdatapackstore(uimod.ui(), packdir)
297 298
298 299 random.shuffle(deltachains)
299 300 for randomchain in deltachains:
300 301 revision = random.choice(randomchain)
301 302 chain = store.getdeltachain(revision[0], revision[1])
302 303
303 304 mostrecentpack = next(iter(store.packs), None)
304 305 self.assertEqual(
305 306 mostrecentpack.getdeltachain(revision[0], revision[1]),
306 307 chain
307 308 )
308 309
309 310 self.assertEqual(randomchain.index(revision) + 1, len(chain))
310 311
311 312 # perf test off by default since it's slow
312 313 def _testIndexPerf(self):
313 314 random.seed(0)
314 315 print("Multi-get perf test")
315 316 packsizes = [
316 317 100,
317 318 10000,
318 319 100000,
319 320 500000,
320 321 1000000,
321 322 3000000,
322 323 ]
323 324 lookupsizes = [
324 325 10,
325 326 100,
326 327 1000,
327 328 10000,
328 329 100000,
329 330 1000000,
330 331 ]
331 332 for packsize in packsizes:
332 333 revisions = []
333 334 for i in pycompat.xrange(packsize):
334 335 filename = b"filename-%d" % i
335 336 content = b"content-%d" % i
336 337 node = self.getHash(content)
337 338 revisions.append((filename, node, nullid, content))
338 339
339 340 path = self.createPack(revisions).path
340 341
341 342 # Perf of large multi-get
342 343 import gc
343 344 gc.disable()
344 345 pack = self.datapackreader(path)
345 346 for lookupsize in lookupsizes:
346 347 if lookupsize > packsize:
347 348 continue
348 349 random.shuffle(revisions)
349 350 findnodes = [(rev[0], rev[1]) for rev in revisions]
350 351
351 352 start = time.time()
352 353 pack.getmissing(findnodes[:lookupsize])
353 354 elapsed = time.time() - start
354 355 print ("%s pack %d lookups = %0.04f" %
355 356 (('%d' % packsize).rjust(7),
356 357 ('%d' % lookupsize).rjust(7),
357 358 elapsed))
358 359
359 360 print("")
360 361 gc.enable()
361 362
362 363 # The perf test is meant to produce output, so we always fail the test
363 364 # so the user sees the output.
364 365 raise RuntimeError("perf test always fails")
365 366
366 367 class datapacktests(datapacktestsbase, unittest.TestCase):
367 368 def __init__(self, *args, **kwargs):
368 369 datapacktestsbase.__init__(self, datapack.datapack, True)
369 370 unittest.TestCase.__init__(self, *args, **kwargs)
370 371
371 372 # TODO:
372 373 # datapack store:
373 374 # - getmissing
374 375 # - GC two packs into one
375 376
376 377 if __name__ == '__main__':
377 378 if pycompat.iswindows:
378 379 sys.exit(80) # Skip on Windows
379 380 silenttestrunner.main(__name__)
General Comments 0
You need to be logged in to leave comments. Login now