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