##// END OF EJS Templates
py3: remove xrange() compatibility code...
Manuel Jacob -
r50180:56f98406 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,73 +1,68 b''
1 1 # bruterebase.py - brute force rebase testing
2 2 #
3 3 # Copyright 2017 Facebook, Inc.
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8
9 9 from mercurial import (
10 10 error,
11 11 registrar,
12 12 revsetlang,
13 13 )
14 14
15 15 from hgext import rebase
16 16
17 try:
18 xrange
19 except NameError:
20 xrange = range
21
22 17 cmdtable = {}
23 18 command = registrar.command(cmdtable)
24 19
25 20
26 21 @command(b'debugbruterebase')
27 22 def debugbruterebase(ui, repo, source, dest):
28 23 """for every non-empty subset of source, run rebase -r subset -d dest
29 24
30 25 Print one line summary for each subset. Assume obsstore is enabled.
31 26 """
32 27 srevs = list(repo.revs(source))
33 28
34 29 with repo.wlock(), repo.lock():
35 30 repolen = len(repo)
36 31 cl = repo.changelog
37 32
38 33 def getdesc(rev):
39 34 result = cl.changelogrevision(rev).description
40 35 if rev >= repolen:
41 36 result += b"'"
42 37 return result
43 38
44 for i in xrange(1, 2 ** len(srevs)):
39 for i in range(1, 2 ** len(srevs)):
45 40 subset = [rev for j, rev in enumerate(srevs) if i & (1 << j) != 0]
46 41 spec = revsetlang.formatspec(b'%ld', subset)
47 42 tr = repo.transaction(b'rebase')
48 43 tr._report = lambda x: 0 # hide "transaction abort"
49 44
50 45 with ui.silent():
51 46 try:
52 47 rebase.rebase(ui, repo, dest=dest, rev=[spec])
53 48 except error.Abort as ex:
54 49 summary = b'ABORT: %s' % ex.message
55 50 except Exception as ex:
56 51 summary = b'CRASH: %s' % ex
57 52 else:
58 53 # short summary about new nodes
59 54 cl = repo.changelog
60 55 descs = []
61 for rev in xrange(repolen, len(repo)):
56 for rev in range(repolen, len(repo)):
62 57 desc = b'%s:' % getdesc(rev)
63 58 for prev in cl.parentrevs(rev):
64 59 if prev > -1:
65 60 desc += getdesc(prev)
66 61 descs.append(desc)
67 62 descs.sort()
68 63 summary = b' '.join(descs)
69 64 repo.vfs.tryunlink(b'rebasestate')
70 65
71 66 subsetdesc = b''.join(getdesc(rev) for rev in subset)
72 67 ui.write(b'%s: %s\n' % (subsetdesc.rjust(len(srevs)), summary))
73 68 tr.abort()
@@ -1,158 +1,157 b''
1 1 #!/usr/bin/env python3
2 2
3 3 # fsmonitor-run-tests.py - Run Mercurial tests with fsmonitor enabled
4 4 #
5 5 # Copyright 2017 Facebook, Inc.
6 6 #
7 7 # This software may be used and distributed according to the terms of the
8 8 # GNU General Public License version 2 or any later version.
9 9 #
10 10 # This is a wrapper around run-tests.py that spins up an isolated instance of
11 11 # Watchman and runs the Mercurial tests against it. This ensures that the global
12 12 # version of Watchman isn't affected by anything this test does.
13 13
14 14
15 15 import argparse
16 16 import contextlib
17 17 import json
18 18 import os
19 19 import shutil
20 20 import subprocess
21 21 import sys
22 22 import tempfile
23 23 import uuid
24 24
25 25 osenvironb = getattr(os, 'environb', os.environ)
26 26
27 27 if sys.version_info > (3, 5, 0):
28 28 PYTHON3 = True
29 xrange = range # we use xrange in one place, and we'd rather not use range
30 29
31 30 def _sys2bytes(p):
32 31 return p.encode('utf-8')
33 32
34 33
35 34 elif sys.version_info >= (3, 0, 0):
36 35 print(
37 36 '%s is only supported on Python 3.5+ and 2.7, not %s'
38 37 % (sys.argv[0], '.'.join(str(v) for v in sys.version_info[:3]))
39 38 )
40 39 sys.exit(70) # EX_SOFTWARE from `man 3 sysexit`
41 40 else:
42 41 PYTHON3 = False
43 42
44 43 # In python 2.x, path operations are generally done using
45 44 # bytestrings by default, so we don't have to do any extra
46 45 # fiddling there. We define the wrapper functions anyway just to
47 46 # help keep code consistent between platforms.
48 47 def _sys2bytes(p):
49 48 return p
50 49
51 50
52 51 def getparser():
53 52 """Obtain the argument parser used by the CLI."""
54 53 parser = argparse.ArgumentParser(
55 54 description='Run tests with fsmonitor enabled.',
56 55 epilog='Unrecognized options are passed to run-tests.py.',
57 56 )
58 57 # - keep these sorted
59 58 # - none of these options should conflict with any in run-tests.py
60 59 parser.add_argument(
61 60 '--keep-fsmonitor-tmpdir',
62 61 action='store_true',
63 62 help='keep temporary directory with fsmonitor state',
64 63 )
65 64 parser.add_argument(
66 65 '--watchman',
67 66 help='location of watchman binary (default: watchman in PATH)',
68 67 default='watchman',
69 68 )
70 69
71 70 return parser
72 71
73 72
74 73 @contextlib.contextmanager
75 74 def watchman(args):
76 75 basedir = tempfile.mkdtemp(prefix='hg-fsmonitor')
77 76 try:
78 77 # Much of this configuration is borrowed from Watchman's test harness.
79 78 cfgfile = os.path.join(basedir, 'config.json')
80 79 # TODO: allow setting a config
81 80 with open(cfgfile, 'w') as f:
82 81 f.write(json.dumps({}))
83 82
84 83 logfile = os.path.join(basedir, 'log')
85 84 clilogfile = os.path.join(basedir, 'cli-log')
86 85 if os.name == 'nt':
87 86 sockfile = '\\\\.\\pipe\\watchman-test-%s' % uuid.uuid4().hex
88 87 else:
89 88 sockfile = os.path.join(basedir, 'sock')
90 89 pidfile = os.path.join(basedir, 'pid')
91 90 statefile = os.path.join(basedir, 'state')
92 91
93 92 argv = [
94 93 args.watchman,
95 94 '--sockname',
96 95 sockfile,
97 96 '--logfile',
98 97 logfile,
99 98 '--pidfile',
100 99 pidfile,
101 100 '--statefile',
102 101 statefile,
103 102 '--foreground',
104 103 '--log-level=2', # debug logging for watchman
105 104 ]
106 105
107 106 envb = osenvironb.copy()
108 107 envb[b'WATCHMAN_CONFIG_FILE'] = _sys2bytes(cfgfile)
109 108 with open(clilogfile, 'wb') as f:
110 109 proc = subprocess.Popen(
111 110 argv, env=envb, stdin=None, stdout=f, stderr=f
112 111 )
113 112 try:
114 113 yield sockfile
115 114 finally:
116 115 proc.terminate()
117 116 proc.kill()
118 117 finally:
119 118 if args.keep_fsmonitor_tmpdir:
120 119 print('fsmonitor dir available at %s' % basedir)
121 120 else:
122 121 shutil.rmtree(basedir, ignore_errors=True)
123 122
124 123
125 124 def run():
126 125 parser = getparser()
127 126 args, runtestsargv = parser.parse_known_args()
128 127
129 128 with watchman(args) as sockfile:
130 129 osenvironb[b'WATCHMAN_SOCK'] = _sys2bytes(sockfile)
131 130 # Indicate to hghave that we're running with fsmonitor enabled.
132 131 osenvironb[b'HGFSMONITOR_TESTS'] = b'1'
133 132
134 133 runtestdir = os.path.dirname(__file__)
135 134 runtests = os.path.join(runtestdir, 'run-tests.py')
136 135 blacklist = os.path.join(runtestdir, 'blacklists', 'fsmonitor')
137 136
138 137 runtestsargv.insert(0, runtests)
139 138 runtestsargv.extend(
140 139 [
141 140 '--extra-config',
142 141 'extensions.fsmonitor=',
143 142 # specify fsmonitor.mode=paranoid always in order to force
144 143 # fsmonitor extension execute "paranoid" code path
145 144 #
146 145 # TODO: make fsmonitor-run-tests.py accept specific options
147 146 '--extra-config',
148 147 'fsmonitor.mode=paranoid',
149 148 '--blacklist',
150 149 blacklist,
151 150 ]
152 151 )
153 152
154 153 return subprocess.call(runtestsargv)
155 154
156 155
157 156 if __name__ == '__main__':
158 157 sys.exit(run())
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
@@ -1,36 +1,33 b''
1 1 #!/usr/bin/env python3
2 2 #
3 3 # A portable replacement for 'seq'
4 4 #
5 5 # Usage:
6 6 # seq STOP [1, STOP] stepping by 1
7 7 # seq START STOP [START, STOP] stepping by 1
8 8 # seq START STEP STOP [START, STOP] stepping by STEP
9 9
10 10 import os
11 11 import sys
12 12
13 13 try:
14 14 import msvcrt
15 15
16 16 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
17 17 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
18 18 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
19 19 except ImportError:
20 20 pass
21 21
22 if sys.version_info[0] >= 3:
23 xrange = range
24
25 22 start = 1
26 23 if len(sys.argv) > 2:
27 24 start = int(sys.argv[1])
28 25
29 26 step = 1
30 27 if len(sys.argv) > 3:
31 28 step = int(sys.argv[2])
32 29
33 30 stop = int(sys.argv[-1]) + 1
34 31
35 for i in xrange(start, stop, step):
32 for i in range(start, stop, step):
36 33 print(i)
@@ -1,471 +1,470 b''
1 1 import binascii
2 2 import getopt
3 3 import math
4 4 import os
5 5 import random
6 6 import sys
7 7 import time
8 8
9 9 from mercurial.node import nullrev
10 10 from mercurial import (
11 11 ancestor,
12 12 debugcommands,
13 13 hg,
14 14 pycompat,
15 15 ui as uimod,
16 16 util,
17 17 )
18 18
19 19 if pycompat.ispy3:
20 20 long = int
21 xrange = range
22 21
23 22
24 23 def buildgraph(rng, nodes=100, rootprob=0.05, mergeprob=0.2, prevprob=0.7):
25 24 """nodes: total number of nodes in the graph
26 25 rootprob: probability that a new node (not 0) will be a root
27 26 mergeprob: probability that, excluding a root a node will be a merge
28 27 prevprob: probability that p1 will be the previous node
29 28
30 29 return value is a graph represented as an adjacency list.
31 30 """
32 31 graph = [None] * nodes
33 for i in xrange(nodes):
32 for i in range(nodes):
34 33 if i == 0 or rng.random() < rootprob:
35 34 graph[i] = [nullrev]
36 35 elif i == 1:
37 36 graph[i] = [0]
38 37 elif rng.random() < mergeprob:
39 38 if i == 2 or rng.random() < prevprob:
40 39 # p1 is prev
41 40 p1 = i - 1
42 41 else:
43 42 p1 = rng.randrange(i - 1)
44 43 p2 = rng.choice(list(range(0, p1)) + list(range(p1 + 1, i)))
45 44 graph[i] = [p1, p2]
46 45 elif rng.random() < prevprob:
47 46 graph[i] = [i - 1]
48 47 else:
49 48 graph[i] = [rng.randrange(i - 1)]
50 49
51 50 return graph
52 51
53 52
54 53 def buildancestorsets(graph):
55 54 ancs = [None] * len(graph)
56 for i in xrange(len(graph)):
55 for i in range(len(graph)):
57 56 ancs[i] = {i}
58 57 if graph[i] == [nullrev]:
59 58 continue
60 59 for p in graph[i]:
61 60 ancs[i].update(ancs[p])
62 61 return ancs
63 62
64 63
65 64 class naiveincrementalmissingancestors:
66 65 def __init__(self, ancs, bases):
67 66 self.ancs = ancs
68 67 self.bases = set(bases)
69 68
70 69 def addbases(self, newbases):
71 70 self.bases.update(newbases)
72 71
73 72 def removeancestorsfrom(self, revs):
74 73 for base in self.bases:
75 74 if base != nullrev:
76 75 revs.difference_update(self.ancs[base])
77 76 revs.discard(nullrev)
78 77
79 78 def missingancestors(self, revs):
80 79 res = set()
81 80 for rev in revs:
82 81 if rev != nullrev:
83 82 res.update(self.ancs[rev])
84 83 for base in self.bases:
85 84 if base != nullrev:
86 85 res.difference_update(self.ancs[base])
87 86 return sorted(res)
88 87
89 88
90 89 def test_missingancestors(seed, rng):
91 90 # empirically observed to take around 1 second
92 91 graphcount = 100
93 92 testcount = 10
94 93 inccount = 10
95 94 nerrs = [0]
96 95 # the default mu and sigma give us a nice distribution of mostly
97 96 # single-digit counts (including 0) with some higher ones
98 97 def lognormrandom(mu, sigma):
99 98 return int(math.floor(rng.lognormvariate(mu, sigma)))
100 99
101 100 def samplerevs(nodes, mu=1.1, sigma=0.8):
102 101 count = min(lognormrandom(mu, sigma), len(nodes))
103 102 return rng.sample(nodes, count)
104 103
105 104 def err(seed, graph, bases, seq, output, expected):
106 105 if nerrs[0] == 0:
107 106 print('seed:', hex(seed)[:-1], file=sys.stderr)
108 107 if gerrs[0] == 0:
109 108 print('graph:', graph, file=sys.stderr)
110 109 print('* bases:', bases, file=sys.stderr)
111 110 print('* seq: ', seq, file=sys.stderr)
112 111 print('* output: ', output, file=sys.stderr)
113 112 print('* expected:', expected, file=sys.stderr)
114 113 nerrs[0] += 1
115 114 gerrs[0] += 1
116 115
117 for g in xrange(graphcount):
116 for g in range(graphcount):
118 117 graph = buildgraph(rng)
119 118 ancs = buildancestorsets(graph)
120 119 gerrs = [0]
121 for _ in xrange(testcount):
120 for _ in range(testcount):
122 121 # start from nullrev to include it as a possibility
123 122 graphnodes = range(nullrev, len(graph))
124 123 bases = samplerevs(graphnodes)
125 124
126 125 # fast algorithm
127 126 inc = ancestor.incrementalmissingancestors(graph.__getitem__, bases)
128 127 # reference slow algorithm
129 128 naiveinc = naiveincrementalmissingancestors(ancs, bases)
130 129 seq = []
131 for _ in xrange(inccount):
130 for _ in range(inccount):
132 131 if rng.random() < 0.2:
133 132 newbases = samplerevs(graphnodes)
134 133 seq.append(('addbases', newbases))
135 134 inc.addbases(newbases)
136 135 naiveinc.addbases(newbases)
137 136 if rng.random() < 0.4:
138 137 # larger set so that there are more revs to remove from
139 138 revs = samplerevs(graphnodes, mu=1.5)
140 139 seq.append(('removeancestorsfrom', revs))
141 140 hrevs = set(revs)
142 141 rrevs = set(revs)
143 142 inc.removeancestorsfrom(hrevs)
144 143 naiveinc.removeancestorsfrom(rrevs)
145 144 if hrevs != rrevs:
146 145 err(
147 146 seed,
148 147 graph,
149 148 bases,
150 149 seq,
151 150 sorted(hrevs),
152 151 sorted(rrevs),
153 152 )
154 153 else:
155 154 revs = samplerevs(graphnodes)
156 155 seq.append(('missingancestors', revs))
157 156 h = inc.missingancestors(revs)
158 157 r = naiveinc.missingancestors(revs)
159 158 if h != r:
160 159 err(seed, graph, bases, seq, h, r)
161 160
162 161
163 162 # graph is a dict of child->parent adjacency lists for this graph:
164 163 # o 13
165 164 # |
166 165 # | o 12
167 166 # | |
168 167 # | | o 11
169 168 # | | |\
170 169 # | | | | o 10
171 170 # | | | | |
172 171 # | o---+ | 9
173 172 # | | | | |
174 173 # o | | | | 8
175 174 # / / / /
176 175 # | | o | 7
177 176 # | | | |
178 177 # o---+ | 6
179 178 # / / /
180 179 # | | o 5
181 180 # | |/
182 181 # | o 4
183 182 # | |
184 183 # o | 3
185 184 # | |
186 185 # | o 2
187 186 # |/
188 187 # o 1
189 188 # |
190 189 # o 0
191 190
192 191 graph = {
193 192 0: [-1, -1],
194 193 1: [0, -1],
195 194 2: [1, -1],
196 195 3: [1, -1],
197 196 4: [2, -1],
198 197 5: [4, -1],
199 198 6: [4, -1],
200 199 7: [4, -1],
201 200 8: [-1, -1],
202 201 9: [6, 7],
203 202 10: [5, -1],
204 203 11: [3, 7],
205 204 12: [9, -1],
206 205 13: [8, -1],
207 206 }
208 207
209 208
210 209 def test_missingancestors_explicit():
211 210 """A few explicit cases, easier to check for catching errors in refactors.
212 211
213 212 The bigger graph at the end has been produced by the random generator
214 213 above, and we have some evidence that the other tests don't cover it.
215 214 """
216 215 for i, (bases, revs) in enumerate(
217 216 (
218 ({1, 2, 3, 4, 7}, set(xrange(10))),
217 ({1, 2, 3, 4, 7}, set(range(10))),
219 218 ({10}, set({11, 12, 13, 14})),
220 219 ({7}, set({1, 2, 3, 4, 5})),
221 220 )
222 221 ):
223 222 print("%% removeancestorsfrom(), example %d" % (i + 1))
224 223 missanc = ancestor.incrementalmissingancestors(graph.get, bases)
225 224 missanc.removeancestorsfrom(revs)
226 225 print("remaining (sorted): %s" % sorted(list(revs)))
227 226
228 227 for i, (bases, revs) in enumerate(
229 228 (
230 229 ({10}, {11}),
231 230 ({11}, {10}),
232 231 ({7}, {9, 11}),
233 232 )
234 233 ):
235 234 print("%% missingancestors(), example %d" % (i + 1))
236 235 missanc = ancestor.incrementalmissingancestors(graph.get, bases)
237 236 print("return %s" % missanc.missingancestors(revs))
238 237
239 238 print("% removeancestorsfrom(), bigger graph")
240 239 vecgraph = [
241 240 [-1, -1],
242 241 [0, -1],
243 242 [1, 0],
244 243 [2, 1],
245 244 [3, -1],
246 245 [4, -1],
247 246 [5, 1],
248 247 [2, -1],
249 248 [7, -1],
250 249 [8, -1],
251 250 [9, -1],
252 251 [10, 1],
253 252 [3, -1],
254 253 [12, -1],
255 254 [13, -1],
256 255 [14, -1],
257 256 [4, -1],
258 257 [16, -1],
259 258 [17, -1],
260 259 [18, -1],
261 260 [19, 11],
262 261 [20, -1],
263 262 [21, -1],
264 263 [22, -1],
265 264 [23, -1],
266 265 [2, -1],
267 266 [3, -1],
268 267 [26, 24],
269 268 [27, -1],
270 269 [28, -1],
271 270 [12, -1],
272 271 [1, -1],
273 272 [1, 9],
274 273 [32, -1],
275 274 [33, -1],
276 275 [34, 31],
277 276 [35, -1],
278 277 [36, 26],
279 278 [37, -1],
280 279 [38, -1],
281 280 [39, -1],
282 281 [40, -1],
283 282 [41, -1],
284 283 [42, 26],
285 284 [0, -1],
286 285 [44, -1],
287 286 [45, 4],
288 287 [40, -1],
289 288 [47, -1],
290 289 [36, 0],
291 290 [49, -1],
292 291 [-1, -1],
293 292 [51, -1],
294 293 [52, -1],
295 294 [53, -1],
296 295 [14, -1],
297 296 [55, -1],
298 297 [15, -1],
299 298 [23, -1],
300 299 [58, -1],
301 300 [59, -1],
302 301 [2, -1],
303 302 [61, 59],
304 303 [62, -1],
305 304 [63, -1],
306 305 [-1, -1],
307 306 [65, -1],
308 307 [66, -1],
309 308 [67, -1],
310 309 [68, -1],
311 310 [37, 28],
312 311 [69, 25],
313 312 [71, -1],
314 313 [72, -1],
315 314 [50, 2],
316 315 [74, -1],
317 316 [12, -1],
318 317 [18, -1],
319 318 [77, -1],
320 319 [78, -1],
321 320 [79, -1],
322 321 [43, 33],
323 322 [81, -1],
324 323 [82, -1],
325 324 [83, -1],
326 325 [84, 45],
327 326 [85, -1],
328 327 [86, -1],
329 328 [-1, -1],
330 329 [88, -1],
331 330 [-1, -1],
332 331 [76, 83],
333 332 [44, -1],
334 333 [92, -1],
335 334 [93, -1],
336 335 [9, -1],
337 336 [95, 67],
338 337 [96, -1],
339 338 [97, -1],
340 339 [-1, -1],
341 340 ]
342 341 problem_rev = 28
343 342 problem_base = 70
344 343 # problem_rev is a parent of problem_base, but a faulty implementation
345 344 # could forget to remove it.
346 345 bases = {60, 26, 70, 3, 96, 19, 98, 49, 97, 47, 1, 6}
347 346 if problem_rev not in vecgraph[problem_base] or problem_base not in bases:
348 347 print("Conditions have changed")
349 348 missanc = ancestor.incrementalmissingancestors(vecgraph.__getitem__, bases)
350 349 revs = {4, 12, 41, 28, 68, 38, 1, 30, 56, 44}
351 350 missanc.removeancestorsfrom(revs)
352 351 if 28 in revs:
353 352 print("Failed!")
354 353 else:
355 354 print("Ok")
356 355
357 356
358 357 def genlazyancestors(revs, stoprev=0, inclusive=False):
359 358 print(
360 359 (
361 360 "%% lazy ancestor set for %s, stoprev = %s, inclusive = %s"
362 361 % (revs, stoprev, inclusive)
363 362 )
364 363 )
365 364 return ancestor.lazyancestors(
366 365 graph.get, revs, stoprev=stoprev, inclusive=inclusive
367 366 )
368 367
369 368
370 369 def printlazyancestors(s, l):
371 370 print('membership: %r' % [n for n in l if n in s])
372 371 print('iteration: %r' % list(s))
373 372
374 373
375 374 def test_lazyancestors():
376 375 # Empty revs
377 376 s = genlazyancestors([])
378 377 printlazyancestors(s, [3, 0, -1])
379 378
380 379 # Standard example
381 380 s = genlazyancestors([11, 13])
382 381 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
383 382
384 383 # Standard with ancestry in the initial set (1 is ancestor of 3)
385 384 s = genlazyancestors([1, 3])
386 385 printlazyancestors(s, [1, -1, 0])
387 386
388 387 # Including revs
389 388 s = genlazyancestors([11, 13], inclusive=True)
390 389 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
391 390
392 391 # Test with stoprev
393 392 s = genlazyancestors([11, 13], stoprev=6)
394 393 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
395 394 s = genlazyancestors([11, 13], stoprev=6, inclusive=True)
396 395 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
397 396
398 397 # Test with stoprev >= min(initrevs)
399 398 s = genlazyancestors([11, 13], stoprev=11, inclusive=True)
400 399 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
401 400 s = genlazyancestors([11, 13], stoprev=12, inclusive=True)
402 401 printlazyancestors(s, [11, 13, 7, 9, 8, 3, 6, 4, 1, -1, 0])
403 402
404 403 # Contiguous chains: 5->4, 2->1 (where 1 is in seen set), 1->0
405 404 s = genlazyancestors([10, 1], inclusive=True)
406 405 printlazyancestors(s, [2, 10, 4, 5, -1, 0, 1])
407 406
408 407
409 408 # The C gca algorithm requires a real repo. These are textual descriptions of
410 409 # DAGs that have been known to be problematic, and, optionally, known pairs
411 410 # of revisions and their expected ancestor list.
412 411 dagtests = [
413 412 (b'+2*2*2/*3/2', {}),
414 413 (b'+3*3/*2*2/*4*4/*4/2*4/2*2', {}),
415 414 (b'+2*2*/2*4*/4*/3*2/4', {(6, 7): [3, 5]}),
416 415 ]
417 416
418 417
419 418 def test_gca():
420 419 u = uimod.ui.load()
421 420 for i, (dag, tests) in enumerate(dagtests):
422 421 repo = hg.repository(u, b'gca%d' % i, create=1)
423 422 cl = repo.changelog
424 423 if not util.safehasattr(cl.index, 'ancestors'):
425 424 # C version not available
426 425 return
427 426
428 427 debugcommands.debugbuilddag(u, repo, dag)
429 428 # Compare the results of the Python and C versions. This does not
430 429 # include choosing a winner when more than one gca exists -- we make
431 430 # sure both return exactly the same set of gcas.
432 431 # Also compare against expected results, if available.
433 432 for a in cl:
434 433 for b in cl:
435 434 cgcas = sorted(cl.index.ancestors(a, b))
436 435 pygcas = sorted(ancestor.ancestors(cl.parentrevs, a, b))
437 436 expected = None
438 437 if (a, b) in tests:
439 438 expected = tests[(a, b)]
440 439 if cgcas != pygcas or (expected and cgcas != expected):
441 440 print(
442 441 "test_gca: for dag %s, gcas for %d, %d:" % (dag, a, b)
443 442 )
444 443 print(" C returned: %s" % cgcas)
445 444 print(" Python returned: %s" % pygcas)
446 445 if expected:
447 446 print(" expected: %s" % expected)
448 447
449 448
450 449 def main():
451 450 seed = None
452 451 opts, args = getopt.getopt(sys.argv[1:], 's:', ['seed='])
453 452 for o, a in opts:
454 453 if o in ('-s', '--seed'):
455 454 seed = long(a, base=0) # accepts base 10 or 16 strings
456 455
457 456 if seed is None:
458 457 try:
459 458 seed = long(binascii.hexlify(os.urandom(16)), 16)
460 459 except AttributeError:
461 460 seed = long(time.time() * 1000)
462 461
463 462 rng = random.Random(seed)
464 463 test_missingancestors_explicit()
465 464 test_missingancestors(seed, rng)
466 465 test_lazyancestors()
467 466 test_gca()
468 467
469 468
470 469 if __name__ == '__main__':
471 470 main()
@@ -1,131 +1,127 b''
1 1 import glob
2 2 import os
3 3 import shutil
4 4 import stat
5 5 import tempfile
6 6 import unittest
7 7
8 8 from mercurial import (
9 pycompat,
10 9 util,
11 10 )
12 11
13 12 atomictempfile = util.atomictempfile
14 13
15 if pycompat.ispy3:
16 xrange = range
17
18 14
19 15 class testatomictempfile(unittest.TestCase):
20 16 def setUp(self):
21 17 self._testdir = tempfile.mkdtemp(b'atomictempfiletest')
22 18 self._filename = os.path.join(self._testdir, b'testfilename')
23 19
24 20 def tearDown(self):
25 21 shutil.rmtree(self._testdir, True)
26 22
27 23 def testsimple(self):
28 24 file = atomictempfile(self._filename)
29 25 self.assertFalse(os.path.isfile(self._filename))
30 26 tempfilename = file._tempname
31 27 self.assertTrue(
32 28 tempfilename
33 29 in glob.glob(os.path.join(self._testdir, b'.testfilename-*'))
34 30 )
35 31
36 32 file.write(b'argh\n')
37 33 file.close()
38 34
39 35 self.assertTrue(os.path.isfile(self._filename))
40 36 self.assertTrue(
41 37 tempfilename
42 38 not in glob.glob(os.path.join(self._testdir, b'.testfilename-*'))
43 39 )
44 40
45 41 # discard() removes the temp file without making the write permanent
46 42 def testdiscard(self):
47 43 file = atomictempfile(self._filename)
48 44 (dir, basename) = os.path.split(file._tempname)
49 45
50 46 file.write(b'yo\n')
51 47 file.discard()
52 48
53 49 self.assertFalse(os.path.isfile(self._filename))
54 50 self.assertTrue(basename not in os.listdir(b'.'))
55 51
56 52 # if a programmer screws up and passes bad args to atomictempfile, they
57 53 # get a plain ordinary TypeError, not infinite recursion
58 54 def testoops(self):
59 55 with self.assertRaises(TypeError):
60 56 atomictempfile()
61 57
62 58 # checkambig=True avoids ambiguity of timestamp
63 59 def testcheckambig(self):
64 60 def atomicwrite(checkambig):
65 61 f = atomictempfile(self._filename, checkambig=checkambig)
66 62 f.write(b'FOO')
67 63 f.close()
68 64
69 65 # try some times, because reproduction of ambiguity depends on
70 66 # "filesystem time"
71 for i in xrange(5):
67 for i in range(5):
72 68 atomicwrite(False)
73 69 oldstat = os.stat(self._filename)
74 70 if oldstat[stat.ST_CTIME] != oldstat[stat.ST_MTIME]:
75 71 # subsequent changing never causes ambiguity
76 72 continue
77 73
78 74 repetition = 3
79 75
80 76 # repeat atomic write with checkambig=True, to examine
81 77 # whether st_mtime is advanced multiple times as expected
82 for j in xrange(repetition):
78 for j in range(repetition):
83 79 atomicwrite(True)
84 80 newstat = os.stat(self._filename)
85 81 if oldstat[stat.ST_CTIME] != newstat[stat.ST_CTIME]:
86 82 # timestamp ambiguity was naturally avoided while repetition
87 83 continue
88 84
89 85 # st_mtime should be advanced "repetition" times, because
90 86 # all atomicwrite() occurred at same time (in sec)
91 87 oldtime = (oldstat[stat.ST_MTIME] + repetition) & 0x7FFFFFFF
92 88 self.assertTrue(newstat[stat.ST_MTIME] == oldtime)
93 89 # no more examination is needed, if assumption above is true
94 90 break
95 91 else:
96 92 # This platform seems too slow to examine anti-ambiguity
97 93 # of file timestamp (or test happened to be executed at
98 94 # bad timing). Exit silently in this case, because running
99 95 # on other faster platforms can detect problems
100 96 pass
101 97
102 98 def testread(self):
103 99 with open(self._filename, 'wb') as f:
104 100 f.write(b'foobar\n')
105 101 file = atomictempfile(self._filename, mode=b'rb')
106 102 self.assertTrue(file.read(), b'foobar\n')
107 103 file.discard()
108 104
109 105 def testcontextmanagersuccess(self):
110 106 """When the context closes, the file is closed"""
111 107 with atomictempfile(b'foo') as f:
112 108 self.assertFalse(os.path.isfile(b'foo'))
113 109 f.write(b'argh\n')
114 110 self.assertTrue(os.path.isfile(b'foo'))
115 111
116 112 def testcontextmanagerfailure(self):
117 113 """On exception, the file is discarded"""
118 114 try:
119 115 with atomictempfile(b'foo') as f:
120 116 self.assertFalse(os.path.isfile(b'foo'))
121 117 f.write(b'argh\n')
122 118 raise ValueError
123 119 except ValueError:
124 120 pass
125 121 self.assertFalse(os.path.isfile(b'foo'))
126 122
127 123
128 124 if __name__ == '__main__':
129 125 import silenttestrunner
130 126
131 127 silenttestrunner.main(__name__)
@@ -1,224 +1,221 b''
1 1 import os
2 2 import tempfile
3 3
4 4 from mercurial import (
5 5 pycompat,
6 6 util,
7 7 )
8 8
9 9 from hgext.fastannotate import error, revmap
10 10
11 if pycompat.ispy3:
12 xrange = range
13
14 11
15 12 def genhsh(i):
16 13 return pycompat.bytechr(i) + b'\0' * 19
17 14
18 15
19 16 def gettemppath():
20 17 fd, path = tempfile.mkstemp()
21 18 os.close(fd)
22 19 os.unlink(path)
23 20 return path
24 21
25 22
26 23 def ensure(condition):
27 24 if not condition:
28 25 raise RuntimeError('Unexpected')
29 26
30 27
31 28 def testbasicreadwrite():
32 29 path = gettemppath()
33 30
34 31 rm = revmap.revmap(path)
35 32 ensure(rm.maxrev == 0)
36 for i in xrange(5):
33 for i in range(5):
37 34 ensure(rm.rev2hsh(i) is None)
38 35 ensure(rm.hsh2rev(b'\0' * 20) is None)
39 36
40 37 paths = [
41 38 b'',
42 39 b'a',
43 40 None,
44 41 b'b',
45 42 b'b',
46 43 b'c',
47 44 b'c',
48 45 None,
49 46 b'a',
50 47 b'b',
51 48 b'a',
52 49 b'a',
53 50 ]
54 for i in xrange(1, 5):
51 for i in range(1, 5):
55 52 ensure(rm.append(genhsh(i), sidebranch=(i & 1), path=paths[i]) == i)
56 53
57 54 ensure(rm.maxrev == 4)
58 for i in xrange(1, 5):
55 for i in range(1, 5):
59 56 ensure(rm.hsh2rev(genhsh(i)) == i)
60 57 ensure(rm.rev2hsh(i) == genhsh(i))
61 58
62 59 # re-load and verify
63 60 rm.flush()
64 61 rm = revmap.revmap(path)
65 62 ensure(rm.maxrev == 4)
66 for i in xrange(1, 5):
63 for i in range(1, 5):
67 64 ensure(rm.hsh2rev(genhsh(i)) == i)
68 65 ensure(rm.rev2hsh(i) == genhsh(i))
69 66 ensure(bool(rm.rev2flag(i) & revmap.sidebranchflag) == bool(i & 1))
70 67
71 68 # append without calling save() explicitly
72 for i in xrange(5, 12):
69 for i in range(5, 12):
73 70 ensure(
74 71 rm.append(genhsh(i), sidebranch=(i & 1), path=paths[i], flush=True)
75 72 == i
76 73 )
77 74
78 75 # re-load and verify
79 76 rm = revmap.revmap(path)
80 77 ensure(rm.maxrev == 11)
81 for i in xrange(1, 12):
78 for i in range(1, 12):
82 79 ensure(rm.hsh2rev(genhsh(i)) == i)
83 80 ensure(rm.rev2hsh(i) == genhsh(i))
84 81 ensure(rm.rev2path(i) == paths[i] or paths[i - 1])
85 82 ensure(bool(rm.rev2flag(i) & revmap.sidebranchflag) == bool(i & 1))
86 83
87 84 os.unlink(path)
88 85
89 86 # missing keys
90 87 ensure(rm.rev2hsh(12) is None)
91 88 ensure(rm.rev2hsh(0) is None)
92 89 ensure(rm.rev2hsh(-1) is None)
93 90 ensure(rm.rev2flag(12) is None)
94 91 ensure(rm.rev2path(12) is None)
95 92 ensure(rm.hsh2rev(b'\1' * 20) is None)
96 93
97 94 # illformed hash (not 20 bytes)
98 95 try:
99 96 rm.append(b'\0')
100 97 ensure(False)
101 98 except Exception:
102 99 pass
103 100
104 101
105 102 def testcorruptformat():
106 103 path = gettemppath()
107 104
108 105 # incorrect header
109 106 with open(path, 'wb') as f:
110 107 f.write(b'NOT A VALID HEADER')
111 108 try:
112 109 revmap.revmap(path)
113 110 ensure(False)
114 111 except error.CorruptedFileError:
115 112 pass
116 113
117 114 # rewrite the file
118 115 os.unlink(path)
119 116 rm = revmap.revmap(path)
120 117 rm.append(genhsh(0), flush=True)
121 118
122 119 rm = revmap.revmap(path)
123 120 ensure(rm.maxrev == 1)
124 121
125 122 # corrupt the file by appending a byte
126 123 size = os.stat(path).st_size
127 124 with open(path, 'ab') as f:
128 125 f.write(b'\xff')
129 126 try:
130 127 revmap.revmap(path)
131 128 ensure(False)
132 129 except error.CorruptedFileError:
133 130 pass
134 131
135 132 # corrupt the file by removing the last byte
136 133 ensure(size > 0)
137 134 with open(path, 'wb') as f:
138 135 f.truncate(size - 1)
139 136 try:
140 137 revmap.revmap(path)
141 138 ensure(False)
142 139 except error.CorruptedFileError:
143 140 pass
144 141
145 142 os.unlink(path)
146 143
147 144
148 145 def testcopyfrom():
149 146 path = gettemppath()
150 147 rm = revmap.revmap(path)
151 for i in xrange(1, 10):
148 for i in range(1, 10):
152 149 ensure(
153 150 rm.append(genhsh(i), sidebranch=(i & 1), path=(b'%d' % (i // 3)))
154 151 == i
155 152 )
156 153 rm.flush()
157 154
158 155 # copy rm to rm2
159 156 rm2 = revmap.revmap()
160 157 rm2.copyfrom(rm)
161 158 path2 = gettemppath()
162 159 rm2.path = path2
163 160 rm2.flush()
164 161
165 162 # two files should be the same
166 163 ensure(len({util.readfile(p) for p in [path, path2]}) == 1)
167 164
168 165 os.unlink(path)
169 166 os.unlink(path2)
170 167
171 168
172 169 class fakefctx:
173 170 def __init__(self, node, path=None):
174 171 self._node = node
175 172 self._path = path
176 173
177 174 def node(self):
178 175 return self._node
179 176
180 177 def path(self):
181 178 return self._path
182 179
183 180
184 181 def testcontains():
185 182 path = gettemppath()
186 183
187 184 rm = revmap.revmap(path)
188 for i in xrange(1, 5):
185 for i in range(1, 5):
189 186 ensure(rm.append(genhsh(i), sidebranch=(i & 1)) == i)
190 187
191 for i in xrange(1, 5):
188 for i in range(1, 5):
192 189 ensure(((genhsh(i), None) in rm) == ((i & 1) == 0))
193 190 ensure((fakefctx(genhsh(i)) in rm) == ((i & 1) == 0))
194 for i in xrange(5, 10):
191 for i in range(5, 10):
195 192 ensure(fakefctx(genhsh(i)) not in rm)
196 193 ensure((genhsh(i), None) not in rm)
197 194
198 195 # "contains" checks paths
199 196 rm = revmap.revmap()
200 for i in xrange(1, 5):
197 for i in range(1, 5):
201 198 ensure(rm.append(genhsh(i), path=(b'%d' % (i // 2))) == i)
202 for i in xrange(1, 5):
199 for i in range(1, 5):
203 200 ensure(fakefctx(genhsh(i), path=(b'%d' % (i // 2))) in rm)
204 201 ensure(fakefctx(genhsh(i), path=b'a') not in rm)
205 202
206 203
207 204 def testlastnode():
208 205 path = gettemppath()
209 206 ensure(revmap.getlastnode(path) is None)
210 207 rm = revmap.revmap(path)
211 208 ensure(revmap.getlastnode(path) is None)
212 for i in xrange(1, 10):
209 for i in range(1, 10):
213 210 hsh = genhsh(i)
214 211 rm.append(hsh, path=(b'%d' % (i // 2)), flush=True)
215 212 ensure(revmap.getlastnode(path) == hsh)
216 213 rm2 = revmap.revmap(path)
217 214 ensure(rm2.rev2hsh(rm2.maxrev) == hsh)
218 215
219 216
220 217 testbasicreadwrite()
221 218 testcorruptformat()
222 219 testcopyfrom()
223 220 testcontains()
224 221 testlastnode()
@@ -1,285 +1,282 b''
1 1 import os
2 2 import stat
3 3 import subprocess
4 4 import sys
5 5
6 6 if subprocess.call(
7 7 [sys.executable, '%s/hghave' % os.environ['TESTDIR'], 'cacheable']
8 8 ):
9 9 sys.exit(80)
10 10
11 11 print_ = print
12 12
13 13
14 14 def print(*args, **kwargs):
15 15 """print() wrapper that flushes stdout buffers to avoid py3 buffer issues
16 16
17 17 We could also just write directly to sys.stdout.buffer the way the
18 18 ui object will, but this was easier for porting the test.
19 19 """
20 20 print_(*args, **kwargs)
21 21 sys.stdout.flush()
22 22
23 23
24 24 from mercurial import (
25 25 extensions,
26 26 hg,
27 27 localrepo,
28 28 pycompat,
29 29 ui as uimod,
30 30 util,
31 31 vfs as vfsmod,
32 32 )
33 33
34 if pycompat.ispy3:
35 xrange = range
36
37 34
38 35 class fakerepo:
39 36 def __init__(self):
40 37 self._filecache = {}
41 38
42 39 class fakevfs:
43 40 def join(self, p):
44 41 return p
45 42
46 43 vfs = fakevfs()
47 44
48 45 def unfiltered(self):
49 46 return self
50 47
51 48 def sjoin(self, p):
52 49 return p
53 50
54 51 @localrepo.repofilecache('x', 'y')
55 52 def cached(self):
56 53 print('creating')
57 54 return 'string from function'
58 55
59 56 def invalidate(self):
60 57 for k in self._filecache:
61 58 try:
62 59 delattr(self, pycompat.sysstr(k))
63 60 except AttributeError:
64 61 pass
65 62
66 63
67 64 def basic(repo):
68 65 print("* neither file exists")
69 66 # calls function
70 67 repo.cached
71 68
72 69 repo.invalidate()
73 70 print("* neither file still exists")
74 71 # uses cache
75 72 repo.cached
76 73
77 74 # create empty file
78 75 f = open('x', 'w')
79 76 f.close()
80 77 repo.invalidate()
81 78 print("* empty file x created")
82 79 # should recreate the object
83 80 repo.cached
84 81
85 82 f = open('x', 'w')
86 83 f.write('a')
87 84 f.close()
88 85 repo.invalidate()
89 86 print("* file x changed size")
90 87 # should recreate the object
91 88 repo.cached
92 89
93 90 repo.invalidate()
94 91 print("* nothing changed with either file")
95 92 # stats file again, reuses object
96 93 repo.cached
97 94
98 95 # atomic replace file, size doesn't change
99 96 # hopefully st_mtime doesn't change as well so this doesn't use the cache
100 97 # because of inode change
101 98 f = vfsmod.vfs(b'.')(b'x', b'w', atomictemp=True)
102 99 f.write(b'b')
103 100 f.close()
104 101
105 102 repo.invalidate()
106 103 print("* file x changed inode")
107 104 repo.cached
108 105
109 106 # create empty file y
110 107 f = open('y', 'w')
111 108 f.close()
112 109 repo.invalidate()
113 110 print("* empty file y created")
114 111 # should recreate the object
115 112 repo.cached
116 113
117 114 f = open('y', 'w')
118 115 f.write('A')
119 116 f.close()
120 117 repo.invalidate()
121 118 print("* file y changed size")
122 119 # should recreate the object
123 120 repo.cached
124 121
125 122 f = vfsmod.vfs(b'.')(b'y', b'w', atomictemp=True)
126 123 f.write(b'B')
127 124 f.close()
128 125
129 126 repo.invalidate()
130 127 print("* file y changed inode")
131 128 repo.cached
132 129
133 130 f = vfsmod.vfs(b'.')(b'x', b'w', atomictemp=True)
134 131 f.write(b'c')
135 132 f.close()
136 133 f = vfsmod.vfs(b'.')(b'y', b'w', atomictemp=True)
137 134 f.write(b'C')
138 135 f.close()
139 136
140 137 repo.invalidate()
141 138 print("* both files changed inode")
142 139 repo.cached
143 140
144 141
145 142 def fakeuncacheable():
146 143 def wrapcacheable(orig, *args, **kwargs):
147 144 return False
148 145
149 146 def wrapinit(orig, *args, **kwargs):
150 147 pass
151 148
152 149 originit = extensions.wrapfunction(util.cachestat, '__init__', wrapinit)
153 150 origcacheable = extensions.wrapfunction(
154 151 util.cachestat, 'cacheable', wrapcacheable
155 152 )
156 153
157 154 for fn in ['x', 'y']:
158 155 try:
159 156 os.remove(fn)
160 157 except OSError:
161 158 pass
162 159
163 160 basic(fakerepo())
164 161
165 162 util.cachestat.cacheable = origcacheable
166 163 util.cachestat.__init__ = originit
167 164
168 165
169 166 def test_filecache_synced():
170 167 # test old behavior that caused filecached properties to go out of sync
171 168 os.system('hg init && echo a >> a && hg ci -qAm.')
172 169 repo = hg.repository(uimod.ui.load())
173 170 # first rollback clears the filecache, but changelog to stays in __dict__
174 171 repo.rollback()
175 172 repo.commit(b'.')
176 173 # second rollback comes along and touches the changelog externally
177 174 # (file is moved)
178 175 repo.rollback()
179 176 # but since changelog isn't under the filecache control anymore, we don't
180 177 # see that it changed, and return the old changelog without reconstructing
181 178 # it
182 179 repo.commit(b'.')
183 180
184 181
185 182 def setbeforeget(repo):
186 183 os.remove('x')
187 184 os.remove('y')
188 185 repo.__class__.cached.set(repo, 'string set externally')
189 186 repo.invalidate()
190 187 print("* neither file exists")
191 188 print(repo.cached)
192 189 repo.invalidate()
193 190 f = open('x', 'w')
194 191 f.write('a')
195 192 f.close()
196 193 print("* file x created")
197 194 print(repo.cached)
198 195
199 196 repo.__class__.cached.set(repo, 'string 2 set externally')
200 197 repo.invalidate()
201 198 print("* string set externally again")
202 199 print(repo.cached)
203 200
204 201 repo.invalidate()
205 202 f = open('y', 'w')
206 203 f.write('b')
207 204 f.close()
208 205 print("* file y created")
209 206 print(repo.cached)
210 207
211 208
212 209 def antiambiguity():
213 210 filename = 'ambigcheck'
214 211
215 212 # try some times, because reproduction of ambiguity depends on
216 213 # "filesystem time"
217 for i in xrange(5):
214 for i in range(5):
218 215 fp = open(filename, 'w')
219 216 fp.write('FOO')
220 217 fp.close()
221 218
222 219 oldstat = os.stat(filename)
223 220 if oldstat[stat.ST_CTIME] != oldstat[stat.ST_MTIME]:
224 221 # subsequent changing never causes ambiguity
225 222 continue
226 223
227 224 repetition = 3
228 225
229 226 # repeat changing via checkambigatclosing, to examine whether
230 227 # st_mtime is advanced multiple times as expected
231 for i in xrange(repetition):
228 for i in range(repetition):
232 229 # explicit closing
233 230 fp = vfsmod.checkambigatclosing(open(filename, 'a'))
234 231 fp.write('FOO')
235 232 fp.close()
236 233
237 234 # implicit closing by "with" statement
238 235 with vfsmod.checkambigatclosing(open(filename, 'a')) as fp:
239 236 fp.write('BAR')
240 237
241 238 newstat = os.stat(filename)
242 239 if oldstat[stat.ST_CTIME] != newstat[stat.ST_CTIME]:
243 240 # timestamp ambiguity was naturally avoided while repetition
244 241 continue
245 242
246 243 # st_mtime should be advanced "repetition * 2" times, because
247 244 # all changes occurred at same time (in sec)
248 245 expected = (oldstat[stat.ST_MTIME] + repetition * 2) & 0x7FFFFFFF
249 246 if newstat[stat.ST_MTIME] != expected:
250 247 print(
251 248 "'newstat[stat.ST_MTIME] %s is not %s (as %s + %s * 2)"
252 249 % (
253 250 newstat[stat.ST_MTIME],
254 251 expected,
255 252 oldstat[stat.ST_MTIME],
256 253 repetition,
257 254 )
258 255 )
259 256
260 257 # no more examination is needed regardless of result
261 258 break
262 259 else:
263 260 # This platform seems too slow to examine anti-ambiguity
264 261 # of file timestamp (or test happened to be executed at
265 262 # bad timing). Exit silently in this case, because running
266 263 # on other faster platforms can detect problems
267 264 pass
268 265
269 266
270 267 print('basic:')
271 268 print()
272 269 basic(fakerepo())
273 270 print()
274 271 print('fakeuncacheable:')
275 272 print()
276 273 fakeuncacheable()
277 274 test_filecache_synced()
278 275 print()
279 276 print('setbeforeget:')
280 277 print()
281 278 setbeforeget(fakerepo())
282 279 print()
283 280 print('antiambiguity:')
284 281 print()
285 282 antiambiguity()
@@ -1,462 +1,460 b''
1 1 import binascii
2 2 import itertools
3 3 import silenttestrunner
4 4 import unittest
5 5 import zlib
6 6
7 7 from mercurial.node import sha1nodeconstants
8 8
9 9 from mercurial import (
10 10 manifest as manifestmod,
11 11 match as matchmod,
12 12 util,
13 13 )
14 14
15 15 EMTPY_MANIFEST = b''
16 16
17 17 HASH_1 = b'1' * 40
18 18 BIN_HASH_1 = binascii.unhexlify(HASH_1)
19 19 HASH_2 = b'f' * 40
20 20 BIN_HASH_2 = binascii.unhexlify(HASH_2)
21 21 HASH_3 = b'1234567890abcdef0987654321deadbeef0fcafe'
22 22 BIN_HASH_3 = binascii.unhexlify(HASH_3)
23 23 A_SHORT_MANIFEST = (
24 24 b'bar/baz/qux.py\0%(hash2)s%(flag2)s\n' b'foo\0%(hash1)s%(flag1)s\n'
25 25 ) % {
26 26 b'hash1': HASH_1,
27 27 b'flag1': b'',
28 28 b'hash2': HASH_2,
29 29 b'flag2': b'l',
30 30 }
31 31
32 32 A_DEEPER_MANIFEST = (
33 33 b'a/b/c/bar.py\0%(hash3)s%(flag1)s\n'
34 34 b'a/b/c/bar.txt\0%(hash1)s%(flag1)s\n'
35 35 b'a/b/c/foo.py\0%(hash3)s%(flag1)s\n'
36 36 b'a/b/c/foo.txt\0%(hash2)s%(flag2)s\n'
37 37 b'a/b/d/baz.py\0%(hash3)s%(flag1)s\n'
38 38 b'a/b/d/qux.py\0%(hash1)s%(flag2)s\n'
39 39 b'a/b/d/ten.txt\0%(hash3)s%(flag2)s\n'
40 40 b'a/b/dog.py\0%(hash3)s%(flag1)s\n'
41 41 b'a/b/fish.py\0%(hash2)s%(flag1)s\n'
42 42 b'a/c/london.py\0%(hash3)s%(flag2)s\n'
43 43 b'a/c/paper.txt\0%(hash2)s%(flag2)s\n'
44 44 b'a/c/paris.py\0%(hash2)s%(flag1)s\n'
45 45 b'a/d/apple.py\0%(hash3)s%(flag1)s\n'
46 46 b'a/d/pizza.py\0%(hash3)s%(flag2)s\n'
47 47 b'a/green.py\0%(hash1)s%(flag2)s\n'
48 48 b'a/purple.py\0%(hash2)s%(flag1)s\n'
49 49 b'app.py\0%(hash3)s%(flag1)s\n'
50 50 b'readme.txt\0%(hash2)s%(flag1)s\n'
51 51 ) % {
52 52 b'hash1': HASH_1,
53 53 b'flag1': b'',
54 54 b'hash2': HASH_2,
55 55 b'flag2': b'l',
56 56 b'hash3': HASH_3,
57 57 }
58 58
59 59 HUGE_MANIFEST_ENTRIES = 200001
60 60
61 61 izip = getattr(itertools, 'izip', zip)
62 if 'xrange' not in globals():
63 xrange = range
64 62
65 63 A_HUGE_MANIFEST = b''.join(
66 64 sorted(
67 65 b'file%d\0%s%s\n' % (i, h, f)
68 66 for i, h, f in izip(
69 xrange(200001),
67 range(200001),
70 68 itertools.cycle((HASH_1, HASH_2)),
71 69 itertools.cycle((b'', b'x', b'l')),
72 70 )
73 71 )
74 72 )
75 73
76 74
77 75 class basemanifesttests:
78 76 def parsemanifest(self, text):
79 77 raise NotImplementedError('parsemanifest not implemented by test case')
80 78
81 79 def testEmptyManifest(self):
82 80 m = self.parsemanifest(20, EMTPY_MANIFEST)
83 81 self.assertEqual(0, len(m))
84 82 self.assertEqual([], list(m))
85 83
86 84 def testManifest(self):
87 85 m = self.parsemanifest(20, A_SHORT_MANIFEST)
88 86 self.assertEqual([b'bar/baz/qux.py', b'foo'], list(m))
89 87 self.assertEqual(BIN_HASH_2, m[b'bar/baz/qux.py'])
90 88 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
91 89 self.assertEqual(BIN_HASH_1, m[b'foo'])
92 90 self.assertEqual(b'', m.flags(b'foo'))
93 91 with self.assertRaises(KeyError):
94 92 m[b'wat']
95 93
96 94 def testSetItem(self):
97 95 want = BIN_HASH_1
98 96
99 97 m = self.parsemanifest(20, EMTPY_MANIFEST)
100 98 m[b'a'] = want
101 99 self.assertIn(b'a', m)
102 100 self.assertEqual(want, m[b'a'])
103 101 self.assertEqual(b'a\0' + HASH_1 + b'\n', m.text())
104 102
105 103 m = self.parsemanifest(20, A_SHORT_MANIFEST)
106 104 m[b'a'] = want
107 105 self.assertEqual(want, m[b'a'])
108 106 self.assertEqual(b'a\0' + HASH_1 + b'\n' + A_SHORT_MANIFEST, m.text())
109 107
110 108 def testSetFlag(self):
111 109 want = b'x'
112 110
113 111 m = self.parsemanifest(20, EMTPY_MANIFEST)
114 112 # first add a file; a file-less flag makes no sense
115 113 m[b'a'] = BIN_HASH_1
116 114 m.setflag(b'a', want)
117 115 self.assertEqual(want, m.flags(b'a'))
118 116 self.assertEqual(b'a\0' + HASH_1 + want + b'\n', m.text())
119 117
120 118 m = self.parsemanifest(20, A_SHORT_MANIFEST)
121 119 # first add a file; a file-less flag makes no sense
122 120 m[b'a'] = BIN_HASH_1
123 121 m.setflag(b'a', want)
124 122 self.assertEqual(want, m.flags(b'a'))
125 123 self.assertEqual(
126 124 b'a\0' + HASH_1 + want + b'\n' + A_SHORT_MANIFEST, m.text()
127 125 )
128 126
129 127 def testCopy(self):
130 128 m = self.parsemanifest(20, A_SHORT_MANIFEST)
131 129 m[b'a'] = BIN_HASH_1
132 130 m2 = m.copy()
133 131 del m
134 132 del m2 # make sure we don't double free() anything
135 133
136 134 def testCompaction(self):
137 135 unhex = binascii.unhexlify
138 136 h1, h2 = unhex(HASH_1), unhex(HASH_2)
139 137 m = self.parsemanifest(20, A_SHORT_MANIFEST)
140 138 m[b'alpha'] = h1
141 139 m[b'beta'] = h2
142 140 del m[b'foo']
143 141 want = b'alpha\0%s\nbar/baz/qux.py\0%sl\nbeta\0%s\n' % (
144 142 HASH_1,
145 143 HASH_2,
146 144 HASH_2,
147 145 )
148 146 self.assertEqual(want, m.text())
149 147 self.assertEqual(3, len(m))
150 148 self.assertEqual([b'alpha', b'bar/baz/qux.py', b'beta'], list(m))
151 149 self.assertEqual(h1, m[b'alpha'])
152 150 self.assertEqual(h2, m[b'bar/baz/qux.py'])
153 151 self.assertEqual(h2, m[b'beta'])
154 152 self.assertEqual(b'', m.flags(b'alpha'))
155 153 self.assertEqual(b'l', m.flags(b'bar/baz/qux.py'))
156 154 self.assertEqual(b'', m.flags(b'beta'))
157 155 with self.assertRaises(KeyError):
158 156 m[b'foo']
159 157
160 158 def testMatchException(self):
161 159 m = self.parsemanifest(20, A_SHORT_MANIFEST)
162 160 match = matchmod.match(util.localpath(b'/repo'), b'', [b're:.*'])
163 161
164 162 def filt(path):
165 163 if path == b'foo':
166 164 assert False
167 165 return True
168 166
169 167 match.matchfn = filt
170 168 with self.assertRaises(AssertionError):
171 169 m._matches(match)
172 170
173 171 def testRemoveItem(self):
174 172 m = self.parsemanifest(20, A_SHORT_MANIFEST)
175 173 del m[b'foo']
176 174 with self.assertRaises(KeyError):
177 175 m[b'foo']
178 176 self.assertEqual(1, len(m))
179 177 self.assertEqual(1, len(list(m)))
180 178 # now restore and make sure everything works right
181 179 m[b'foo'] = b'a' * 20
182 180 self.assertEqual(2, len(m))
183 181 self.assertEqual(2, len(list(m)))
184 182
185 183 def testManifestDiff(self):
186 184 MISSING = (None, b'')
187 185 addl = b'z-only-in-left\0' + HASH_1 + b'\n'
188 186 addr = b'z-only-in-right\0' + HASH_2 + b'x\n'
189 187 left = self.parsemanifest(
190 188 20, A_SHORT_MANIFEST.replace(HASH_1, HASH_3 + b'x') + addl
191 189 )
192 190 right = self.parsemanifest(20, A_SHORT_MANIFEST + addr)
193 191 want = {
194 192 b'foo': ((BIN_HASH_3, b'x'), (BIN_HASH_1, b'')),
195 193 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
196 194 b'z-only-in-right': (MISSING, (BIN_HASH_2, b'x')),
197 195 }
198 196 self.assertEqual(want, left.diff(right))
199 197
200 198 want = {
201 199 b'bar/baz/qux.py': (MISSING, (BIN_HASH_2, b'l')),
202 200 b'foo': (MISSING, (BIN_HASH_3, b'x')),
203 201 b'z-only-in-left': (MISSING, (BIN_HASH_1, b'')),
204 202 }
205 203 self.assertEqual(
206 204 want, self.parsemanifest(20, EMTPY_MANIFEST).diff(left)
207 205 )
208 206
209 207 want = {
210 208 b'bar/baz/qux.py': ((BIN_HASH_2, b'l'), MISSING),
211 209 b'foo': ((BIN_HASH_3, b'x'), MISSING),
212 210 b'z-only-in-left': ((BIN_HASH_1, b''), MISSING),
213 211 }
214 212 self.assertEqual(
215 213 want, left.diff(self.parsemanifest(20, EMTPY_MANIFEST))
216 214 )
217 215 copy = right.copy()
218 216 del copy[b'z-only-in-right']
219 217 del right[b'foo']
220 218 want = {
221 219 b'foo': (MISSING, (BIN_HASH_1, b'')),
222 220 b'z-only-in-right': ((BIN_HASH_2, b'x'), MISSING),
223 221 }
224 222 self.assertEqual(want, right.diff(copy))
225 223
226 224 short = self.parsemanifest(20, A_SHORT_MANIFEST)
227 225 pruned = short.copy()
228 226 del pruned[b'foo']
229 227 want = {
230 228 b'foo': ((BIN_HASH_1, b''), MISSING),
231 229 }
232 230 self.assertEqual(want, short.diff(pruned))
233 231 want = {
234 232 b'foo': (MISSING, (BIN_HASH_1, b'')),
235 233 }
236 234 self.assertEqual(want, pruned.diff(short))
237 235 want = {
238 236 b'bar/baz/qux.py': None,
239 237 b'foo': (MISSING, (BIN_HASH_1, b'')),
240 238 }
241 239 self.assertEqual(want, pruned.diff(short, clean=True))
242 240
243 241 def testReversedLines(self):
244 242 backwards = b''.join(
245 243 l + b'\n' for l in reversed(A_SHORT_MANIFEST.split(b'\n')) if l
246 244 )
247 245 try:
248 246 self.parsemanifest(20, backwards)
249 247 self.fail('Should have raised ValueError')
250 248 except ValueError as v:
251 249 self.assertIn('Manifest lines not in sorted order.', str(v))
252 250
253 251 def testNoTerminalNewline(self):
254 252 try:
255 253 self.parsemanifest(20, A_SHORT_MANIFEST + b'wat')
256 254 self.fail('Should have raised ValueError')
257 255 except ValueError as v:
258 256 self.assertIn('Manifest did not end in a newline.', str(v))
259 257
260 258 def testNoNewLineAtAll(self):
261 259 try:
262 260 self.parsemanifest(20, b'wat')
263 261 self.fail('Should have raised ValueError')
264 262 except ValueError as v:
265 263 self.assertIn('Manifest did not end in a newline.', str(v))
266 264
267 265 def testHugeManifest(self):
268 266 m = self.parsemanifest(20, A_HUGE_MANIFEST)
269 267 self.assertEqual(HUGE_MANIFEST_ENTRIES, len(m))
270 268 self.assertEqual(len(m), len(list(m)))
271 269
272 270 def testMatchesMetadata(self):
273 271 """Tests matches() for a few specific files to make sure that both
274 272 the set of files as well as their flags and nodeids are correct in
275 273 the resulting manifest."""
276 274 m = self.parsemanifest(20, A_HUGE_MANIFEST)
277 275
278 276 match = matchmod.exact([b'file1', b'file200', b'file300'])
279 277 m2 = m._matches(match)
280 278
281 279 w = (b'file1\0%sx\n' b'file200\0%sl\n' b'file300\0%s\n') % (
282 280 HASH_2,
283 281 HASH_1,
284 282 HASH_1,
285 283 )
286 284 self.assertEqual(w, m2.text())
287 285
288 286 def testMatchesNonexistentFile(self):
289 287 """Tests matches() for a small set of specific files, including one
290 288 nonexistent file to make sure in only matches against existing files.
291 289 """
292 290 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
293 291
294 292 match = matchmod.exact(
295 293 [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt', b'nonexistent']
296 294 )
297 295 m2 = m._matches(match)
298 296
299 297 self.assertEqual(
300 298 [b'a/b/c/bar.txt', b'a/b/d/qux.py', b'readme.txt'], m2.keys()
301 299 )
302 300
303 301 def testMatchesNonexistentDirectory(self):
304 302 """Tests matches() for a relpath match on a directory that doesn't
305 303 actually exist."""
306 304 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
307 305
308 306 match = matchmod.match(
309 307 util.localpath(b'/repo'), b'', [b'a/f'], default=b'relpath'
310 308 )
311 309 m2 = m._matches(match)
312 310
313 311 self.assertEqual([], m2.keys())
314 312
315 313 def testMatchesExactLarge(self):
316 314 """Tests matches() for files matching a large list of exact files."""
317 315 m = self.parsemanifest(20, A_HUGE_MANIFEST)
318 316
319 317 flist = m.keys()[80:300]
320 318 match = matchmod.exact(flist)
321 319 m2 = m._matches(match)
322 320
323 321 self.assertEqual(flist, m2.keys())
324 322
325 323 def testMatchesFull(self):
326 324 '''Tests matches() for what should be a full match.'''
327 325 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
328 326
329 327 match = matchmod.match(util.localpath(b'/repo'), b'', [b''])
330 328 m2 = m._matches(match)
331 329
332 330 self.assertEqual(m.keys(), m2.keys())
333 331
334 332 def testMatchesDirectory(self):
335 333 """Tests matches() on a relpath match on a directory, which should
336 334 match against all files within said directory."""
337 335 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
338 336
339 337 match = matchmod.match(
340 338 util.localpath(b'/repo'), b'', [b'a/b'], default=b'relpath'
341 339 )
342 340 m2 = m._matches(match)
343 341
344 342 self.assertEqual(
345 343 [
346 344 b'a/b/c/bar.py',
347 345 b'a/b/c/bar.txt',
348 346 b'a/b/c/foo.py',
349 347 b'a/b/c/foo.txt',
350 348 b'a/b/d/baz.py',
351 349 b'a/b/d/qux.py',
352 350 b'a/b/d/ten.txt',
353 351 b'a/b/dog.py',
354 352 b'a/b/fish.py',
355 353 ],
356 354 m2.keys(),
357 355 )
358 356
359 357 def testMatchesExactPath(self):
360 358 """Tests matches() on an exact match on a directory, which should
361 359 result in an empty manifest because you can't perform an exact match
362 360 against a directory."""
363 361 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
364 362
365 363 match = matchmod.exact([b'a/b'])
366 364 m2 = m._matches(match)
367 365
368 366 self.assertEqual([], m2.keys())
369 367
370 368 def testMatchesCwd(self):
371 369 """Tests matches() on a relpath match with the current directory ('.')
372 370 when not in the root directory."""
373 371 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
374 372
375 373 match = matchmod.match(
376 374 util.localpath(b'/repo'), b'a/b', [b'.'], default=b'relpath'
377 375 )
378 376 m2 = m._matches(match)
379 377
380 378 self.assertEqual(
381 379 [
382 380 b'a/b/c/bar.py',
383 381 b'a/b/c/bar.txt',
384 382 b'a/b/c/foo.py',
385 383 b'a/b/c/foo.txt',
386 384 b'a/b/d/baz.py',
387 385 b'a/b/d/qux.py',
388 386 b'a/b/d/ten.txt',
389 387 b'a/b/dog.py',
390 388 b'a/b/fish.py',
391 389 ],
392 390 m2.keys(),
393 391 )
394 392
395 393 def testMatchesWithPattern(self):
396 394 """Tests matches() for files matching a pattern that reside
397 395 deeper than the specified directory."""
398 396 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
399 397
400 398 match = matchmod.match(util.localpath(b'/repo'), b'', [b'a/b/*/*.txt'])
401 399 m2 = m._matches(match)
402 400
403 401 self.assertEqual(
404 402 [b'a/b/c/bar.txt', b'a/b/c/foo.txt', b'a/b/d/ten.txt'], m2.keys()
405 403 )
406 404
407 405
408 406 class testmanifestdict(unittest.TestCase, basemanifesttests):
409 407 def parsemanifest(self, nodelen, text):
410 408 return manifestmod.manifestdict(nodelen, text)
411 409
412 410 def testManifestLongHashes(self):
413 411 m = self.parsemanifest(32, b'a\0' + b'f' * 64 + b'\n')
414 412 self.assertEqual(binascii.unhexlify(b'f' * 64), m[b'a'])
415 413
416 414 def testObviouslyBogusManifest(self):
417 415 # This is a 163k manifest that came from oss-fuzz. It was a
418 416 # timeout there, but when run normally it doesn't seem to
419 417 # present any particular slowness.
420 418 data = zlib.decompress(
421 419 b'x\x9c\xed\xce;\n\x83\x00\x10\x04\xd0\x8deNa\x93~\xf1\x03\xc9q\xf4'
422 420 b'\x14\xeaU\xbdB\xda\xd4\xe6Cj\xc1FA\xde+\x86\xe9f\xa2\xfci\xbb\xfb'
423 421 b'\xa3\xef\xea\xba\xca\x7fk\x86q\x9a\xc6\xc8\xcc&\xb3\xcf\xf8\xb8|#'
424 422 b'\x8a9\x00\xd8\xe6v\xf4\x01N\xe1\n\x00\x00\x00\x00\x00\x00\x00\x00'
425 423 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
426 424 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
427 425 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
428 426 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
429 427 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
430 428 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
431 429 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
432 430 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
433 431 b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
434 432 b'\x00\x00\xc0\x8aey\x1d}\x01\xd8\xe0\xb9\xf3\xde\x1b\xcf\x17'
435 433 b'\xac\xbe'
436 434 )
437 435 with self.assertRaises(ValueError):
438 436 self.parsemanifest(20, data)
439 437
440 438
441 439 class testtreemanifest(unittest.TestCase, basemanifesttests):
442 440 def parsemanifest(self, nodelen, text):
443 441 return manifestmod.treemanifest(sha1nodeconstants, b'', text)
444 442
445 443 def testWalkSubtrees(self):
446 444 m = self.parsemanifest(20, A_DEEPER_MANIFEST)
447 445
448 446 dirs = [s._dir for s in m.walksubtrees()]
449 447 self.assertEqual(
450 448 sorted(
451 449 [b'', b'a/', b'a/c/', b'a/d/', b'a/b/', b'a/b/c/', b'a/b/d/']
452 450 ),
453 451 sorted(dirs),
454 452 )
455 453
456 454 match = matchmod.match(util.localpath(b'/repo'), b'', [b'path:a/b/'])
457 455 dirs = [s._dir for s in m.walksubtrees(matcher=match)]
458 456 self.assertEqual(sorted([b'a/b/', b'a/b/c/', b'a/b/d/']), sorted(dirs))
459 457
460 458
461 459 if __name__ == '__main__':
462 460 silenttestrunner.main(__name__)
@@ -1,283 +1,278 b''
1 1 # This is a randomized test that generates different pathnames every
2 2 # time it is invoked, and tests the encoding of those pathnames.
3 3 #
4 4 # It uses a simple probabilistic model to generate valid pathnames
5 5 # that have proven likely to expose bugs and divergent behavior in
6 6 # different encoding implementations.
7 7
8 8
9 9 import binascii
10 10 import collections
11 11 import itertools
12 12 import math
13 13 import os
14 14 import random
15 15 import sys
16 16 import time
17 17 from mercurial import (
18 18 pycompat,
19 19 store,
20 20 )
21 21
22 try:
23 xrange
24 except NameError:
25 xrange = range
26
27 22 validchars = set(map(pycompat.bytechr, range(0, 256)))
28 23 alphanum = range(ord('A'), ord('Z'))
29 24
30 25 for c in (b'\0', b'/'):
31 26 validchars.remove(c)
32 27
33 28 winreserved = (
34 29 b'aux con prn nul'.split()
35 + [b'com%d' % i for i in xrange(1, 10)]
36 + [b'lpt%d' % i for i in xrange(1, 10)]
30 + [b'com%d' % i for i in range(1, 10)]
31 + [b'lpt%d' % i for i in range(1, 10)]
37 32 )
38 33
39 34
40 35 def casecombinations(names):
41 36 '''Build all case-diddled combinations of names.'''
42 37
43 38 combos = set()
44 39
45 40 for r in names:
46 for i in xrange(len(r) + 1):
47 for c in itertools.combinations(xrange(len(r)), i):
41 for i in range(len(r) + 1):
42 for c in itertools.combinations(range(len(r)), i):
48 43 d = r
49 44 for j in c:
50 45 d = b''.join((d[:j], d[j : j + 1].upper(), d[j + 1 :]))
51 46 combos.add(d)
52 47 return sorted(combos)
53 48
54 49
55 50 def buildprobtable(fp, cmd='hg manifest tip'):
56 51 """Construct and print a table of probabilities for path name
57 52 components. The numbers are percentages."""
58 53
59 54 counts = collections.defaultdict(lambda: 0)
60 55 for line in os.popen(cmd).read().splitlines():
61 56 if line[-2:] in ('.i', '.d'):
62 57 line = line[:-2]
63 58 if line.startswith('data/'):
64 59 line = line[5:]
65 60 for c in line:
66 61 counts[c] += 1
67 62 for c in '\r/\n':
68 63 counts.pop(c, None)
69 64 t = sum(counts.values()) / 100.0
70 65 fp.write('probtable = (')
71 66 for i, (k, v) in enumerate(
72 67 sorted(counts.items(), key=lambda x: x[1], reverse=True)
73 68 ):
74 69 if (i % 5) == 0:
75 70 fp.write('\n ')
76 71 vt = v / t
77 72 if vt < 0.0005:
78 73 break
79 74 fp.write('(%r, %.03f), ' % (k, vt))
80 75 fp.write('\n )\n')
81 76
82 77
83 78 # A table of character frequencies (as percentages), gleaned by
84 79 # looking at filelog names from a real-world, very large repo.
85 80
86 81 probtable = (
87 82 (b't', 9.828),
88 83 (b'e', 9.042),
89 84 (b's', 8.011),
90 85 (b'a', 6.801),
91 86 (b'i', 6.618),
92 87 (b'g', 5.053),
93 88 (b'r', 5.030),
94 89 (b'o', 4.887),
95 90 (b'p', 4.363),
96 91 (b'n', 4.258),
97 92 (b'l', 3.830),
98 93 (b'h', 3.693),
99 94 (b'_', 3.659),
100 95 (b'.', 3.377),
101 96 (b'm', 3.194),
102 97 (b'u', 2.364),
103 98 (b'd', 2.296),
104 99 (b'c', 2.163),
105 100 (b'b', 1.739),
106 101 (b'f', 1.625),
107 102 (b'6', 0.666),
108 103 (b'j', 0.610),
109 104 (b'y', 0.554),
110 105 (b'x', 0.487),
111 106 (b'w', 0.477),
112 107 (b'k', 0.476),
113 108 (b'v', 0.473),
114 109 (b'3', 0.336),
115 110 (b'1', 0.335),
116 111 (b'2', 0.326),
117 112 (b'4', 0.310),
118 113 (b'5', 0.305),
119 114 (b'9', 0.302),
120 115 (b'8', 0.300),
121 116 (b'7', 0.299),
122 117 (b'q', 0.298),
123 118 (b'0', 0.250),
124 119 (b'z', 0.223),
125 120 (b'-', 0.118),
126 121 (b'C', 0.095),
127 122 (b'T', 0.087),
128 123 (b'F', 0.085),
129 124 (b'B', 0.077),
130 125 (b'S', 0.076),
131 126 (b'P', 0.076),
132 127 (b'L', 0.059),
133 128 (b'A', 0.058),
134 129 (b'N', 0.051),
135 130 (b'D', 0.049),
136 131 (b'M', 0.046),
137 132 (b'E', 0.039),
138 133 (b'I', 0.035),
139 134 (b'R', 0.035),
140 135 (b'G', 0.028),
141 136 (b'U', 0.026),
142 137 (b'W', 0.025),
143 138 (b'O', 0.017),
144 139 (b'V', 0.015),
145 140 (b'H', 0.013),
146 141 (b'Q', 0.011),
147 142 (b'J', 0.007),
148 143 (b'K', 0.005),
149 144 (b'+', 0.004),
150 145 (b'X', 0.003),
151 146 (b'Y', 0.001),
152 147 )
153 148
154 149 for c, _ in probtable:
155 150 validchars.remove(c)
156 151 validchars = list(validchars)
157 152
158 153
159 154 def pickfrom(rng, table):
160 155 c = 0
161 156 r = rng.random() * sum(i[1] for i in table)
162 157 for i, p in table:
163 158 c += p
164 159 if c >= r:
165 160 return i
166 161
167 162
168 163 reservedcombos = casecombinations(winreserved)
169 164
170 165 # The first component of a name following a slash.
171 166
172 167 firsttable = (
173 168 (lambda rng: pickfrom(rng, probtable), 90),
174 169 (lambda rng: rng.choice(validchars), 5),
175 170 (lambda rng: rng.choice(reservedcombos), 5),
176 171 )
177 172
178 173 # Components of a name following the first.
179 174
180 175 resttable = firsttable[:-1]
181 176
182 177 # Special suffixes.
183 178
184 179 internalsuffixcombos = casecombinations(b'.hg .i .d'.split())
185 180
186 181 # The last component of a path, before a slash or at the end of a name.
187 182
188 183 lasttable = resttable + (
189 184 (lambda rng: b'', 95),
190 185 (lambda rng: rng.choice(internalsuffixcombos), 5),
191 186 )
192 187
193 188
194 189 def makepart(rng, k):
195 190 '''Construct a part of a pathname, without slashes.'''
196 191
197 192 p = pickfrom(rng, firsttable)(rng)
198 193 l = len(p)
199 194 ps = [p]
200 195 maxl = rng.randint(1, k)
201 196 while l < maxl:
202 197 p = pickfrom(rng, resttable)(rng)
203 198 l += len(p)
204 199 ps.append(p)
205 200 ps.append(pickfrom(rng, lasttable)(rng))
206 201 return b''.join(ps)
207 202
208 203
209 204 def makepath(rng, j, k):
210 205 '''Construct a complete pathname.'''
211 206
212 207 return (
213 208 b'data/'
214 + b'/'.join(makepart(rng, k) for _ in xrange(j))
209 + b'/'.join(makepart(rng, k) for _ in range(j))
215 210 + rng.choice([b'.d', b'.i'])
216 211 )
217 212
218 213
219 214 def genpath(rng, count):
220 215 '''Generate random pathnames with gradually increasing lengths.'''
221 216
222 217 mink, maxk = 1, 4096
223 218
224 219 def steps():
225 for i in xrange(count):
220 for i in range(count):
226 221 yield mink + int(round(math.sqrt((maxk - mink) * float(i) / count)))
227 222
228 223 for k in steps():
229 224 x = rng.randint(1, k)
230 225 y = rng.randint(1, k)
231 226 yield makepath(rng, x, y)
232 227
233 228
234 229 def runtests(rng, seed, count):
235 230 nerrs = 0
236 231 for p in genpath(rng, count):
237 232 h = store._pathencode(p) # uses C implementation, if available
238 233 r = store._hybridencode(p, True) # reference implementation in Python
239 234 if h != r:
240 235 if nerrs == 0:
241 236 print('seed:', hex(seed)[:-1], file=sys.stderr)
242 237 print("\np: '%s'" % p.encode("string_escape"), file=sys.stderr)
243 238 print("h: '%s'" % h.encode("string_escape"), file=sys.stderr)
244 239 print("r: '%s'" % r.encode("string_escape"), file=sys.stderr)
245 240 nerrs += 1
246 241 return nerrs
247 242
248 243
249 244 def main():
250 245 import getopt
251 246
252 247 # Empirically observed to take about a second to run
253 248 count = 100
254 249 seed = None
255 250 opts, args = getopt.getopt(
256 251 sys.argv[1:], 'c:s:', ['build', 'count=', 'seed=']
257 252 )
258 253 for o, a in opts:
259 254 if o in ('-c', '--count'):
260 255 count = int(a)
261 256 elif o in ('-s', '--seed'):
262 257 seed = int(a, base=0) # accepts base 10 or 16 strings
263 258 elif o == '--build':
264 259 buildprobtable(
265 260 sys.stdout,
266 261 'find .hg/store/data -type f && '
267 262 'cat .hg/store/fncache 2>/dev/null',
268 263 )
269 264 sys.exit(0)
270 265
271 266 if seed is None:
272 267 try:
273 268 seed = int(binascii.hexlify(os.urandom(16)), 16)
274 269 except AttributeError:
275 270 seed = int(time.time() * 1000)
276 271
277 272 rng = random.Random(seed)
278 273 if runtests(rng, seed, count):
279 274 sys.exit(1)
280 275
281 276
282 277 if __name__ == '__main__':
283 278 main()
@@ -1,125 +1,123 b''
1 1 #require no-windows
2 2
3 3 $ . "$TESTDIR/remotefilelog-library.sh"
4 4
5 5 $ hg init repo
6 6 $ cd repo
7 7 $ cat >> .hg/hgrc <<EOF
8 8 > [remotefilelog]
9 9 > server=True
10 10 > EOF
11 11 $ echo x > x
12 12 $ echo y > y
13 13 $ echo z > z
14 14 $ hg commit -qAm xy
15 15 $ cd ..
16 16
17 17 $ cat > cacheprocess-logger.py <<EOF
18 18 > import os
19 19 > import shutil
20 20 > import sys
21 > if sys.version_info[0] > 2:
22 > xrange = range
23 21 > f = open('$TESTTMP/cachelog.log', 'w')
24 22 > srccache = os.path.join('$TESTTMP', 'oldhgcache')
25 23 > def log(message):
26 24 > f.write(message)
27 25 > f.flush()
28 26 > destcache = sys.argv[-1]
29 27 > try:
30 28 > while True:
31 29 > cmd = sys.stdin.readline().strip()
32 30 > log('got command %r\n' % cmd)
33 31 > if cmd == 'exit':
34 32 > sys.exit(0)
35 33 > elif cmd == 'get':
36 34 > count = int(sys.stdin.readline())
37 35 > log('client wants %r blobs\n' % count)
38 36 > wants = []
39 > for _ in xrange(count):
37 > for _ in range(count):
40 38 > key = sys.stdin.readline()[:-1]
41 39 > wants.append(key)
42 40 > if '\0' in key:
43 41 > _, key = key.split('\0')
44 42 > srcpath = os.path.join(srccache, key)
45 43 > if os.path.exists(srcpath):
46 44 > dest = os.path.join(destcache, key)
47 45 > destdir = os.path.dirname(dest)
48 46 > if not os.path.exists(destdir):
49 47 > os.makedirs(destdir)
50 48 > shutil.copyfile(srcpath, dest)
51 49 > else:
52 50 > # report a cache miss
53 51 > sys.stdout.write(key + '\n')
54 52 > sys.stdout.write('0\n')
55 53 > for key in sorted(wants):
56 54 > log('requested %r\n' % key)
57 55 > sys.stdout.flush()
58 56 > elif cmd == 'set':
59 57 > raise Exception('todo writing')
60 58 > else:
61 59 > raise Exception('unknown command! %r' % cmd)
62 60 > except Exception as e:
63 61 > log('Exception! %s\n' % e)
64 62 > raise
65 63 > EOF
66 64
67 65 $ cat >> $HGRCPATH <<EOF
68 66 > [remotefilelog]
69 67 > cacheprocess = "$PYTHON" $TESTTMP/cacheprocess-logger.py
70 68 > EOF
71 69
72 70 Test cache keys and cache misses.
73 71 $ hgcloneshallow ssh://user@dummy/repo clone -q
74 72 3 files fetched over 1 fetches - (3 misses, 0.00% hit ratio) over *s (glob)
75 73 $ cat cachelog.log
76 74 got command 'get'
77 75 client wants 3 blobs
78 76 requested 'master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0'
79 77 requested 'master/39/5df8f7c51f007019cb30201c49e884b46b92fa/69a1b67522704ec122181c0890bd16e9d3e7516a'
80 78 requested 'master/95/cb0bfd2977c761298d9624e4b4d4c72a39974a/076f5e2225b3ff0400b98c92aa6cdf403ee24cca'
81 79 got command 'set'
82 80 Exception! todo writing
83 81
84 82 Test cache hits.
85 83 $ mv hgcache oldhgcache
86 84 $ rm cachelog.log
87 85 $ hgcloneshallow ssh://user@dummy/repo clone-cachehit -q
88 86 3 files fetched over 1 fetches - (0 misses, 100.00% hit ratio) over *s (glob)
89 87 $ cat cachelog.log | grep -v exit
90 88 got command 'get'
91 89 client wants 3 blobs
92 90 requested 'master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0'
93 91 requested 'master/39/5df8f7c51f007019cb30201c49e884b46b92fa/69a1b67522704ec122181c0890bd16e9d3e7516a'
94 92 requested 'master/95/cb0bfd2977c761298d9624e4b4d4c72a39974a/076f5e2225b3ff0400b98c92aa6cdf403ee24cca'
95 93
96 94 $ cat >> $HGRCPATH <<EOF
97 95 > [remotefilelog]
98 96 > cacheprocess.includepath = yes
99 97 > EOF
100 98
101 99 Test cache keys and cache misses with includepath.
102 100 $ rm -r hgcache oldhgcache
103 101 $ rm cachelog.log
104 102 $ hgcloneshallow ssh://user@dummy/repo clone-withpath -q
105 103 3 files fetched over 1 fetches - (3 misses, 0.00% hit ratio) over *s (glob)
106 104 $ cat cachelog.log
107 105 got command 'get'
108 106 client wants 3 blobs
109 107 requested 'x\x00master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0'
110 108 requested 'y\x00master/95/cb0bfd2977c761298d9624e4b4d4c72a39974a/076f5e2225b3ff0400b98c92aa6cdf403ee24cca'
111 109 requested 'z\x00master/39/5df8f7c51f007019cb30201c49e884b46b92fa/69a1b67522704ec122181c0890bd16e9d3e7516a'
112 110 got command 'set'
113 111 Exception! todo writing
114 112
115 113 Test cache hits with includepath.
116 114 $ mv hgcache oldhgcache
117 115 $ rm cachelog.log
118 116 $ hgcloneshallow ssh://user@dummy/repo clone-withpath-cachehit -q
119 117 3 files fetched over 1 fetches - (0 misses, 100.00% hit ratio) over *s (glob)
120 118 $ cat cachelog.log | grep -v exit
121 119 got command 'get'
122 120 client wants 3 blobs
123 121 requested 'x\x00master/11/f6ad8ec52a2984abaafd7c3b516503785c2072/1406e74118627694268417491f018a4a883152f0'
124 122 requested 'y\x00master/95/cb0bfd2977c761298d9624e4b4d4c72a39974a/076f5e2225b3ff0400b98c92aa6cdf403ee24cca'
125 123 requested 'z\x00master/39/5df8f7c51f007019cb30201c49e884b46b92fa/69a1b67522704ec122181c0890bd16e9d3e7516a'
@@ -1,67 +1,64 b''
1 1 import os
2 2 from mercurial import (
3 3 pycompat,
4 4 ui as uimod,
5 5 )
6 6
7 if pycompat.ispy3:
8 xrange = range
9
10 7 hgrc = os.environ['HGRCPATH']
11 8 f = open(hgrc)
12 9 basehgrc = f.read()
13 10 f.close()
14 11
15 12 print(' hgrc settings command line options final result ')
16 13 print(' quiet verbo debug quiet verbo debug quiet verbo debug')
17 14
18 for i in xrange(64):
15 for i in range(64):
19 16 hgrc_quiet = bool(i & 1 << 0)
20 17 hgrc_verbose = bool(i & 1 << 1)
21 18 hgrc_debug = bool(i & 1 << 2)
22 19 cmd_quiet = bool(i & 1 << 3)
23 20 cmd_verbose = bool(i & 1 << 4)
24 21 cmd_debug = bool(i & 1 << 5)
25 22
26 23 f = open(hgrc, 'w')
27 24 f.write(basehgrc)
28 25 f.write('\n[ui]\n')
29 26 if hgrc_quiet:
30 27 f.write('quiet = True\n')
31 28 if hgrc_verbose:
32 29 f.write('verbose = True\n')
33 30 if hgrc_debug:
34 31 f.write('debug = True\n')
35 32 f.close()
36 33
37 34 u = uimod.ui.load()
38 35 if cmd_quiet or cmd_debug or cmd_verbose:
39 36 u.setconfig(b'ui', b'quiet', pycompat.bytestr(bool(cmd_quiet)))
40 37 u.setconfig(b'ui', b'verbose', pycompat.bytestr(bool(cmd_verbose)))
41 38 u.setconfig(b'ui', b'debug', pycompat.bytestr(bool(cmd_debug)))
42 39
43 40 check = ''
44 41 if u.debugflag:
45 42 if not u.verbose or u.quiet:
46 43 check = ' *'
47 44 elif u.verbose and u.quiet:
48 45 check = ' +'
49 46
50 47 print(
51 48 (
52 49 '%2d %5s %5s %5s %5s %5s %5s -> %5s %5s %5s%s'
53 50 % (
54 51 i,
55 52 hgrc_quiet,
56 53 hgrc_verbose,
57 54 hgrc_debug,
58 55 cmd_quiet,
59 56 cmd_verbose,
60 57 cmd_debug,
61 58 u.quiet,
62 59 u.verbose,
63 60 u.debugflag,
64 61 check,
65 62 )
66 63 )
67 64 )
General Comments 0
You need to be logged in to leave comments. Login now