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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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( |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
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 |
|
|
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 |
|
30 | + [b'com%d' % i for i in range(1, 10)] | |
36 |
+ [b'lpt%d' % i for i in |
|
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 |
|
41 | for i in range(len(r) + 1): | |
47 |
for c in itertools.combinations( |
|
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 |
|
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 |
|
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 |
|
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 |
|
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