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