##// END OF EJS Templates
run-test: enable the devel warning during tests...
Pierre-Yves David -
r24751:dc4daf02 default
parent child Browse files
Show More
@@ -1,2081 +1,2083 b''
1 #!/usr/bin/env python
1 #!/usr/bin/env python
2 #
2 #
3 # run-tests.py - Run a set of tests on Mercurial
3 # run-tests.py - Run a set of tests on Mercurial
4 #
4 #
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
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 # Modifying this script is tricky because it has many modes:
10 # Modifying this script is tricky because it has many modes:
11 # - serial (default) vs parallel (-jN, N > 1)
11 # - serial (default) vs parallel (-jN, N > 1)
12 # - no coverage (default) vs coverage (-c, -C, -s)
12 # - no coverage (default) vs coverage (-c, -C, -s)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 # - tests are a mix of shell scripts and Python scripts
14 # - tests are a mix of shell scripts and Python scripts
15 #
15 #
16 # If you change this script, it is recommended that you ensure you
16 # If you change this script, it is recommended that you ensure you
17 # haven't broken it by running it in various modes with a representative
17 # haven't broken it by running it in various modes with a representative
18 # sample of test scripts. For example:
18 # sample of test scripts. For example:
19 #
19 #
20 # 1) serial, no coverage, temp install:
20 # 1) serial, no coverage, temp install:
21 # ./run-tests.py test-s*
21 # ./run-tests.py test-s*
22 # 2) serial, no coverage, local hg:
22 # 2) serial, no coverage, local hg:
23 # ./run-tests.py --local test-s*
23 # ./run-tests.py --local test-s*
24 # 3) serial, coverage, temp install:
24 # 3) serial, coverage, temp install:
25 # ./run-tests.py -c test-s*
25 # ./run-tests.py -c test-s*
26 # 4) serial, coverage, local hg:
26 # 4) serial, coverage, local hg:
27 # ./run-tests.py -c --local test-s* # unsupported
27 # ./run-tests.py -c --local test-s* # unsupported
28 # 5) parallel, no coverage, temp install:
28 # 5) parallel, no coverage, temp install:
29 # ./run-tests.py -j2 test-s*
29 # ./run-tests.py -j2 test-s*
30 # 6) parallel, no coverage, local hg:
30 # 6) parallel, no coverage, local hg:
31 # ./run-tests.py -j2 --local test-s*
31 # ./run-tests.py -j2 --local test-s*
32 # 7) parallel, coverage, temp install:
32 # 7) parallel, coverage, temp install:
33 # ./run-tests.py -j2 -c test-s* # currently broken
33 # ./run-tests.py -j2 -c test-s* # currently broken
34 # 8) parallel, coverage, local install:
34 # 8) parallel, coverage, local install:
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 # 9) parallel, custom tmp dir:
36 # 9) parallel, custom tmp dir:
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 #
38 #
39 # (You could use any subset of the tests: test-s* happens to match
39 # (You could use any subset of the tests: test-s* happens to match
40 # enough that it's worth doing parallel runs, few enough that it
40 # enough that it's worth doing parallel runs, few enough that it
41 # completes fairly quickly, includes both shell and Python scripts, and
41 # completes fairly quickly, includes both shell and Python scripts, and
42 # includes some scripts that run daemon processes.)
42 # includes some scripts that run daemon processes.)
43
43
44 from distutils import version
44 from distutils import version
45 import difflib
45 import difflib
46 import errno
46 import errno
47 import optparse
47 import optparse
48 import os
48 import os
49 import shutil
49 import shutil
50 import subprocess
50 import subprocess
51 import signal
51 import signal
52 import sys
52 import sys
53 import tempfile
53 import tempfile
54 import time
54 import time
55 import random
55 import random
56 import re
56 import re
57 import threading
57 import threading
58 import killdaemons as killmod
58 import killdaemons as killmod
59 import Queue as queue
59 import Queue as queue
60 from xml.dom import minidom
60 from xml.dom import minidom
61 import unittest
61 import unittest
62
62
63 try:
63 try:
64 import json
64 import json
65 except ImportError:
65 except ImportError:
66 try:
66 try:
67 import simplejson as json
67 import simplejson as json
68 except ImportError:
68 except ImportError:
69 json = None
69 json = None
70
70
71 processlock = threading.Lock()
71 processlock = threading.Lock()
72
72
73 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
73 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
74 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
74 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
75 # zombies but it's pretty harmless even if we do.
75 # zombies but it's pretty harmless even if we do.
76 if sys.version_info < (2, 5):
76 if sys.version_info < (2, 5):
77 subprocess._cleanup = lambda: None
77 subprocess._cleanup = lambda: None
78
78
79 wifexited = getattr(os, "WIFEXITED", lambda x: False)
79 wifexited = getattr(os, "WIFEXITED", lambda x: False)
80
80
81 closefds = os.name == 'posix'
81 closefds = os.name == 'posix'
82 def Popen4(cmd, wd, timeout, env=None):
82 def Popen4(cmd, wd, timeout, env=None):
83 processlock.acquire()
83 processlock.acquire()
84 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
84 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
85 close_fds=closefds,
85 close_fds=closefds,
86 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
86 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
87 stderr=subprocess.STDOUT)
87 stderr=subprocess.STDOUT)
88 processlock.release()
88 processlock.release()
89
89
90 p.fromchild = p.stdout
90 p.fromchild = p.stdout
91 p.tochild = p.stdin
91 p.tochild = p.stdin
92 p.childerr = p.stderr
92 p.childerr = p.stderr
93
93
94 p.timeout = False
94 p.timeout = False
95 if timeout:
95 if timeout:
96 def t():
96 def t():
97 start = time.time()
97 start = time.time()
98 while time.time() - start < timeout and p.returncode is None:
98 while time.time() - start < timeout and p.returncode is None:
99 time.sleep(.1)
99 time.sleep(.1)
100 p.timeout = True
100 p.timeout = True
101 if p.returncode is None:
101 if p.returncode is None:
102 terminate(p)
102 terminate(p)
103 threading.Thread(target=t).start()
103 threading.Thread(target=t).start()
104
104
105 return p
105 return p
106
106
107 PYTHON = sys.executable.replace('\\', '/')
107 PYTHON = sys.executable.replace('\\', '/')
108 IMPL_PATH = 'PYTHONPATH'
108 IMPL_PATH = 'PYTHONPATH'
109 if 'java' in sys.platform:
109 if 'java' in sys.platform:
110 IMPL_PATH = 'JYTHONPATH'
110 IMPL_PATH = 'JYTHONPATH'
111
111
112 defaults = {
112 defaults = {
113 'jobs': ('HGTEST_JOBS', 1),
113 'jobs': ('HGTEST_JOBS', 1),
114 'timeout': ('HGTEST_TIMEOUT', 180),
114 'timeout': ('HGTEST_TIMEOUT', 180),
115 'port': ('HGTEST_PORT', 20059),
115 'port': ('HGTEST_PORT', 20059),
116 'shell': ('HGTEST_SHELL', 'sh'),
116 'shell': ('HGTEST_SHELL', 'sh'),
117 }
117 }
118
118
119 def parselistfiles(files, listtype, warn=True):
119 def parselistfiles(files, listtype, warn=True):
120 entries = dict()
120 entries = dict()
121 for filename in files:
121 for filename in files:
122 try:
122 try:
123 path = os.path.expanduser(os.path.expandvars(filename))
123 path = os.path.expanduser(os.path.expandvars(filename))
124 f = open(path, "rb")
124 f = open(path, "rb")
125 except IOError, err:
125 except IOError, err:
126 if err.errno != errno.ENOENT:
126 if err.errno != errno.ENOENT:
127 raise
127 raise
128 if warn:
128 if warn:
129 print "warning: no such %s file: %s" % (listtype, filename)
129 print "warning: no such %s file: %s" % (listtype, filename)
130 continue
130 continue
131
131
132 for line in f.readlines():
132 for line in f.readlines():
133 line = line.split('#', 1)[0].strip()
133 line = line.split('#', 1)[0].strip()
134 if line:
134 if line:
135 entries[line] = filename
135 entries[line] = filename
136
136
137 f.close()
137 f.close()
138 return entries
138 return entries
139
139
140 def getparser():
140 def getparser():
141 """Obtain the OptionParser used by the CLI."""
141 """Obtain the OptionParser used by the CLI."""
142 parser = optparse.OptionParser("%prog [options] [tests]")
142 parser = optparse.OptionParser("%prog [options] [tests]")
143
143
144 # keep these sorted
144 # keep these sorted
145 parser.add_option("--blacklist", action="append",
145 parser.add_option("--blacklist", action="append",
146 help="skip tests listed in the specified blacklist file")
146 help="skip tests listed in the specified blacklist file")
147 parser.add_option("--whitelist", action="append",
147 parser.add_option("--whitelist", action="append",
148 help="always run tests listed in the specified whitelist file")
148 help="always run tests listed in the specified whitelist file")
149 parser.add_option("--changed", type="string",
149 parser.add_option("--changed", type="string",
150 help="run tests that are changed in parent rev or working directory")
150 help="run tests that are changed in parent rev or working directory")
151 parser.add_option("-C", "--annotate", action="store_true",
151 parser.add_option("-C", "--annotate", action="store_true",
152 help="output files annotated with coverage")
152 help="output files annotated with coverage")
153 parser.add_option("-c", "--cover", action="store_true",
153 parser.add_option("-c", "--cover", action="store_true",
154 help="print a test coverage report")
154 help="print a test coverage report")
155 parser.add_option("-d", "--debug", action="store_true",
155 parser.add_option("-d", "--debug", action="store_true",
156 help="debug mode: write output of test scripts to console"
156 help="debug mode: write output of test scripts to console"
157 " rather than capturing and diffing it (disables timeout)")
157 " rather than capturing and diffing it (disables timeout)")
158 parser.add_option("-f", "--first", action="store_true",
158 parser.add_option("-f", "--first", action="store_true",
159 help="exit on the first test failure")
159 help="exit on the first test failure")
160 parser.add_option("-H", "--htmlcov", action="store_true",
160 parser.add_option("-H", "--htmlcov", action="store_true",
161 help="create an HTML report of the coverage of the files")
161 help="create an HTML report of the coverage of the files")
162 parser.add_option("-i", "--interactive", action="store_true",
162 parser.add_option("-i", "--interactive", action="store_true",
163 help="prompt to accept changed output")
163 help="prompt to accept changed output")
164 parser.add_option("-j", "--jobs", type="int",
164 parser.add_option("-j", "--jobs", type="int",
165 help="number of jobs to run in parallel"
165 help="number of jobs to run in parallel"
166 " (default: $%s or %d)" % defaults['jobs'])
166 " (default: $%s or %d)" % defaults['jobs'])
167 parser.add_option("--keep-tmpdir", action="store_true",
167 parser.add_option("--keep-tmpdir", action="store_true",
168 help="keep temporary directory after running tests")
168 help="keep temporary directory after running tests")
169 parser.add_option("-k", "--keywords",
169 parser.add_option("-k", "--keywords",
170 help="run tests matching keywords")
170 help="run tests matching keywords")
171 parser.add_option("-l", "--local", action="store_true",
171 parser.add_option("-l", "--local", action="store_true",
172 help="shortcut for --with-hg=<testdir>/../hg")
172 help="shortcut for --with-hg=<testdir>/../hg")
173 parser.add_option("--loop", action="store_true",
173 parser.add_option("--loop", action="store_true",
174 help="loop tests repeatedly")
174 help="loop tests repeatedly")
175 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
175 parser.add_option("--runs-per-test", type="int", dest="runs_per_test",
176 help="run each test N times (default=1)", default=1)
176 help="run each test N times (default=1)", default=1)
177 parser.add_option("-n", "--nodiff", action="store_true",
177 parser.add_option("-n", "--nodiff", action="store_true",
178 help="skip showing test changes")
178 help="skip showing test changes")
179 parser.add_option("-p", "--port", type="int",
179 parser.add_option("-p", "--port", type="int",
180 help="port on which servers should listen"
180 help="port on which servers should listen"
181 " (default: $%s or %d)" % defaults['port'])
181 " (default: $%s or %d)" % defaults['port'])
182 parser.add_option("--compiler", type="string",
182 parser.add_option("--compiler", type="string",
183 help="compiler to build with")
183 help="compiler to build with")
184 parser.add_option("--pure", action="store_true",
184 parser.add_option("--pure", action="store_true",
185 help="use pure Python code instead of C extensions")
185 help="use pure Python code instead of C extensions")
186 parser.add_option("-R", "--restart", action="store_true",
186 parser.add_option("-R", "--restart", action="store_true",
187 help="restart at last error")
187 help="restart at last error")
188 parser.add_option("-r", "--retest", action="store_true",
188 parser.add_option("-r", "--retest", action="store_true",
189 help="retest failed tests")
189 help="retest failed tests")
190 parser.add_option("-S", "--noskips", action="store_true",
190 parser.add_option("-S", "--noskips", action="store_true",
191 help="don't report skip tests verbosely")
191 help="don't report skip tests verbosely")
192 parser.add_option("--shell", type="string",
192 parser.add_option("--shell", type="string",
193 help="shell to use (default: $%s or %s)" % defaults['shell'])
193 help="shell to use (default: $%s or %s)" % defaults['shell'])
194 parser.add_option("-t", "--timeout", type="int",
194 parser.add_option("-t", "--timeout", type="int",
195 help="kill errant tests after TIMEOUT seconds"
195 help="kill errant tests after TIMEOUT seconds"
196 " (default: $%s or %d)" % defaults['timeout'])
196 " (default: $%s or %d)" % defaults['timeout'])
197 parser.add_option("--time", action="store_true",
197 parser.add_option("--time", action="store_true",
198 help="time how long each test takes")
198 help="time how long each test takes")
199 parser.add_option("--json", action="store_true",
199 parser.add_option("--json", action="store_true",
200 help="store test result data in 'report.json' file")
200 help="store test result data in 'report.json' file")
201 parser.add_option("--tmpdir", type="string",
201 parser.add_option("--tmpdir", type="string",
202 help="run tests in the given temporary directory"
202 help="run tests in the given temporary directory"
203 " (implies --keep-tmpdir)")
203 " (implies --keep-tmpdir)")
204 parser.add_option("-v", "--verbose", action="store_true",
204 parser.add_option("-v", "--verbose", action="store_true",
205 help="output verbose messages")
205 help="output verbose messages")
206 parser.add_option("--xunit", type="string",
206 parser.add_option("--xunit", type="string",
207 help="record xunit results at specified path")
207 help="record xunit results at specified path")
208 parser.add_option("--view", type="string",
208 parser.add_option("--view", type="string",
209 help="external diff viewer")
209 help="external diff viewer")
210 parser.add_option("--with-hg", type="string",
210 parser.add_option("--with-hg", type="string",
211 metavar="HG",
211 metavar="HG",
212 help="test using specified hg script rather than a "
212 help="test using specified hg script rather than a "
213 "temporary installation")
213 "temporary installation")
214 parser.add_option("-3", "--py3k-warnings", action="store_true",
214 parser.add_option("-3", "--py3k-warnings", action="store_true",
215 help="enable Py3k warnings on Python 2.6+")
215 help="enable Py3k warnings on Python 2.6+")
216 parser.add_option('--extra-config-opt', action="append",
216 parser.add_option('--extra-config-opt', action="append",
217 help='set the given config opt in the test hgrc')
217 help='set the given config opt in the test hgrc')
218 parser.add_option('--random', action="store_true",
218 parser.add_option('--random', action="store_true",
219 help='run tests in random order')
219 help='run tests in random order')
220
220
221 for option, (envvar, default) in defaults.items():
221 for option, (envvar, default) in defaults.items():
222 defaults[option] = type(default)(os.environ.get(envvar, default))
222 defaults[option] = type(default)(os.environ.get(envvar, default))
223 parser.set_defaults(**defaults)
223 parser.set_defaults(**defaults)
224
224
225 return parser
225 return parser
226
226
227 def parseargs(args, parser):
227 def parseargs(args, parser):
228 """Parse arguments with our OptionParser and validate results."""
228 """Parse arguments with our OptionParser and validate results."""
229 (options, args) = parser.parse_args(args)
229 (options, args) = parser.parse_args(args)
230
230
231 # jython is always pure
231 # jython is always pure
232 if 'java' in sys.platform or '__pypy__' in sys.modules:
232 if 'java' in sys.platform or '__pypy__' in sys.modules:
233 options.pure = True
233 options.pure = True
234
234
235 if options.with_hg:
235 if options.with_hg:
236 options.with_hg = os.path.expanduser(options.with_hg)
236 options.with_hg = os.path.expanduser(options.with_hg)
237 if not (os.path.isfile(options.with_hg) and
237 if not (os.path.isfile(options.with_hg) and
238 os.access(options.with_hg, os.X_OK)):
238 os.access(options.with_hg, os.X_OK)):
239 parser.error('--with-hg must specify an executable hg script')
239 parser.error('--with-hg must specify an executable hg script')
240 if not os.path.basename(options.with_hg) == 'hg':
240 if not os.path.basename(options.with_hg) == 'hg':
241 sys.stderr.write('warning: --with-hg should specify an hg script\n')
241 sys.stderr.write('warning: --with-hg should specify an hg script\n')
242 if options.local:
242 if options.local:
243 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
243 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
244 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
244 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
245 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
245 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
246 parser.error('--local specified, but %r not found or not executable'
246 parser.error('--local specified, but %r not found or not executable'
247 % hgbin)
247 % hgbin)
248 options.with_hg = hgbin
248 options.with_hg = hgbin
249
249
250 options.anycoverage = options.cover or options.annotate or options.htmlcov
250 options.anycoverage = options.cover or options.annotate or options.htmlcov
251 if options.anycoverage:
251 if options.anycoverage:
252 try:
252 try:
253 import coverage
253 import coverage
254 covver = version.StrictVersion(coverage.__version__).version
254 covver = version.StrictVersion(coverage.__version__).version
255 if covver < (3, 3):
255 if covver < (3, 3):
256 parser.error('coverage options require coverage 3.3 or later')
256 parser.error('coverage options require coverage 3.3 or later')
257 except ImportError:
257 except ImportError:
258 parser.error('coverage options now require the coverage package')
258 parser.error('coverage options now require the coverage package')
259
259
260 if options.anycoverage and options.local:
260 if options.anycoverage and options.local:
261 # this needs some path mangling somewhere, I guess
261 # this needs some path mangling somewhere, I guess
262 parser.error("sorry, coverage options do not work when --local "
262 parser.error("sorry, coverage options do not work when --local "
263 "is specified")
263 "is specified")
264
264
265 if options.anycoverage and options.with_hg:
265 if options.anycoverage and options.with_hg:
266 parser.error("sorry, coverage options do not work when --with-hg "
266 parser.error("sorry, coverage options do not work when --with-hg "
267 "is specified")
267 "is specified")
268
268
269 global verbose
269 global verbose
270 if options.verbose:
270 if options.verbose:
271 verbose = ''
271 verbose = ''
272
272
273 if options.tmpdir:
273 if options.tmpdir:
274 options.tmpdir = os.path.expanduser(options.tmpdir)
274 options.tmpdir = os.path.expanduser(options.tmpdir)
275
275
276 if options.jobs < 1:
276 if options.jobs < 1:
277 parser.error('--jobs must be positive')
277 parser.error('--jobs must be positive')
278 if options.interactive and options.debug:
278 if options.interactive and options.debug:
279 parser.error("-i/--interactive and -d/--debug are incompatible")
279 parser.error("-i/--interactive and -d/--debug are incompatible")
280 if options.debug:
280 if options.debug:
281 if options.timeout != defaults['timeout']:
281 if options.timeout != defaults['timeout']:
282 sys.stderr.write(
282 sys.stderr.write(
283 'warning: --timeout option ignored with --debug\n')
283 'warning: --timeout option ignored with --debug\n')
284 options.timeout = 0
284 options.timeout = 0
285 if options.py3k_warnings:
285 if options.py3k_warnings:
286 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
286 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
287 parser.error('--py3k-warnings can only be used on Python 2.6+')
287 parser.error('--py3k-warnings can only be used on Python 2.6+')
288 if options.blacklist:
288 if options.blacklist:
289 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
289 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
290 if options.whitelist:
290 if options.whitelist:
291 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
291 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
292 else:
292 else:
293 options.whitelisted = {}
293 options.whitelisted = {}
294
294
295 return (options, args)
295 return (options, args)
296
296
297 def rename(src, dst):
297 def rename(src, dst):
298 """Like os.rename(), trade atomicity and opened files friendliness
298 """Like os.rename(), trade atomicity and opened files friendliness
299 for existing destination support.
299 for existing destination support.
300 """
300 """
301 shutil.copy(src, dst)
301 shutil.copy(src, dst)
302 os.remove(src)
302 os.remove(src)
303
303
304 def getdiff(expected, output, ref, err):
304 def getdiff(expected, output, ref, err):
305 servefail = False
305 servefail = False
306 lines = []
306 lines = []
307 for line in difflib.unified_diff(expected, output, ref, err):
307 for line in difflib.unified_diff(expected, output, ref, err):
308 if line.startswith('+++') or line.startswith('---'):
308 if line.startswith('+++') or line.startswith('---'):
309 line = line.replace('\\', '/')
309 line = line.replace('\\', '/')
310 if line.endswith(' \n'):
310 if line.endswith(' \n'):
311 line = line[:-2] + '\n'
311 line = line[:-2] + '\n'
312 lines.append(line)
312 lines.append(line)
313 if not servefail and line.startswith(
313 if not servefail and line.startswith(
314 '+ abort: child process failed to start'):
314 '+ abort: child process failed to start'):
315 servefail = True
315 servefail = True
316
316
317 return servefail, lines
317 return servefail, lines
318
318
319 verbose = False
319 verbose = False
320 def vlog(*msg):
320 def vlog(*msg):
321 """Log only when in verbose mode."""
321 """Log only when in verbose mode."""
322 if verbose is False:
322 if verbose is False:
323 return
323 return
324
324
325 return log(*msg)
325 return log(*msg)
326
326
327 # Bytes that break XML even in a CDATA block: control characters 0-31
327 # Bytes that break XML even in a CDATA block: control characters 0-31
328 # sans \t, \n and \r
328 # sans \t, \n and \r
329 CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]")
329 CDATA_EVIL = re.compile(r"[\000-\010\013\014\016-\037]")
330
330
331 def cdatasafe(data):
331 def cdatasafe(data):
332 """Make a string safe to include in a CDATA block.
332 """Make a string safe to include in a CDATA block.
333
333
334 Certain control characters are illegal in a CDATA block, and
334 Certain control characters are illegal in a CDATA block, and
335 there's no way to include a ]]> in a CDATA either. This function
335 there's no way to include a ]]> in a CDATA either. This function
336 replaces illegal bytes with ? and adds a space between the ]] so
336 replaces illegal bytes with ? and adds a space between the ]] so
337 that it won't break the CDATA block.
337 that it won't break the CDATA block.
338 """
338 """
339 return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>')
339 return CDATA_EVIL.sub('?', data).replace(']]>', '] ]>')
340
340
341 def log(*msg):
341 def log(*msg):
342 """Log something to stdout.
342 """Log something to stdout.
343
343
344 Arguments are strings to print.
344 Arguments are strings to print.
345 """
345 """
346 iolock.acquire()
346 iolock.acquire()
347 if verbose:
347 if verbose:
348 print verbose,
348 print verbose,
349 for m in msg:
349 for m in msg:
350 print m,
350 print m,
351 print
351 print
352 sys.stdout.flush()
352 sys.stdout.flush()
353 iolock.release()
353 iolock.release()
354
354
355 def terminate(proc):
355 def terminate(proc):
356 """Terminate subprocess (with fallback for Python versions < 2.6)"""
356 """Terminate subprocess (with fallback for Python versions < 2.6)"""
357 vlog('# Terminating process %d' % proc.pid)
357 vlog('# Terminating process %d' % proc.pid)
358 try:
358 try:
359 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
359 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
360 except OSError:
360 except OSError:
361 pass
361 pass
362
362
363 def killdaemons(pidfile):
363 def killdaemons(pidfile):
364 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
364 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
365 logfn=vlog)
365 logfn=vlog)
366
366
367 class Test(unittest.TestCase):
367 class Test(unittest.TestCase):
368 """Encapsulates a single, runnable test.
368 """Encapsulates a single, runnable test.
369
369
370 While this class conforms to the unittest.TestCase API, it differs in that
370 While this class conforms to the unittest.TestCase API, it differs in that
371 instances need to be instantiated manually. (Typically, unittest.TestCase
371 instances need to be instantiated manually. (Typically, unittest.TestCase
372 classes are instantiated automatically by scanning modules.)
372 classes are instantiated automatically by scanning modules.)
373 """
373 """
374
374
375 # Status code reserved for skipped tests (used by hghave).
375 # Status code reserved for skipped tests (used by hghave).
376 SKIPPED_STATUS = 80
376 SKIPPED_STATUS = 80
377
377
378 def __init__(self, path, tmpdir, keeptmpdir=False,
378 def __init__(self, path, tmpdir, keeptmpdir=False,
379 debug=False,
379 debug=False,
380 timeout=defaults['timeout'],
380 timeout=defaults['timeout'],
381 startport=defaults['port'], extraconfigopts=None,
381 startport=defaults['port'], extraconfigopts=None,
382 py3kwarnings=False, shell=None):
382 py3kwarnings=False, shell=None):
383 """Create a test from parameters.
383 """Create a test from parameters.
384
384
385 path is the full path to the file defining the test.
385 path is the full path to the file defining the test.
386
386
387 tmpdir is the main temporary directory to use for this test.
387 tmpdir is the main temporary directory to use for this test.
388
388
389 keeptmpdir determines whether to keep the test's temporary directory
389 keeptmpdir determines whether to keep the test's temporary directory
390 after execution. It defaults to removal (False).
390 after execution. It defaults to removal (False).
391
391
392 debug mode will make the test execute verbosely, with unfiltered
392 debug mode will make the test execute verbosely, with unfiltered
393 output.
393 output.
394
394
395 timeout controls the maximum run time of the test. It is ignored when
395 timeout controls the maximum run time of the test. It is ignored when
396 debug is True.
396 debug is True.
397
397
398 startport controls the starting port number to use for this test. Each
398 startport controls the starting port number to use for this test. Each
399 test will reserve 3 port numbers for execution. It is the caller's
399 test will reserve 3 port numbers for execution. It is the caller's
400 responsibility to allocate a non-overlapping port range to Test
400 responsibility to allocate a non-overlapping port range to Test
401 instances.
401 instances.
402
402
403 extraconfigopts is an iterable of extra hgrc config options. Values
403 extraconfigopts is an iterable of extra hgrc config options. Values
404 must have the form "key=value" (something understood by hgrc). Values
404 must have the form "key=value" (something understood by hgrc). Values
405 of the form "foo.key=value" will result in "[foo] key=value".
405 of the form "foo.key=value" will result in "[foo] key=value".
406
406
407 py3kwarnings enables Py3k warnings.
407 py3kwarnings enables Py3k warnings.
408
408
409 shell is the shell to execute tests in.
409 shell is the shell to execute tests in.
410 """
410 """
411
411
412 self.path = path
412 self.path = path
413 self.name = os.path.basename(path)
413 self.name = os.path.basename(path)
414 self._testdir = os.path.dirname(path)
414 self._testdir = os.path.dirname(path)
415 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
415 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
416
416
417 self._threadtmp = tmpdir
417 self._threadtmp = tmpdir
418 self._keeptmpdir = keeptmpdir
418 self._keeptmpdir = keeptmpdir
419 self._debug = debug
419 self._debug = debug
420 self._timeout = timeout
420 self._timeout = timeout
421 self._startport = startport
421 self._startport = startport
422 self._extraconfigopts = extraconfigopts or []
422 self._extraconfigopts = extraconfigopts or []
423 self._py3kwarnings = py3kwarnings
423 self._py3kwarnings = py3kwarnings
424 self._shell = shell
424 self._shell = shell
425
425
426 self._aborted = False
426 self._aborted = False
427 self._daemonpids = []
427 self._daemonpids = []
428 self._finished = None
428 self._finished = None
429 self._ret = None
429 self._ret = None
430 self._out = None
430 self._out = None
431 self._skipped = None
431 self._skipped = None
432 self._testtmp = None
432 self._testtmp = None
433
433
434 # If we're not in --debug mode and reference output file exists,
434 # If we're not in --debug mode and reference output file exists,
435 # check test output against it.
435 # check test output against it.
436 if debug:
436 if debug:
437 self._refout = None # to match "out is None"
437 self._refout = None # to match "out is None"
438 elif os.path.exists(self.refpath):
438 elif os.path.exists(self.refpath):
439 f = open(self.refpath, 'rb')
439 f = open(self.refpath, 'rb')
440 self._refout = f.read().splitlines(True)
440 self._refout = f.read().splitlines(True)
441 f.close()
441 f.close()
442 else:
442 else:
443 self._refout = []
443 self._refout = []
444
444
445 def __str__(self):
445 def __str__(self):
446 return self.name
446 return self.name
447
447
448 def shortDescription(self):
448 def shortDescription(self):
449 return self.name
449 return self.name
450
450
451 def setUp(self):
451 def setUp(self):
452 """Tasks to perform before run()."""
452 """Tasks to perform before run()."""
453 self._finished = False
453 self._finished = False
454 self._ret = None
454 self._ret = None
455 self._out = None
455 self._out = None
456 self._skipped = None
456 self._skipped = None
457
457
458 try:
458 try:
459 os.mkdir(self._threadtmp)
459 os.mkdir(self._threadtmp)
460 except OSError, e:
460 except OSError, e:
461 if e.errno != errno.EEXIST:
461 if e.errno != errno.EEXIST:
462 raise
462 raise
463
463
464 self._testtmp = os.path.join(self._threadtmp,
464 self._testtmp = os.path.join(self._threadtmp,
465 os.path.basename(self.path))
465 os.path.basename(self.path))
466 os.mkdir(self._testtmp)
466 os.mkdir(self._testtmp)
467
467
468 # Remove any previous output files.
468 # Remove any previous output files.
469 if os.path.exists(self.errpath):
469 if os.path.exists(self.errpath):
470 try:
470 try:
471 os.remove(self.errpath)
471 os.remove(self.errpath)
472 except OSError, e:
472 except OSError, e:
473 # We might have raced another test to clean up a .err
473 # We might have raced another test to clean up a .err
474 # file, so ignore ENOENT when removing a previous .err
474 # file, so ignore ENOENT when removing a previous .err
475 # file.
475 # file.
476 if e.errno != errno.ENOENT:
476 if e.errno != errno.ENOENT:
477 raise
477 raise
478
478
479 def run(self, result):
479 def run(self, result):
480 """Run this test and report results against a TestResult instance."""
480 """Run this test and report results against a TestResult instance."""
481 # This function is extremely similar to unittest.TestCase.run(). Once
481 # This function is extremely similar to unittest.TestCase.run(). Once
482 # we require Python 2.7 (or at least its version of unittest), this
482 # we require Python 2.7 (or at least its version of unittest), this
483 # function can largely go away.
483 # function can largely go away.
484 self._result = result
484 self._result = result
485 result.startTest(self)
485 result.startTest(self)
486 try:
486 try:
487 try:
487 try:
488 self.setUp()
488 self.setUp()
489 except (KeyboardInterrupt, SystemExit):
489 except (KeyboardInterrupt, SystemExit):
490 self._aborted = True
490 self._aborted = True
491 raise
491 raise
492 except Exception:
492 except Exception:
493 result.addError(self, sys.exc_info())
493 result.addError(self, sys.exc_info())
494 return
494 return
495
495
496 success = False
496 success = False
497 try:
497 try:
498 self.runTest()
498 self.runTest()
499 except KeyboardInterrupt:
499 except KeyboardInterrupt:
500 self._aborted = True
500 self._aborted = True
501 raise
501 raise
502 except SkipTest, e:
502 except SkipTest, e:
503 result.addSkip(self, str(e))
503 result.addSkip(self, str(e))
504 # The base class will have already counted this as a
504 # The base class will have already counted this as a
505 # test we "ran", but we want to exclude skipped tests
505 # test we "ran", but we want to exclude skipped tests
506 # from those we count towards those run.
506 # from those we count towards those run.
507 result.testsRun -= 1
507 result.testsRun -= 1
508 except IgnoreTest, e:
508 except IgnoreTest, e:
509 result.addIgnore(self, str(e))
509 result.addIgnore(self, str(e))
510 # As with skips, ignores also should be excluded from
510 # As with skips, ignores also should be excluded from
511 # the number of tests executed.
511 # the number of tests executed.
512 result.testsRun -= 1
512 result.testsRun -= 1
513 except WarnTest, e:
513 except WarnTest, e:
514 result.addWarn(self, str(e))
514 result.addWarn(self, str(e))
515 except self.failureException, e:
515 except self.failureException, e:
516 # This differs from unittest in that we don't capture
516 # This differs from unittest in that we don't capture
517 # the stack trace. This is for historical reasons and
517 # the stack trace. This is for historical reasons and
518 # this decision could be revisited in the future,
518 # this decision could be revisited in the future,
519 # especially for PythonTest instances.
519 # especially for PythonTest instances.
520 if result.addFailure(self, str(e)):
520 if result.addFailure(self, str(e)):
521 success = True
521 success = True
522 except Exception:
522 except Exception:
523 result.addError(self, sys.exc_info())
523 result.addError(self, sys.exc_info())
524 else:
524 else:
525 success = True
525 success = True
526
526
527 try:
527 try:
528 self.tearDown()
528 self.tearDown()
529 except (KeyboardInterrupt, SystemExit):
529 except (KeyboardInterrupt, SystemExit):
530 self._aborted = True
530 self._aborted = True
531 raise
531 raise
532 except Exception:
532 except Exception:
533 result.addError(self, sys.exc_info())
533 result.addError(self, sys.exc_info())
534 success = False
534 success = False
535
535
536 if success:
536 if success:
537 result.addSuccess(self)
537 result.addSuccess(self)
538 finally:
538 finally:
539 result.stopTest(self, interrupted=self._aborted)
539 result.stopTest(self, interrupted=self._aborted)
540
540
541 def runTest(self):
541 def runTest(self):
542 """Run this test instance.
542 """Run this test instance.
543
543
544 This will return a tuple describing the result of the test.
544 This will return a tuple describing the result of the test.
545 """
545 """
546 env = self._getenv()
546 env = self._getenv()
547 self._daemonpids.append(env['DAEMON_PIDS'])
547 self._daemonpids.append(env['DAEMON_PIDS'])
548 self._createhgrc(env['HGRCPATH'])
548 self._createhgrc(env['HGRCPATH'])
549
549
550 vlog('# Test', self.name)
550 vlog('# Test', self.name)
551
551
552 ret, out = self._run(env)
552 ret, out = self._run(env)
553 self._finished = True
553 self._finished = True
554 self._ret = ret
554 self._ret = ret
555 self._out = out
555 self._out = out
556
556
557 def describe(ret):
557 def describe(ret):
558 if ret < 0:
558 if ret < 0:
559 return 'killed by signal: %d' % -ret
559 return 'killed by signal: %d' % -ret
560 return 'returned error code %d' % ret
560 return 'returned error code %d' % ret
561
561
562 self._skipped = False
562 self._skipped = False
563
563
564 if ret == self.SKIPPED_STATUS:
564 if ret == self.SKIPPED_STATUS:
565 if out is None: # Debug mode, nothing to parse.
565 if out is None: # Debug mode, nothing to parse.
566 missing = ['unknown']
566 missing = ['unknown']
567 failed = None
567 failed = None
568 else:
568 else:
569 missing, failed = TTest.parsehghaveoutput(out)
569 missing, failed = TTest.parsehghaveoutput(out)
570
570
571 if not missing:
571 if not missing:
572 missing = ['skipped']
572 missing = ['skipped']
573
573
574 if failed:
574 if failed:
575 self.fail('hg have failed checking for %s' % failed[-1])
575 self.fail('hg have failed checking for %s' % failed[-1])
576 else:
576 else:
577 self._skipped = True
577 self._skipped = True
578 raise SkipTest(missing[-1])
578 raise SkipTest(missing[-1])
579 elif ret == 'timeout':
579 elif ret == 'timeout':
580 self.fail('timed out')
580 self.fail('timed out')
581 elif ret is False:
581 elif ret is False:
582 raise WarnTest('no result code from test')
582 raise WarnTest('no result code from test')
583 elif out != self._refout:
583 elif out != self._refout:
584 # Diff generation may rely on written .err file.
584 # Diff generation may rely on written .err file.
585 if (ret != 0 or out != self._refout) and not self._skipped \
585 if (ret != 0 or out != self._refout) and not self._skipped \
586 and not self._debug:
586 and not self._debug:
587 f = open(self.errpath, 'wb')
587 f = open(self.errpath, 'wb')
588 for line in out:
588 for line in out:
589 f.write(line)
589 f.write(line)
590 f.close()
590 f.close()
591
591
592 # The result object handles diff calculation for us.
592 # The result object handles diff calculation for us.
593 if self._result.addOutputMismatch(self, ret, out, self._refout):
593 if self._result.addOutputMismatch(self, ret, out, self._refout):
594 # change was accepted, skip failing
594 # change was accepted, skip failing
595 return
595 return
596
596
597 if ret:
597 if ret:
598 msg = 'output changed and ' + describe(ret)
598 msg = 'output changed and ' + describe(ret)
599 else:
599 else:
600 msg = 'output changed'
600 msg = 'output changed'
601
601
602 self.fail(msg)
602 self.fail(msg)
603 elif ret:
603 elif ret:
604 self.fail(describe(ret))
604 self.fail(describe(ret))
605
605
606 def tearDown(self):
606 def tearDown(self):
607 """Tasks to perform after run()."""
607 """Tasks to perform after run()."""
608 for entry in self._daemonpids:
608 for entry in self._daemonpids:
609 killdaemons(entry)
609 killdaemons(entry)
610 self._daemonpids = []
610 self._daemonpids = []
611
611
612 if not self._keeptmpdir:
612 if not self._keeptmpdir:
613 shutil.rmtree(self._testtmp, True)
613 shutil.rmtree(self._testtmp, True)
614 shutil.rmtree(self._threadtmp, True)
614 shutil.rmtree(self._threadtmp, True)
615
615
616 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
616 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
617 and not self._debug and self._out:
617 and not self._debug and self._out:
618 f = open(self.errpath, 'wb')
618 f = open(self.errpath, 'wb')
619 for line in self._out:
619 for line in self._out:
620 f.write(line)
620 f.write(line)
621 f.close()
621 f.close()
622
622
623 vlog("# Ret was:", self._ret)
623 vlog("# Ret was:", self._ret)
624
624
625 def _run(self, env):
625 def _run(self, env):
626 # This should be implemented in child classes to run tests.
626 # This should be implemented in child classes to run tests.
627 raise SkipTest('unknown test type')
627 raise SkipTest('unknown test type')
628
628
629 def abort(self):
629 def abort(self):
630 """Terminate execution of this test."""
630 """Terminate execution of this test."""
631 self._aborted = True
631 self._aborted = True
632
632
633 def _getreplacements(self):
633 def _getreplacements(self):
634 """Obtain a mapping of text replacements to apply to test output.
634 """Obtain a mapping of text replacements to apply to test output.
635
635
636 Test output needs to be normalized so it can be compared to expected
636 Test output needs to be normalized so it can be compared to expected
637 output. This function defines how some of that normalization will
637 output. This function defines how some of that normalization will
638 occur.
638 occur.
639 """
639 """
640 r = [
640 r = [
641 (r':%s\b' % self._startport, ':$HGPORT'),
641 (r':%s\b' % self._startport, ':$HGPORT'),
642 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
642 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
643 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
643 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
644 (r'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
644 (r'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
645 r'\1 (glob)'),
645 r'\1 (glob)'),
646 ]
646 ]
647
647
648 if os.name == 'nt':
648 if os.name == 'nt':
649 r.append(
649 r.append(
650 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
650 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
651 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
651 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
652 for c in self._testtmp), '$TESTTMP'))
652 for c in self._testtmp), '$TESTTMP'))
653 else:
653 else:
654 r.append((re.escape(self._testtmp), '$TESTTMP'))
654 r.append((re.escape(self._testtmp), '$TESTTMP'))
655
655
656 return r
656 return r
657
657
658 def _getenv(self):
658 def _getenv(self):
659 """Obtain environment variables to use during test execution."""
659 """Obtain environment variables to use during test execution."""
660 env = os.environ.copy()
660 env = os.environ.copy()
661 env['TESTTMP'] = self._testtmp
661 env['TESTTMP'] = self._testtmp
662 env['HOME'] = self._testtmp
662 env['HOME'] = self._testtmp
663 env["HGPORT"] = str(self._startport)
663 env["HGPORT"] = str(self._startport)
664 env["HGPORT1"] = str(self._startport + 1)
664 env["HGPORT1"] = str(self._startport + 1)
665 env["HGPORT2"] = str(self._startport + 2)
665 env["HGPORT2"] = str(self._startport + 2)
666 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
666 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
667 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
667 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
668 env["HGEDITOR"] = ('"' + sys.executable + '"'
668 env["HGEDITOR"] = ('"' + sys.executable + '"'
669 + ' -c "import sys; sys.exit(0)"')
669 + ' -c "import sys; sys.exit(0)"')
670 env["HGMERGE"] = "internal:merge"
670 env["HGMERGE"] = "internal:merge"
671 env["HGUSER"] = "test"
671 env["HGUSER"] = "test"
672 env["HGENCODING"] = "ascii"
672 env["HGENCODING"] = "ascii"
673 env["HGENCODINGMODE"] = "strict"
673 env["HGENCODINGMODE"] = "strict"
674
674
675 # Reset some environment variables to well-known values so that
675 # Reset some environment variables to well-known values so that
676 # the tests produce repeatable output.
676 # the tests produce repeatable output.
677 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
677 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
678 env['TZ'] = 'GMT'
678 env['TZ'] = 'GMT'
679 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
679 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
680 env['COLUMNS'] = '80'
680 env['COLUMNS'] = '80'
681 env['TERM'] = 'xterm'
681 env['TERM'] = 'xterm'
682
682
683 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
683 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
684 'NO_PROXY').split():
684 'NO_PROXY').split():
685 if k in env:
685 if k in env:
686 del env[k]
686 del env[k]
687
687
688 # unset env related to hooks
688 # unset env related to hooks
689 for k in env.keys():
689 for k in env.keys():
690 if k.startswith('HG_'):
690 if k.startswith('HG_'):
691 del env[k]
691 del env[k]
692
692
693 return env
693 return env
694
694
695 def _createhgrc(self, path):
695 def _createhgrc(self, path):
696 """Create an hgrc file for this test."""
696 """Create an hgrc file for this test."""
697 hgrc = open(path, 'wb')
697 hgrc = open(path, 'wb')
698 hgrc.write('[ui]\n')
698 hgrc.write('[ui]\n')
699 hgrc.write('slash = True\n')
699 hgrc.write('slash = True\n')
700 hgrc.write('interactive = False\n')
700 hgrc.write('interactive = False\n')
701 hgrc.write('mergemarkers = detailed\n')
701 hgrc.write('mergemarkers = detailed\n')
702 hgrc.write('promptecho = True\n')
702 hgrc.write('promptecho = True\n')
703 hgrc.write('[defaults]\n')
703 hgrc.write('[defaults]\n')
704 hgrc.write('backout = -d "0 0"\n')
704 hgrc.write('backout = -d "0 0"\n')
705 hgrc.write('commit = -d "0 0"\n')
705 hgrc.write('commit = -d "0 0"\n')
706 hgrc.write('shelve = --date "0 0"\n')
706 hgrc.write('shelve = --date "0 0"\n')
707 hgrc.write('tag = -d "0 0"\n')
707 hgrc.write('tag = -d "0 0"\n')
708 hgrc.write('[devel]\n')
709 hgrc.write('all = true\n')
708 hgrc.write('[largefiles]\n')
710 hgrc.write('[largefiles]\n')
709 hgrc.write('usercache = %s\n' %
711 hgrc.write('usercache = %s\n' %
710 (os.path.join(self._testtmp, '.cache/largefiles')))
712 (os.path.join(self._testtmp, '.cache/largefiles')))
711
713
712 for opt in self._extraconfigopts:
714 for opt in self._extraconfigopts:
713 section, key = opt.split('.', 1)
715 section, key = opt.split('.', 1)
714 assert '=' in key, ('extra config opt %s must '
716 assert '=' in key, ('extra config opt %s must '
715 'have an = for assignment' % opt)
717 'have an = for assignment' % opt)
716 hgrc.write('[%s]\n%s\n' % (section, key))
718 hgrc.write('[%s]\n%s\n' % (section, key))
717 hgrc.close()
719 hgrc.close()
718
720
719 def fail(self, msg):
721 def fail(self, msg):
720 # unittest differentiates between errored and failed.
722 # unittest differentiates between errored and failed.
721 # Failed is denoted by AssertionError (by default at least).
723 # Failed is denoted by AssertionError (by default at least).
722 raise AssertionError(msg)
724 raise AssertionError(msg)
723
725
724 def _runcommand(self, cmd, env, normalizenewlines=False):
726 def _runcommand(self, cmd, env, normalizenewlines=False):
725 """Run command in a sub-process, capturing the output (stdout and
727 """Run command in a sub-process, capturing the output (stdout and
726 stderr).
728 stderr).
727
729
728 Return a tuple (exitcode, output). output is None in debug mode.
730 Return a tuple (exitcode, output). output is None in debug mode.
729 """
731 """
730 if self._debug:
732 if self._debug:
731 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
733 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
732 env=env)
734 env=env)
733 ret = proc.wait()
735 ret = proc.wait()
734 return (ret, None)
736 return (ret, None)
735
737
736 proc = Popen4(cmd, self._testtmp, self._timeout, env)
738 proc = Popen4(cmd, self._testtmp, self._timeout, env)
737 def cleanup():
739 def cleanup():
738 terminate(proc)
740 terminate(proc)
739 ret = proc.wait()
741 ret = proc.wait()
740 if ret == 0:
742 if ret == 0:
741 ret = signal.SIGTERM << 8
743 ret = signal.SIGTERM << 8
742 killdaemons(env['DAEMON_PIDS'])
744 killdaemons(env['DAEMON_PIDS'])
743 return ret
745 return ret
744
746
745 output = ''
747 output = ''
746 proc.tochild.close()
748 proc.tochild.close()
747
749
748 try:
750 try:
749 output = proc.fromchild.read()
751 output = proc.fromchild.read()
750 except KeyboardInterrupt:
752 except KeyboardInterrupt:
751 vlog('# Handling keyboard interrupt')
753 vlog('# Handling keyboard interrupt')
752 cleanup()
754 cleanup()
753 raise
755 raise
754
756
755 ret = proc.wait()
757 ret = proc.wait()
756 if wifexited(ret):
758 if wifexited(ret):
757 ret = os.WEXITSTATUS(ret)
759 ret = os.WEXITSTATUS(ret)
758
760
759 if proc.timeout:
761 if proc.timeout:
760 ret = 'timeout'
762 ret = 'timeout'
761
763
762 if ret:
764 if ret:
763 killdaemons(env['DAEMON_PIDS'])
765 killdaemons(env['DAEMON_PIDS'])
764
766
765 for s, r in self._getreplacements():
767 for s, r in self._getreplacements():
766 output = re.sub(s, r, output)
768 output = re.sub(s, r, output)
767
769
768 if normalizenewlines:
770 if normalizenewlines:
769 output = output.replace('\r\n', '\n')
771 output = output.replace('\r\n', '\n')
770
772
771 return ret, output.splitlines(True)
773 return ret, output.splitlines(True)
772
774
773 class PythonTest(Test):
775 class PythonTest(Test):
774 """A Python-based test."""
776 """A Python-based test."""
775
777
776 @property
778 @property
777 def refpath(self):
779 def refpath(self):
778 return os.path.join(self._testdir, '%s.out' % self.name)
780 return os.path.join(self._testdir, '%s.out' % self.name)
779
781
780 def _run(self, env):
782 def _run(self, env):
781 py3kswitch = self._py3kwarnings and ' -3' or ''
783 py3kswitch = self._py3kwarnings and ' -3' or ''
782 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
784 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
783 vlog("# Running", cmd)
785 vlog("# Running", cmd)
784 normalizenewlines = os.name == 'nt'
786 normalizenewlines = os.name == 'nt'
785 result = self._runcommand(cmd, env,
787 result = self._runcommand(cmd, env,
786 normalizenewlines=normalizenewlines)
788 normalizenewlines=normalizenewlines)
787 if self._aborted:
789 if self._aborted:
788 raise KeyboardInterrupt()
790 raise KeyboardInterrupt()
789
791
790 return result
792 return result
791
793
792 # This script may want to drop globs from lines matching these patterns on
794 # This script may want to drop globs from lines matching these patterns on
793 # Windows, but check-code.py wants a glob on these lines unconditionally. Don't
795 # Windows, but check-code.py wants a glob on these lines unconditionally. Don't
794 # warn if that is the case for anything matching these lines.
796 # warn if that is the case for anything matching these lines.
795 checkcodeglobpats = [
797 checkcodeglobpats = [
796 re.compile(r'^pushing to \$TESTTMP/.*[^)]$'),
798 re.compile(r'^pushing to \$TESTTMP/.*[^)]$'),
797 re.compile(r'^moving \S+/.*[^)]$'),
799 re.compile(r'^moving \S+/.*[^)]$'),
798 re.compile(r'^pulling from \$TESTTMP/.*[^)]$')
800 re.compile(r'^pulling from \$TESTTMP/.*[^)]$')
799 ]
801 ]
800
802
801 class TTest(Test):
803 class TTest(Test):
802 """A "t test" is a test backed by a .t file."""
804 """A "t test" is a test backed by a .t file."""
803
805
804 SKIPPED_PREFIX = 'skipped: '
806 SKIPPED_PREFIX = 'skipped: '
805 FAILED_PREFIX = 'hghave check failed: '
807 FAILED_PREFIX = 'hghave check failed: '
806 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
808 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
807
809
808 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
810 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
809 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256))
811 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256))
810 ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'})
812 ESCAPEMAP.update({'\\': '\\\\', '\r': r'\r'})
811
813
812 @property
814 @property
813 def refpath(self):
815 def refpath(self):
814 return os.path.join(self._testdir, self.name)
816 return os.path.join(self._testdir, self.name)
815
817
816 def _run(self, env):
818 def _run(self, env):
817 f = open(self.path, 'rb')
819 f = open(self.path, 'rb')
818 lines = f.readlines()
820 lines = f.readlines()
819 f.close()
821 f.close()
820
822
821 salt, script, after, expected = self._parsetest(lines)
823 salt, script, after, expected = self._parsetest(lines)
822
824
823 # Write out the generated script.
825 # Write out the generated script.
824 fname = '%s.sh' % self._testtmp
826 fname = '%s.sh' % self._testtmp
825 f = open(fname, 'wb')
827 f = open(fname, 'wb')
826 for l in script:
828 for l in script:
827 f.write(l)
829 f.write(l)
828 f.close()
830 f.close()
829
831
830 cmd = '%s "%s"' % (self._shell, fname)
832 cmd = '%s "%s"' % (self._shell, fname)
831 vlog("# Running", cmd)
833 vlog("# Running", cmd)
832
834
833 exitcode, output = self._runcommand(cmd, env)
835 exitcode, output = self._runcommand(cmd, env)
834
836
835 if self._aborted:
837 if self._aborted:
836 raise KeyboardInterrupt()
838 raise KeyboardInterrupt()
837
839
838 # Do not merge output if skipped. Return hghave message instead.
840 # Do not merge output if skipped. Return hghave message instead.
839 # Similarly, with --debug, output is None.
841 # Similarly, with --debug, output is None.
840 if exitcode == self.SKIPPED_STATUS or output is None:
842 if exitcode == self.SKIPPED_STATUS or output is None:
841 return exitcode, output
843 return exitcode, output
842
844
843 return self._processoutput(exitcode, output, salt, after, expected)
845 return self._processoutput(exitcode, output, salt, after, expected)
844
846
845 def _hghave(self, reqs):
847 def _hghave(self, reqs):
846 # TODO do something smarter when all other uses of hghave are gone.
848 # TODO do something smarter when all other uses of hghave are gone.
847 tdir = self._testdir.replace('\\', '/')
849 tdir = self._testdir.replace('\\', '/')
848 proc = Popen4('%s -c "%s/hghave %s"' %
850 proc = Popen4('%s -c "%s/hghave %s"' %
849 (self._shell, tdir, ' '.join(reqs)),
851 (self._shell, tdir, ' '.join(reqs)),
850 self._testtmp, 0, self._getenv())
852 self._testtmp, 0, self._getenv())
851 stdout, stderr = proc.communicate()
853 stdout, stderr = proc.communicate()
852 ret = proc.wait()
854 ret = proc.wait()
853 if wifexited(ret):
855 if wifexited(ret):
854 ret = os.WEXITSTATUS(ret)
856 ret = os.WEXITSTATUS(ret)
855 if ret == 2:
857 if ret == 2:
856 print stdout
858 print stdout
857 sys.exit(1)
859 sys.exit(1)
858
860
859 return ret == 0
861 return ret == 0
860
862
861 def _parsetest(self, lines):
863 def _parsetest(self, lines):
862 # We generate a shell script which outputs unique markers to line
864 # We generate a shell script which outputs unique markers to line
863 # up script results with our source. These markers include input
865 # up script results with our source. These markers include input
864 # line number and the last return code.
866 # line number and the last return code.
865 salt = "SALT" + str(time.time())
867 salt = "SALT" + str(time.time())
866 def addsalt(line, inpython):
868 def addsalt(line, inpython):
867 if inpython:
869 if inpython:
868 script.append('%s %d 0\n' % (salt, line))
870 script.append('%s %d 0\n' % (salt, line))
869 else:
871 else:
870 script.append('echo %s %s $?\n' % (salt, line))
872 script.append('echo %s %s $?\n' % (salt, line))
871
873
872 script = []
874 script = []
873
875
874 # After we run the shell script, we re-unify the script output
876 # After we run the shell script, we re-unify the script output
875 # with non-active parts of the source, with synchronization by our
877 # with non-active parts of the source, with synchronization by our
876 # SALT line number markers. The after table contains the non-active
878 # SALT line number markers. The after table contains the non-active
877 # components, ordered by line number.
879 # components, ordered by line number.
878 after = {}
880 after = {}
879
881
880 # Expected shell script output.
882 # Expected shell script output.
881 expected = {}
883 expected = {}
882
884
883 pos = prepos = -1
885 pos = prepos = -1
884
886
885 # True or False when in a true or false conditional section
887 # True or False when in a true or false conditional section
886 skipping = None
888 skipping = None
887
889
888 # We keep track of whether or not we're in a Python block so we
890 # We keep track of whether or not we're in a Python block so we
889 # can generate the surrounding doctest magic.
891 # can generate the surrounding doctest magic.
890 inpython = False
892 inpython = False
891
893
892 if self._debug:
894 if self._debug:
893 script.append('set -x\n')
895 script.append('set -x\n')
894 if os.getenv('MSYSTEM'):
896 if os.getenv('MSYSTEM'):
895 script.append('alias pwd="pwd -W"\n')
897 script.append('alias pwd="pwd -W"\n')
896
898
897 for n, l in enumerate(lines):
899 for n, l in enumerate(lines):
898 if not l.endswith('\n'):
900 if not l.endswith('\n'):
899 l += '\n'
901 l += '\n'
900 if l.startswith('#require'):
902 if l.startswith('#require'):
901 lsplit = l.split()
903 lsplit = l.split()
902 if len(lsplit) < 2 or lsplit[0] != '#require':
904 if len(lsplit) < 2 or lsplit[0] != '#require':
903 after.setdefault(pos, []).append(' !!! invalid #require\n')
905 after.setdefault(pos, []).append(' !!! invalid #require\n')
904 if not self._hghave(lsplit[1:]):
906 if not self._hghave(lsplit[1:]):
905 script = ["exit 80\n"]
907 script = ["exit 80\n"]
906 break
908 break
907 after.setdefault(pos, []).append(l)
909 after.setdefault(pos, []).append(l)
908 elif l.startswith('#if'):
910 elif l.startswith('#if'):
909 lsplit = l.split()
911 lsplit = l.split()
910 if len(lsplit) < 2 or lsplit[0] != '#if':
912 if len(lsplit) < 2 or lsplit[0] != '#if':
911 after.setdefault(pos, []).append(' !!! invalid #if\n')
913 after.setdefault(pos, []).append(' !!! invalid #if\n')
912 if skipping is not None:
914 if skipping is not None:
913 after.setdefault(pos, []).append(' !!! nested #if\n')
915 after.setdefault(pos, []).append(' !!! nested #if\n')
914 skipping = not self._hghave(lsplit[1:])
916 skipping = not self._hghave(lsplit[1:])
915 after.setdefault(pos, []).append(l)
917 after.setdefault(pos, []).append(l)
916 elif l.startswith('#else'):
918 elif l.startswith('#else'):
917 if skipping is None:
919 if skipping is None:
918 after.setdefault(pos, []).append(' !!! missing #if\n')
920 after.setdefault(pos, []).append(' !!! missing #if\n')
919 skipping = not skipping
921 skipping = not skipping
920 after.setdefault(pos, []).append(l)
922 after.setdefault(pos, []).append(l)
921 elif l.startswith('#endif'):
923 elif l.startswith('#endif'):
922 if skipping is None:
924 if skipping is None:
923 after.setdefault(pos, []).append(' !!! missing #if\n')
925 after.setdefault(pos, []).append(' !!! missing #if\n')
924 skipping = None
926 skipping = None
925 after.setdefault(pos, []).append(l)
927 after.setdefault(pos, []).append(l)
926 elif skipping:
928 elif skipping:
927 after.setdefault(pos, []).append(l)
929 after.setdefault(pos, []).append(l)
928 elif l.startswith(' >>> '): # python inlines
930 elif l.startswith(' >>> '): # python inlines
929 after.setdefault(pos, []).append(l)
931 after.setdefault(pos, []).append(l)
930 prepos = pos
932 prepos = pos
931 pos = n
933 pos = n
932 if not inpython:
934 if not inpython:
933 # We've just entered a Python block. Add the header.
935 # We've just entered a Python block. Add the header.
934 inpython = True
936 inpython = True
935 addsalt(prepos, False) # Make sure we report the exit code.
937 addsalt(prepos, False) # Make sure we report the exit code.
936 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
938 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
937 addsalt(n, True)
939 addsalt(n, True)
938 script.append(l[2:])
940 script.append(l[2:])
939 elif l.startswith(' ... '): # python inlines
941 elif l.startswith(' ... '): # python inlines
940 after.setdefault(prepos, []).append(l)
942 after.setdefault(prepos, []).append(l)
941 script.append(l[2:])
943 script.append(l[2:])
942 elif l.startswith(' $ '): # commands
944 elif l.startswith(' $ '): # commands
943 if inpython:
945 if inpython:
944 script.append('EOF\n')
946 script.append('EOF\n')
945 inpython = False
947 inpython = False
946 after.setdefault(pos, []).append(l)
948 after.setdefault(pos, []).append(l)
947 prepos = pos
949 prepos = pos
948 pos = n
950 pos = n
949 addsalt(n, False)
951 addsalt(n, False)
950 cmd = l[4:].split()
952 cmd = l[4:].split()
951 if len(cmd) == 2 and cmd[0] == 'cd':
953 if len(cmd) == 2 and cmd[0] == 'cd':
952 l = ' $ cd %s || exit 1\n' % cmd[1]
954 l = ' $ cd %s || exit 1\n' % cmd[1]
953 script.append(l[4:])
955 script.append(l[4:])
954 elif l.startswith(' > '): # continuations
956 elif l.startswith(' > '): # continuations
955 after.setdefault(prepos, []).append(l)
957 after.setdefault(prepos, []).append(l)
956 script.append(l[4:])
958 script.append(l[4:])
957 elif l.startswith(' '): # results
959 elif l.startswith(' '): # results
958 # Queue up a list of expected results.
960 # Queue up a list of expected results.
959 expected.setdefault(pos, []).append(l[2:])
961 expected.setdefault(pos, []).append(l[2:])
960 else:
962 else:
961 if inpython:
963 if inpython:
962 script.append('EOF\n')
964 script.append('EOF\n')
963 inpython = False
965 inpython = False
964 # Non-command/result. Queue up for merged output.
966 # Non-command/result. Queue up for merged output.
965 after.setdefault(pos, []).append(l)
967 after.setdefault(pos, []).append(l)
966
968
967 if inpython:
969 if inpython:
968 script.append('EOF\n')
970 script.append('EOF\n')
969 if skipping is not None:
971 if skipping is not None:
970 after.setdefault(pos, []).append(' !!! missing #endif\n')
972 after.setdefault(pos, []).append(' !!! missing #endif\n')
971 addsalt(n + 1, False)
973 addsalt(n + 1, False)
972
974
973 return salt, script, after, expected
975 return salt, script, after, expected
974
976
975 def _processoutput(self, exitcode, output, salt, after, expected):
977 def _processoutput(self, exitcode, output, salt, after, expected):
976 # Merge the script output back into a unified test.
978 # Merge the script output back into a unified test.
977 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
979 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
978 if exitcode != 0:
980 if exitcode != 0:
979 warnonly = 3
981 warnonly = 3
980
982
981 pos = -1
983 pos = -1
982 postout = []
984 postout = []
983 for l in output:
985 for l in output:
984 lout, lcmd = l, None
986 lout, lcmd = l, None
985 if salt in l:
987 if salt in l:
986 lout, lcmd = l.split(salt, 1)
988 lout, lcmd = l.split(salt, 1)
987
989
988 if lout:
990 if lout:
989 if not lout.endswith('\n'):
991 if not lout.endswith('\n'):
990 lout += ' (no-eol)\n'
992 lout += ' (no-eol)\n'
991
993
992 # Find the expected output at the current position.
994 # Find the expected output at the current position.
993 el = None
995 el = None
994 if expected.get(pos, None):
996 if expected.get(pos, None):
995 el = expected[pos].pop(0)
997 el = expected[pos].pop(0)
996
998
997 r = TTest.linematch(el, lout)
999 r = TTest.linematch(el, lout)
998 if isinstance(r, str):
1000 if isinstance(r, str):
999 if r == '+glob':
1001 if r == '+glob':
1000 lout = el[:-1] + ' (glob)\n'
1002 lout = el[:-1] + ' (glob)\n'
1001 r = '' # Warn only this line.
1003 r = '' # Warn only this line.
1002 elif r == '-glob':
1004 elif r == '-glob':
1003 lout = ''.join(el.rsplit(' (glob)', 1))
1005 lout = ''.join(el.rsplit(' (glob)', 1))
1004 r = '' # Warn only this line.
1006 r = '' # Warn only this line.
1005 else:
1007 else:
1006 log('\ninfo, unknown linematch result: %r\n' % r)
1008 log('\ninfo, unknown linematch result: %r\n' % r)
1007 r = False
1009 r = False
1008 if r:
1010 if r:
1009 postout.append(' ' + el)
1011 postout.append(' ' + el)
1010 else:
1012 else:
1011 if self.NEEDESCAPE(lout):
1013 if self.NEEDESCAPE(lout):
1012 lout = TTest._stringescape('%s (esc)\n' %
1014 lout = TTest._stringescape('%s (esc)\n' %
1013 lout.rstrip('\n'))
1015 lout.rstrip('\n'))
1014 postout.append(' ' + lout) # Let diff deal with it.
1016 postout.append(' ' + lout) # Let diff deal with it.
1015 if r != '': # If line failed.
1017 if r != '': # If line failed.
1016 warnonly = 3 # for sure not
1018 warnonly = 3 # for sure not
1017 elif warnonly == 1: # Is "not yet" and line is warn only.
1019 elif warnonly == 1: # Is "not yet" and line is warn only.
1018 warnonly = 2 # Yes do warn.
1020 warnonly = 2 # Yes do warn.
1019
1021
1020 if lcmd:
1022 if lcmd:
1021 # Add on last return code.
1023 # Add on last return code.
1022 ret = int(lcmd.split()[1])
1024 ret = int(lcmd.split()[1])
1023 if ret != 0:
1025 if ret != 0:
1024 postout.append(' [%s]\n' % ret)
1026 postout.append(' [%s]\n' % ret)
1025 if pos in after:
1027 if pos in after:
1026 # Merge in non-active test bits.
1028 # Merge in non-active test bits.
1027 postout += after.pop(pos)
1029 postout += after.pop(pos)
1028 pos = int(lcmd.split()[0])
1030 pos = int(lcmd.split()[0])
1029
1031
1030 if pos in after:
1032 if pos in after:
1031 postout += after.pop(pos)
1033 postout += after.pop(pos)
1032
1034
1033 if warnonly == 2:
1035 if warnonly == 2:
1034 exitcode = False # Set exitcode to warned.
1036 exitcode = False # Set exitcode to warned.
1035
1037
1036 return exitcode, postout
1038 return exitcode, postout
1037
1039
1038 @staticmethod
1040 @staticmethod
1039 def rematch(el, l):
1041 def rematch(el, l):
1040 try:
1042 try:
1041 # use \Z to ensure that the regex matches to the end of the string
1043 # use \Z to ensure that the regex matches to the end of the string
1042 if os.name == 'nt':
1044 if os.name == 'nt':
1043 return re.match(el + r'\r?\n\Z', l)
1045 return re.match(el + r'\r?\n\Z', l)
1044 return re.match(el + r'\n\Z', l)
1046 return re.match(el + r'\n\Z', l)
1045 except re.error:
1047 except re.error:
1046 # el is an invalid regex
1048 # el is an invalid regex
1047 return False
1049 return False
1048
1050
1049 @staticmethod
1051 @staticmethod
1050 def globmatch(el, l):
1052 def globmatch(el, l):
1051 # The only supported special characters are * and ? plus / which also
1053 # The only supported special characters are * and ? plus / which also
1052 # matches \ on windows. Escaping of these characters is supported.
1054 # matches \ on windows. Escaping of these characters is supported.
1053 if el + '\n' == l:
1055 if el + '\n' == l:
1054 if os.altsep:
1056 if os.altsep:
1055 # matching on "/" is not needed for this line
1057 # matching on "/" is not needed for this line
1056 for pat in checkcodeglobpats:
1058 for pat in checkcodeglobpats:
1057 if pat.match(el):
1059 if pat.match(el):
1058 return True
1060 return True
1059 return '-glob'
1061 return '-glob'
1060 return True
1062 return True
1061 i, n = 0, len(el)
1063 i, n = 0, len(el)
1062 res = ''
1064 res = ''
1063 while i < n:
1065 while i < n:
1064 c = el[i]
1066 c = el[i]
1065 i += 1
1067 i += 1
1066 if c == '\\' and el[i] in '*?\\/':
1068 if c == '\\' and el[i] in '*?\\/':
1067 res += el[i - 1:i + 1]
1069 res += el[i - 1:i + 1]
1068 i += 1
1070 i += 1
1069 elif c == '*':
1071 elif c == '*':
1070 res += '.*'
1072 res += '.*'
1071 elif c == '?':
1073 elif c == '?':
1072 res += '.'
1074 res += '.'
1073 elif c == '/' and os.altsep:
1075 elif c == '/' and os.altsep:
1074 res += '[/\\\\]'
1076 res += '[/\\\\]'
1075 else:
1077 else:
1076 res += re.escape(c)
1078 res += re.escape(c)
1077 return TTest.rematch(res, l)
1079 return TTest.rematch(res, l)
1078
1080
1079 @staticmethod
1081 @staticmethod
1080 def linematch(el, l):
1082 def linematch(el, l):
1081 if el == l: # perfect match (fast)
1083 if el == l: # perfect match (fast)
1082 return True
1084 return True
1083 if el:
1085 if el:
1084 if el.endswith(" (esc)\n"):
1086 if el.endswith(" (esc)\n"):
1085 el = el[:-7].decode('string-escape') + '\n'
1087 el = el[:-7].decode('string-escape') + '\n'
1086 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1088 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1087 return True
1089 return True
1088 if el.endswith(" (re)\n"):
1090 if el.endswith(" (re)\n"):
1089 return TTest.rematch(el[:-6], l)
1091 return TTest.rematch(el[:-6], l)
1090 if el.endswith(" (glob)\n"):
1092 if el.endswith(" (glob)\n"):
1091 # ignore '(glob)' added to l by 'replacements'
1093 # ignore '(glob)' added to l by 'replacements'
1092 if l.endswith(" (glob)\n"):
1094 if l.endswith(" (glob)\n"):
1093 l = l[:-8] + "\n"
1095 l = l[:-8] + "\n"
1094 return TTest.globmatch(el[:-8], l)
1096 return TTest.globmatch(el[:-8], l)
1095 if os.altsep and l.replace('\\', '/') == el:
1097 if os.altsep and l.replace('\\', '/') == el:
1096 return '+glob'
1098 return '+glob'
1097 return False
1099 return False
1098
1100
1099 @staticmethod
1101 @staticmethod
1100 def parsehghaveoutput(lines):
1102 def parsehghaveoutput(lines):
1101 '''Parse hghave log lines.
1103 '''Parse hghave log lines.
1102
1104
1103 Return tuple of lists (missing, failed):
1105 Return tuple of lists (missing, failed):
1104 * the missing/unknown features
1106 * the missing/unknown features
1105 * the features for which existence check failed'''
1107 * the features for which existence check failed'''
1106 missing = []
1108 missing = []
1107 failed = []
1109 failed = []
1108 for line in lines:
1110 for line in lines:
1109 if line.startswith(TTest.SKIPPED_PREFIX):
1111 if line.startswith(TTest.SKIPPED_PREFIX):
1110 line = line.splitlines()[0]
1112 line = line.splitlines()[0]
1111 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1113 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1112 elif line.startswith(TTest.FAILED_PREFIX):
1114 elif line.startswith(TTest.FAILED_PREFIX):
1113 line = line.splitlines()[0]
1115 line = line.splitlines()[0]
1114 failed.append(line[len(TTest.FAILED_PREFIX):])
1116 failed.append(line[len(TTest.FAILED_PREFIX):])
1115
1117
1116 return missing, failed
1118 return missing, failed
1117
1119
1118 @staticmethod
1120 @staticmethod
1119 def _escapef(m):
1121 def _escapef(m):
1120 return TTest.ESCAPEMAP[m.group(0)]
1122 return TTest.ESCAPEMAP[m.group(0)]
1121
1123
1122 @staticmethod
1124 @staticmethod
1123 def _stringescape(s):
1125 def _stringescape(s):
1124 return TTest.ESCAPESUB(TTest._escapef, s)
1126 return TTest.ESCAPESUB(TTest._escapef, s)
1125
1127
1126 iolock = threading.RLock()
1128 iolock = threading.RLock()
1127
1129
1128 class SkipTest(Exception):
1130 class SkipTest(Exception):
1129 """Raised to indicate that a test is to be skipped."""
1131 """Raised to indicate that a test is to be skipped."""
1130
1132
1131 class IgnoreTest(Exception):
1133 class IgnoreTest(Exception):
1132 """Raised to indicate that a test is to be ignored."""
1134 """Raised to indicate that a test is to be ignored."""
1133
1135
1134 class WarnTest(Exception):
1136 class WarnTest(Exception):
1135 """Raised to indicate that a test warned."""
1137 """Raised to indicate that a test warned."""
1136
1138
1137 class TestResult(unittest._TextTestResult):
1139 class TestResult(unittest._TextTestResult):
1138 """Holds results when executing via unittest."""
1140 """Holds results when executing via unittest."""
1139 # Don't worry too much about accessing the non-public _TextTestResult.
1141 # Don't worry too much about accessing the non-public _TextTestResult.
1140 # It is relatively common in Python testing tools.
1142 # It is relatively common in Python testing tools.
1141 def __init__(self, options, *args, **kwargs):
1143 def __init__(self, options, *args, **kwargs):
1142 super(TestResult, self).__init__(*args, **kwargs)
1144 super(TestResult, self).__init__(*args, **kwargs)
1143
1145
1144 self._options = options
1146 self._options = options
1145
1147
1146 # unittest.TestResult didn't have skipped until 2.7. We need to
1148 # unittest.TestResult didn't have skipped until 2.7. We need to
1147 # polyfill it.
1149 # polyfill it.
1148 self.skipped = []
1150 self.skipped = []
1149
1151
1150 # We have a custom "ignored" result that isn't present in any Python
1152 # We have a custom "ignored" result that isn't present in any Python
1151 # unittest implementation. It is very similar to skipped. It may make
1153 # unittest implementation. It is very similar to skipped. It may make
1152 # sense to map it into skip some day.
1154 # sense to map it into skip some day.
1153 self.ignored = []
1155 self.ignored = []
1154
1156
1155 # We have a custom "warned" result that isn't present in any Python
1157 # We have a custom "warned" result that isn't present in any Python
1156 # unittest implementation. It is very similar to failed. It may make
1158 # unittest implementation. It is very similar to failed. It may make
1157 # sense to map it into fail some day.
1159 # sense to map it into fail some day.
1158 self.warned = []
1160 self.warned = []
1159
1161
1160 self.times = []
1162 self.times = []
1161 # Data stored for the benefit of generating xunit reports.
1163 # Data stored for the benefit of generating xunit reports.
1162 self.successes = []
1164 self.successes = []
1163 self.faildata = {}
1165 self.faildata = {}
1164
1166
1165 def addFailure(self, test, reason):
1167 def addFailure(self, test, reason):
1166 self.failures.append((test, reason))
1168 self.failures.append((test, reason))
1167
1169
1168 if self._options.first:
1170 if self._options.first:
1169 self.stop()
1171 self.stop()
1170 else:
1172 else:
1171 iolock.acquire()
1173 iolock.acquire()
1172 if not self._options.nodiff:
1174 if not self._options.nodiff:
1173 self.stream.write('\nERROR: %s output changed\n' % test)
1175 self.stream.write('\nERROR: %s output changed\n' % test)
1174
1176
1175 self.stream.write('!')
1177 self.stream.write('!')
1176 self.stream.flush()
1178 self.stream.flush()
1177 iolock.release()
1179 iolock.release()
1178
1180
1179 def addSuccess(self, test):
1181 def addSuccess(self, test):
1180 iolock.acquire()
1182 iolock.acquire()
1181 super(TestResult, self).addSuccess(test)
1183 super(TestResult, self).addSuccess(test)
1182 iolock.release()
1184 iolock.release()
1183 self.successes.append(test)
1185 self.successes.append(test)
1184
1186
1185 def addError(self, test, err):
1187 def addError(self, test, err):
1186 super(TestResult, self).addError(test, err)
1188 super(TestResult, self).addError(test, err)
1187 if self._options.first:
1189 if self._options.first:
1188 self.stop()
1190 self.stop()
1189
1191
1190 # Polyfill.
1192 # Polyfill.
1191 def addSkip(self, test, reason):
1193 def addSkip(self, test, reason):
1192 self.skipped.append((test, reason))
1194 self.skipped.append((test, reason))
1193 iolock.acquire()
1195 iolock.acquire()
1194 if self.showAll:
1196 if self.showAll:
1195 self.stream.writeln('skipped %s' % reason)
1197 self.stream.writeln('skipped %s' % reason)
1196 else:
1198 else:
1197 self.stream.write('s')
1199 self.stream.write('s')
1198 self.stream.flush()
1200 self.stream.flush()
1199 iolock.release()
1201 iolock.release()
1200
1202
1201 def addIgnore(self, test, reason):
1203 def addIgnore(self, test, reason):
1202 self.ignored.append((test, reason))
1204 self.ignored.append((test, reason))
1203 iolock.acquire()
1205 iolock.acquire()
1204 if self.showAll:
1206 if self.showAll:
1205 self.stream.writeln('ignored %s' % reason)
1207 self.stream.writeln('ignored %s' % reason)
1206 else:
1208 else:
1207 if reason != 'not retesting' and reason != "doesn't match keyword":
1209 if reason != 'not retesting' and reason != "doesn't match keyword":
1208 self.stream.write('i')
1210 self.stream.write('i')
1209 else:
1211 else:
1210 self.testsRun += 1
1212 self.testsRun += 1
1211 self.stream.flush()
1213 self.stream.flush()
1212 iolock.release()
1214 iolock.release()
1213
1215
1214 def addWarn(self, test, reason):
1216 def addWarn(self, test, reason):
1215 self.warned.append((test, reason))
1217 self.warned.append((test, reason))
1216
1218
1217 if self._options.first:
1219 if self._options.first:
1218 self.stop()
1220 self.stop()
1219
1221
1220 iolock.acquire()
1222 iolock.acquire()
1221 if self.showAll:
1223 if self.showAll:
1222 self.stream.writeln('warned %s' % reason)
1224 self.stream.writeln('warned %s' % reason)
1223 else:
1225 else:
1224 self.stream.write('~')
1226 self.stream.write('~')
1225 self.stream.flush()
1227 self.stream.flush()
1226 iolock.release()
1228 iolock.release()
1227
1229
1228 def addOutputMismatch(self, test, ret, got, expected):
1230 def addOutputMismatch(self, test, ret, got, expected):
1229 """Record a mismatch in test output for a particular test."""
1231 """Record a mismatch in test output for a particular test."""
1230 if self.shouldStop:
1232 if self.shouldStop:
1231 # don't print, some other test case already failed and
1233 # don't print, some other test case already failed and
1232 # printed, we're just stale and probably failed due to our
1234 # printed, we're just stale and probably failed due to our
1233 # temp dir getting cleaned up.
1235 # temp dir getting cleaned up.
1234 return
1236 return
1235
1237
1236 accepted = False
1238 accepted = False
1237 failed = False
1239 failed = False
1238 lines = []
1240 lines = []
1239
1241
1240 iolock.acquire()
1242 iolock.acquire()
1241 if self._options.nodiff:
1243 if self._options.nodiff:
1242 pass
1244 pass
1243 elif self._options.view:
1245 elif self._options.view:
1244 os.system("%s %s %s" %
1246 os.system("%s %s %s" %
1245 (self._options.view, test.refpath, test.errpath))
1247 (self._options.view, test.refpath, test.errpath))
1246 else:
1248 else:
1247 servefail, lines = getdiff(expected, got,
1249 servefail, lines = getdiff(expected, got,
1248 test.refpath, test.errpath)
1250 test.refpath, test.errpath)
1249 if servefail:
1251 if servefail:
1250 self.addFailure(
1252 self.addFailure(
1251 test,
1253 test,
1252 'server failed to start (HGPORT=%s)' % test._startport)
1254 'server failed to start (HGPORT=%s)' % test._startport)
1253 else:
1255 else:
1254 self.stream.write('\n')
1256 self.stream.write('\n')
1255 for line in lines:
1257 for line in lines:
1256 self.stream.write(line)
1258 self.stream.write(line)
1257 self.stream.flush()
1259 self.stream.flush()
1258
1260
1259 # handle interactive prompt without releasing iolock
1261 # handle interactive prompt without releasing iolock
1260 if self._options.interactive:
1262 if self._options.interactive:
1261 self.stream.write('Accept this change? [n] ')
1263 self.stream.write('Accept this change? [n] ')
1262 answer = sys.stdin.readline().strip()
1264 answer = sys.stdin.readline().strip()
1263 if answer.lower() in ('y', 'yes'):
1265 if answer.lower() in ('y', 'yes'):
1264 if test.name.endswith('.t'):
1266 if test.name.endswith('.t'):
1265 rename(test.errpath, test.path)
1267 rename(test.errpath, test.path)
1266 else:
1268 else:
1267 rename(test.errpath, '%s.out' % test.path)
1269 rename(test.errpath, '%s.out' % test.path)
1268 accepted = True
1270 accepted = True
1269 if not accepted and not failed:
1271 if not accepted and not failed:
1270 self.faildata[test.name] = ''.join(lines)
1272 self.faildata[test.name] = ''.join(lines)
1271 iolock.release()
1273 iolock.release()
1272
1274
1273 return accepted
1275 return accepted
1274
1276
1275 def startTest(self, test):
1277 def startTest(self, test):
1276 super(TestResult, self).startTest(test)
1278 super(TestResult, self).startTest(test)
1277
1279
1278 # os.times module computes the user time and system time spent by
1280 # os.times module computes the user time and system time spent by
1279 # child's processes along with real elapsed time taken by a process.
1281 # child's processes along with real elapsed time taken by a process.
1280 # This module has one limitation. It can only work for Linux user
1282 # This module has one limitation. It can only work for Linux user
1281 # and not for Windows.
1283 # and not for Windows.
1282 test.started = os.times()
1284 test.started = os.times()
1283
1285
1284 def stopTest(self, test, interrupted=False):
1286 def stopTest(self, test, interrupted=False):
1285 super(TestResult, self).stopTest(test)
1287 super(TestResult, self).stopTest(test)
1286
1288
1287 test.stopped = os.times()
1289 test.stopped = os.times()
1288
1290
1289 starttime = test.started
1291 starttime = test.started
1290 endtime = test.stopped
1292 endtime = test.stopped
1291 self.times.append((test.name, endtime[2] - starttime[2],
1293 self.times.append((test.name, endtime[2] - starttime[2],
1292 endtime[3] - starttime[3], endtime[4] - starttime[4]))
1294 endtime[3] - starttime[3], endtime[4] - starttime[4]))
1293
1295
1294 if interrupted:
1296 if interrupted:
1295 iolock.acquire()
1297 iolock.acquire()
1296 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1298 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1297 test.name, self.times[-1][3]))
1299 test.name, self.times[-1][3]))
1298 iolock.release()
1300 iolock.release()
1299
1301
1300 class TestSuite(unittest.TestSuite):
1302 class TestSuite(unittest.TestSuite):
1301 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1303 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1302
1304
1303 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1305 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1304 retest=False, keywords=None, loop=False, runs_per_test=1,
1306 retest=False, keywords=None, loop=False, runs_per_test=1,
1305 loadtest=None,
1307 loadtest=None,
1306 *args, **kwargs):
1308 *args, **kwargs):
1307 """Create a new instance that can run tests with a configuration.
1309 """Create a new instance that can run tests with a configuration.
1308
1310
1309 testdir specifies the directory where tests are executed from. This
1311 testdir specifies the directory where tests are executed from. This
1310 is typically the ``tests`` directory from Mercurial's source
1312 is typically the ``tests`` directory from Mercurial's source
1311 repository.
1313 repository.
1312
1314
1313 jobs specifies the number of jobs to run concurrently. Each test
1315 jobs specifies the number of jobs to run concurrently. Each test
1314 executes on its own thread. Tests actually spawn new processes, so
1316 executes on its own thread. Tests actually spawn new processes, so
1315 state mutation should not be an issue.
1317 state mutation should not be an issue.
1316
1318
1317 whitelist and blacklist denote tests that have been whitelisted and
1319 whitelist and blacklist denote tests that have been whitelisted and
1318 blacklisted, respectively. These arguments don't belong in TestSuite.
1320 blacklisted, respectively. These arguments don't belong in TestSuite.
1319 Instead, whitelist and blacklist should be handled by the thing that
1321 Instead, whitelist and blacklist should be handled by the thing that
1320 populates the TestSuite with tests. They are present to preserve
1322 populates the TestSuite with tests. They are present to preserve
1321 backwards compatible behavior which reports skipped tests as part
1323 backwards compatible behavior which reports skipped tests as part
1322 of the results.
1324 of the results.
1323
1325
1324 retest denotes whether to retest failed tests. This arguably belongs
1326 retest denotes whether to retest failed tests. This arguably belongs
1325 outside of TestSuite.
1327 outside of TestSuite.
1326
1328
1327 keywords denotes key words that will be used to filter which tests
1329 keywords denotes key words that will be used to filter which tests
1328 to execute. This arguably belongs outside of TestSuite.
1330 to execute. This arguably belongs outside of TestSuite.
1329
1331
1330 loop denotes whether to loop over tests forever.
1332 loop denotes whether to loop over tests forever.
1331 """
1333 """
1332 super(TestSuite, self).__init__(*args, **kwargs)
1334 super(TestSuite, self).__init__(*args, **kwargs)
1333
1335
1334 self._jobs = jobs
1336 self._jobs = jobs
1335 self._whitelist = whitelist
1337 self._whitelist = whitelist
1336 self._blacklist = blacklist
1338 self._blacklist = blacklist
1337 self._retest = retest
1339 self._retest = retest
1338 self._keywords = keywords
1340 self._keywords = keywords
1339 self._loop = loop
1341 self._loop = loop
1340 self._runs_per_test = runs_per_test
1342 self._runs_per_test = runs_per_test
1341 self._loadtest = loadtest
1343 self._loadtest = loadtest
1342
1344
1343 def run(self, result):
1345 def run(self, result):
1344 # We have a number of filters that need to be applied. We do this
1346 # We have a number of filters that need to be applied. We do this
1345 # here instead of inside Test because it makes the running logic for
1347 # here instead of inside Test because it makes the running logic for
1346 # Test simpler.
1348 # Test simpler.
1347 tests = []
1349 tests = []
1348 num_tests = [0]
1350 num_tests = [0]
1349 for test in self._tests:
1351 for test in self._tests:
1350 def get():
1352 def get():
1351 num_tests[0] += 1
1353 num_tests[0] += 1
1352 if getattr(test, 'should_reload', False):
1354 if getattr(test, 'should_reload', False):
1353 return self._loadtest(test.name, num_tests[0])
1355 return self._loadtest(test.name, num_tests[0])
1354 return test
1356 return test
1355 if not os.path.exists(test.path):
1357 if not os.path.exists(test.path):
1356 result.addSkip(test, "Doesn't exist")
1358 result.addSkip(test, "Doesn't exist")
1357 continue
1359 continue
1358
1360
1359 if not (self._whitelist and test.name in self._whitelist):
1361 if not (self._whitelist and test.name in self._whitelist):
1360 if self._blacklist and test.name in self._blacklist:
1362 if self._blacklist and test.name in self._blacklist:
1361 result.addSkip(test, 'blacklisted')
1363 result.addSkip(test, 'blacklisted')
1362 continue
1364 continue
1363
1365
1364 if self._retest and not os.path.exists(test.errpath):
1366 if self._retest and not os.path.exists(test.errpath):
1365 result.addIgnore(test, 'not retesting')
1367 result.addIgnore(test, 'not retesting')
1366 continue
1368 continue
1367
1369
1368 if self._keywords:
1370 if self._keywords:
1369 f = open(test.path, 'rb')
1371 f = open(test.path, 'rb')
1370 t = f.read().lower() + test.name.lower()
1372 t = f.read().lower() + test.name.lower()
1371 f.close()
1373 f.close()
1372 ignored = False
1374 ignored = False
1373 for k in self._keywords.lower().split():
1375 for k in self._keywords.lower().split():
1374 if k not in t:
1376 if k not in t:
1375 result.addIgnore(test, "doesn't match keyword")
1377 result.addIgnore(test, "doesn't match keyword")
1376 ignored = True
1378 ignored = True
1377 break
1379 break
1378
1380
1379 if ignored:
1381 if ignored:
1380 continue
1382 continue
1381 for _ in xrange(self._runs_per_test):
1383 for _ in xrange(self._runs_per_test):
1382 tests.append(get())
1384 tests.append(get())
1383
1385
1384 runtests = list(tests)
1386 runtests = list(tests)
1385 done = queue.Queue()
1387 done = queue.Queue()
1386 running = 0
1388 running = 0
1387
1389
1388 def job(test, result):
1390 def job(test, result):
1389 try:
1391 try:
1390 test(result)
1392 test(result)
1391 done.put(None)
1393 done.put(None)
1392 except KeyboardInterrupt:
1394 except KeyboardInterrupt:
1393 pass
1395 pass
1394 except: # re-raises
1396 except: # re-raises
1395 done.put(('!', test, 'run-test raised an error, see traceback'))
1397 done.put(('!', test, 'run-test raised an error, see traceback'))
1396 raise
1398 raise
1397
1399
1398 stoppedearly = False
1400 stoppedearly = False
1399
1401
1400 try:
1402 try:
1401 while tests or running:
1403 while tests or running:
1402 if not done.empty() or running == self._jobs or not tests:
1404 if not done.empty() or running == self._jobs or not tests:
1403 try:
1405 try:
1404 done.get(True, 1)
1406 done.get(True, 1)
1405 running -= 1
1407 running -= 1
1406 if result and result.shouldStop:
1408 if result and result.shouldStop:
1407 stoppedearly = True
1409 stoppedearly = True
1408 break
1410 break
1409 except queue.Empty:
1411 except queue.Empty:
1410 continue
1412 continue
1411 if tests and not running == self._jobs:
1413 if tests and not running == self._jobs:
1412 test = tests.pop(0)
1414 test = tests.pop(0)
1413 if self._loop:
1415 if self._loop:
1414 if getattr(test, 'should_reload', False):
1416 if getattr(test, 'should_reload', False):
1415 num_tests[0] += 1
1417 num_tests[0] += 1
1416 tests.append(
1418 tests.append(
1417 self._loadtest(test.name, num_tests[0]))
1419 self._loadtest(test.name, num_tests[0]))
1418 else:
1420 else:
1419 tests.append(test)
1421 tests.append(test)
1420 t = threading.Thread(target=job, name=test.name,
1422 t = threading.Thread(target=job, name=test.name,
1421 args=(test, result))
1423 args=(test, result))
1422 t.start()
1424 t.start()
1423 running += 1
1425 running += 1
1424
1426
1425 # If we stop early we still need to wait on started tests to
1427 # If we stop early we still need to wait on started tests to
1426 # finish. Otherwise, there is a race between the test completing
1428 # finish. Otherwise, there is a race between the test completing
1427 # and the test's cleanup code running. This could result in the
1429 # and the test's cleanup code running. This could result in the
1428 # test reporting incorrect.
1430 # test reporting incorrect.
1429 if stoppedearly:
1431 if stoppedearly:
1430 while running:
1432 while running:
1431 try:
1433 try:
1432 done.get(True, 1)
1434 done.get(True, 1)
1433 running -= 1
1435 running -= 1
1434 except queue.Empty:
1436 except queue.Empty:
1435 continue
1437 continue
1436 except KeyboardInterrupt:
1438 except KeyboardInterrupt:
1437 for test in runtests:
1439 for test in runtests:
1438 test.abort()
1440 test.abort()
1439
1441
1440 return result
1442 return result
1441
1443
1442 class TextTestRunner(unittest.TextTestRunner):
1444 class TextTestRunner(unittest.TextTestRunner):
1443 """Custom unittest test runner that uses appropriate settings."""
1445 """Custom unittest test runner that uses appropriate settings."""
1444
1446
1445 def __init__(self, runner, *args, **kwargs):
1447 def __init__(self, runner, *args, **kwargs):
1446 super(TextTestRunner, self).__init__(*args, **kwargs)
1448 super(TextTestRunner, self).__init__(*args, **kwargs)
1447
1449
1448 self._runner = runner
1450 self._runner = runner
1449
1451
1450 def run(self, test):
1452 def run(self, test):
1451 result = TestResult(self._runner.options, self.stream,
1453 result = TestResult(self._runner.options, self.stream,
1452 self.descriptions, self.verbosity)
1454 self.descriptions, self.verbosity)
1453
1455
1454 test(result)
1456 test(result)
1455
1457
1456 failed = len(result.failures)
1458 failed = len(result.failures)
1457 warned = len(result.warned)
1459 warned = len(result.warned)
1458 skipped = len(result.skipped)
1460 skipped = len(result.skipped)
1459 ignored = len(result.ignored)
1461 ignored = len(result.ignored)
1460
1462
1461 iolock.acquire()
1463 iolock.acquire()
1462 self.stream.writeln('')
1464 self.stream.writeln('')
1463
1465
1464 if not self._runner.options.noskips:
1466 if not self._runner.options.noskips:
1465 for test, msg in result.skipped:
1467 for test, msg in result.skipped:
1466 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1468 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1467 for test, msg in result.warned:
1469 for test, msg in result.warned:
1468 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1470 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1469 for test, msg in result.failures:
1471 for test, msg in result.failures:
1470 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1472 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1471 for test, msg in result.errors:
1473 for test, msg in result.errors:
1472 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1474 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1473
1475
1474 if self._runner.options.xunit:
1476 if self._runner.options.xunit:
1475 xuf = open(self._runner.options.xunit, 'wb')
1477 xuf = open(self._runner.options.xunit, 'wb')
1476 try:
1478 try:
1477 timesd = dict(
1479 timesd = dict(
1478 (test, real) for test, cuser, csys, real in result.times)
1480 (test, real) for test, cuser, csys, real in result.times)
1479 doc = minidom.Document()
1481 doc = minidom.Document()
1480 s = doc.createElement('testsuite')
1482 s = doc.createElement('testsuite')
1481 s.setAttribute('name', 'run-tests')
1483 s.setAttribute('name', 'run-tests')
1482 s.setAttribute('tests', str(result.testsRun))
1484 s.setAttribute('tests', str(result.testsRun))
1483 s.setAttribute('errors', "0") # TODO
1485 s.setAttribute('errors', "0") # TODO
1484 s.setAttribute('failures', str(failed))
1486 s.setAttribute('failures', str(failed))
1485 s.setAttribute('skipped', str(skipped + ignored))
1487 s.setAttribute('skipped', str(skipped + ignored))
1486 doc.appendChild(s)
1488 doc.appendChild(s)
1487 for tc in result.successes:
1489 for tc in result.successes:
1488 t = doc.createElement('testcase')
1490 t = doc.createElement('testcase')
1489 t.setAttribute('name', tc.name)
1491 t.setAttribute('name', tc.name)
1490 t.setAttribute('time', '%.3f' % timesd[tc.name])
1492 t.setAttribute('time', '%.3f' % timesd[tc.name])
1491 s.appendChild(t)
1493 s.appendChild(t)
1492 for tc, err in sorted(result.faildata.iteritems()):
1494 for tc, err in sorted(result.faildata.iteritems()):
1493 t = doc.createElement('testcase')
1495 t = doc.createElement('testcase')
1494 t.setAttribute('name', tc)
1496 t.setAttribute('name', tc)
1495 t.setAttribute('time', '%.3f' % timesd[tc])
1497 t.setAttribute('time', '%.3f' % timesd[tc])
1496 # createCDATASection expects a unicode or it will convert
1498 # createCDATASection expects a unicode or it will convert
1497 # using default conversion rules, which will fail if
1499 # using default conversion rules, which will fail if
1498 # string isn't ASCII.
1500 # string isn't ASCII.
1499 err = cdatasafe(err).decode('utf-8', 'replace')
1501 err = cdatasafe(err).decode('utf-8', 'replace')
1500 cd = doc.createCDATASection(err)
1502 cd = doc.createCDATASection(err)
1501 t.appendChild(cd)
1503 t.appendChild(cd)
1502 s.appendChild(t)
1504 s.appendChild(t)
1503 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1505 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1504 finally:
1506 finally:
1505 xuf.close()
1507 xuf.close()
1506
1508
1507 if self._runner.options.json:
1509 if self._runner.options.json:
1508 if json is None:
1510 if json is None:
1509 raise ImportError("json module not installed")
1511 raise ImportError("json module not installed")
1510 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1512 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1511 fp = open(jsonpath, 'w')
1513 fp = open(jsonpath, 'w')
1512 try:
1514 try:
1513 timesd = {}
1515 timesd = {}
1514 for test, cuser, csys, real in result.times:
1516 for test, cuser, csys, real in result.times:
1515 timesd[test] = (real, cuser, csys)
1517 timesd[test] = (real, cuser, csys)
1516
1518
1517 outcome = {}
1519 outcome = {}
1518 for tc in result.successes:
1520 for tc in result.successes:
1519 testresult = {'result': 'success',
1521 testresult = {'result': 'success',
1520 'time': ('%0.3f' % timesd[tc.name][0]),
1522 'time': ('%0.3f' % timesd[tc.name][0]),
1521 'cuser': ('%0.3f' % timesd[tc.name][1]),
1523 'cuser': ('%0.3f' % timesd[tc.name][1]),
1522 'csys': ('%0.3f' % timesd[tc.name][2])}
1524 'csys': ('%0.3f' % timesd[tc.name][2])}
1523 outcome[tc.name] = testresult
1525 outcome[tc.name] = testresult
1524
1526
1525 for tc, err in sorted(result.faildata.iteritems()):
1527 for tc, err in sorted(result.faildata.iteritems()):
1526 testresult = {'result': 'failure',
1528 testresult = {'result': 'failure',
1527 'time': ('%0.3f' % timesd[tc][0]),
1529 'time': ('%0.3f' % timesd[tc][0]),
1528 'cuser': ('%0.3f' % timesd[tc][1]),
1530 'cuser': ('%0.3f' % timesd[tc][1]),
1529 'csys': ('%0.3f' % timesd[tc][2])}
1531 'csys': ('%0.3f' % timesd[tc][2])}
1530 outcome[tc] = testresult
1532 outcome[tc] = testresult
1531
1533
1532 for tc, reason in result.skipped:
1534 for tc, reason in result.skipped:
1533 testresult = {'result': 'skip',
1535 testresult = {'result': 'skip',
1534 'time': ('%0.3f' % timesd[tc.name][0]),
1536 'time': ('%0.3f' % timesd[tc.name][0]),
1535 'cuser': ('%0.3f' % timesd[tc.name][1]),
1537 'cuser': ('%0.3f' % timesd[tc.name][1]),
1536 'csys': ('%0.3f' % timesd[tc.name][2])}
1538 'csys': ('%0.3f' % timesd[tc.name][2])}
1537 outcome[tc.name] = testresult
1539 outcome[tc.name] = testresult
1538
1540
1539 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1541 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1540 fp.writelines(("testreport =", jsonout))
1542 fp.writelines(("testreport =", jsonout))
1541 finally:
1543 finally:
1542 fp.close()
1544 fp.close()
1543
1545
1544 self._runner._checkhglib('Tested')
1546 self._runner._checkhglib('Tested')
1545
1547
1546 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1548 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1547 % (result.testsRun,
1549 % (result.testsRun,
1548 skipped + ignored, warned, failed))
1550 skipped + ignored, warned, failed))
1549 if failed:
1551 if failed:
1550 self.stream.writeln('python hash seed: %s' %
1552 self.stream.writeln('python hash seed: %s' %
1551 os.environ['PYTHONHASHSEED'])
1553 os.environ['PYTHONHASHSEED'])
1552 if self._runner.options.time:
1554 if self._runner.options.time:
1553 self.printtimes(result.times)
1555 self.printtimes(result.times)
1554
1556
1555 iolock.release()
1557 iolock.release()
1556
1558
1557 return result
1559 return result
1558
1560
1559 def printtimes(self, times):
1561 def printtimes(self, times):
1560 # iolock held by run
1562 # iolock held by run
1561 self.stream.writeln('# Producing time report')
1563 self.stream.writeln('# Producing time report')
1562 times.sort(key=lambda t: (t[3]))
1564 times.sort(key=lambda t: (t[3]))
1563 cols = '%7.3f %7.3f %7.3f %s'
1565 cols = '%7.3f %7.3f %7.3f %s'
1564 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1566 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1565 'Test'))
1567 'Test'))
1566 for test, cuser, csys, real in times:
1568 for test, cuser, csys, real in times:
1567 self.stream.writeln(cols % (cuser, csys, real, test))
1569 self.stream.writeln(cols % (cuser, csys, real, test))
1568
1570
1569 class TestRunner(object):
1571 class TestRunner(object):
1570 """Holds context for executing tests.
1572 """Holds context for executing tests.
1571
1573
1572 Tests rely on a lot of state. This object holds it for them.
1574 Tests rely on a lot of state. This object holds it for them.
1573 """
1575 """
1574
1576
1575 # Programs required to run tests.
1577 # Programs required to run tests.
1576 REQUIREDTOOLS = [
1578 REQUIREDTOOLS = [
1577 os.path.basename(sys.executable),
1579 os.path.basename(sys.executable),
1578 'diff',
1580 'diff',
1579 'grep',
1581 'grep',
1580 'unzip',
1582 'unzip',
1581 'gunzip',
1583 'gunzip',
1582 'bunzip2',
1584 'bunzip2',
1583 'sed',
1585 'sed',
1584 ]
1586 ]
1585
1587
1586 # Maps file extensions to test class.
1588 # Maps file extensions to test class.
1587 TESTTYPES = [
1589 TESTTYPES = [
1588 ('.py', PythonTest),
1590 ('.py', PythonTest),
1589 ('.t', TTest),
1591 ('.t', TTest),
1590 ]
1592 ]
1591
1593
1592 def __init__(self):
1594 def __init__(self):
1593 self.options = None
1595 self.options = None
1594 self._hgroot = None
1596 self._hgroot = None
1595 self._testdir = None
1597 self._testdir = None
1596 self._hgtmp = None
1598 self._hgtmp = None
1597 self._installdir = None
1599 self._installdir = None
1598 self._bindir = None
1600 self._bindir = None
1599 self._tmpbinddir = None
1601 self._tmpbinddir = None
1600 self._pythondir = None
1602 self._pythondir = None
1601 self._coveragefile = None
1603 self._coveragefile = None
1602 self._createdfiles = []
1604 self._createdfiles = []
1603 self._hgpath = None
1605 self._hgpath = None
1604
1606
1605 def run(self, args, parser=None):
1607 def run(self, args, parser=None):
1606 """Run the test suite."""
1608 """Run the test suite."""
1607 oldmask = os.umask(022)
1609 oldmask = os.umask(022)
1608 try:
1610 try:
1609 parser = parser or getparser()
1611 parser = parser or getparser()
1610 options, args = parseargs(args, parser)
1612 options, args = parseargs(args, parser)
1611 self.options = options
1613 self.options = options
1612
1614
1613 self._checktools()
1615 self._checktools()
1614 tests = self.findtests(args)
1616 tests = self.findtests(args)
1615 return self._run(tests)
1617 return self._run(tests)
1616 finally:
1618 finally:
1617 os.umask(oldmask)
1619 os.umask(oldmask)
1618
1620
1619 def _run(self, tests):
1621 def _run(self, tests):
1620 if self.options.random:
1622 if self.options.random:
1621 random.shuffle(tests)
1623 random.shuffle(tests)
1622 else:
1624 else:
1623 # keywords for slow tests
1625 # keywords for slow tests
1624 slow = 'svn gendoc check-code-hg'.split()
1626 slow = 'svn gendoc check-code-hg'.split()
1625 def sortkey(f):
1627 def sortkey(f):
1626 # run largest tests first, as they tend to take the longest
1628 # run largest tests first, as they tend to take the longest
1627 try:
1629 try:
1628 val = -os.stat(f).st_size
1630 val = -os.stat(f).st_size
1629 except OSError, e:
1631 except OSError, e:
1630 if e.errno != errno.ENOENT:
1632 if e.errno != errno.ENOENT:
1631 raise
1633 raise
1632 return -1e9 # file does not exist, tell early
1634 return -1e9 # file does not exist, tell early
1633 for kw in slow:
1635 for kw in slow:
1634 if kw in f:
1636 if kw in f:
1635 val *= 10
1637 val *= 10
1636 return val
1638 return val
1637 tests.sort(key=sortkey)
1639 tests.sort(key=sortkey)
1638
1640
1639 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1641 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1640
1642
1641 if 'PYTHONHASHSEED' not in os.environ:
1643 if 'PYTHONHASHSEED' not in os.environ:
1642 # use a random python hash seed all the time
1644 # use a random python hash seed all the time
1643 # we do the randomness ourself to know what seed is used
1645 # we do the randomness ourself to know what seed is used
1644 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1646 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1645
1647
1646 if self.options.tmpdir:
1648 if self.options.tmpdir:
1647 self.options.keep_tmpdir = True
1649 self.options.keep_tmpdir = True
1648 tmpdir = self.options.tmpdir
1650 tmpdir = self.options.tmpdir
1649 if os.path.exists(tmpdir):
1651 if os.path.exists(tmpdir):
1650 # Meaning of tmpdir has changed since 1.3: we used to create
1652 # Meaning of tmpdir has changed since 1.3: we used to create
1651 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1653 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1652 # tmpdir already exists.
1654 # tmpdir already exists.
1653 print "error: temp dir %r already exists" % tmpdir
1655 print "error: temp dir %r already exists" % tmpdir
1654 return 1
1656 return 1
1655
1657
1656 # Automatically removing tmpdir sounds convenient, but could
1658 # Automatically removing tmpdir sounds convenient, but could
1657 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1659 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1658 # or "--tmpdir=$HOME".
1660 # or "--tmpdir=$HOME".
1659 #vlog("# Removing temp dir", tmpdir)
1661 #vlog("# Removing temp dir", tmpdir)
1660 #shutil.rmtree(tmpdir)
1662 #shutil.rmtree(tmpdir)
1661 os.makedirs(tmpdir)
1663 os.makedirs(tmpdir)
1662 else:
1664 else:
1663 d = None
1665 d = None
1664 if os.name == 'nt':
1666 if os.name == 'nt':
1665 # without this, we get the default temp dir location, but
1667 # without this, we get the default temp dir location, but
1666 # in all lowercase, which causes troubles with paths (issue3490)
1668 # in all lowercase, which causes troubles with paths (issue3490)
1667 d = os.getenv('TMP')
1669 d = os.getenv('TMP')
1668 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1670 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1669 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1671 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1670
1672
1671 if self.options.with_hg:
1673 if self.options.with_hg:
1672 self._installdir = None
1674 self._installdir = None
1673 self._bindir = os.path.dirname(os.path.realpath(
1675 self._bindir = os.path.dirname(os.path.realpath(
1674 self.options.with_hg))
1676 self.options.with_hg))
1675 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1677 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1676 os.makedirs(self._tmpbindir)
1678 os.makedirs(self._tmpbindir)
1677
1679
1678 # This looks redundant with how Python initializes sys.path from
1680 # This looks redundant with how Python initializes sys.path from
1679 # the location of the script being executed. Needed because the
1681 # the location of the script being executed. Needed because the
1680 # "hg" specified by --with-hg is not the only Python script
1682 # "hg" specified by --with-hg is not the only Python script
1681 # executed in the test suite that needs to import 'mercurial'
1683 # executed in the test suite that needs to import 'mercurial'
1682 # ... which means it's not really redundant at all.
1684 # ... which means it's not really redundant at all.
1683 self._pythondir = self._bindir
1685 self._pythondir = self._bindir
1684 else:
1686 else:
1685 self._installdir = os.path.join(self._hgtmp, "install")
1687 self._installdir = os.path.join(self._hgtmp, "install")
1686 self._bindir = os.environ["BINDIR"] = \
1688 self._bindir = os.environ["BINDIR"] = \
1687 os.path.join(self._installdir, "bin")
1689 os.path.join(self._installdir, "bin")
1688 self._tmpbindir = self._bindir
1690 self._tmpbindir = self._bindir
1689 self._pythondir = os.path.join(self._installdir, "lib", "python")
1691 self._pythondir = os.path.join(self._installdir, "lib", "python")
1690
1692
1691 os.environ["BINDIR"] = self._bindir
1693 os.environ["BINDIR"] = self._bindir
1692 os.environ["PYTHON"] = PYTHON
1694 os.environ["PYTHON"] = PYTHON
1693
1695
1694 runtestdir = os.path.abspath(os.path.dirname(__file__))
1696 runtestdir = os.path.abspath(os.path.dirname(__file__))
1695 path = [self._bindir, runtestdir] + os.environ["PATH"].split(os.pathsep)
1697 path = [self._bindir, runtestdir] + os.environ["PATH"].split(os.pathsep)
1696 if os.path.islink(__file__):
1698 if os.path.islink(__file__):
1697 # test helper will likely be at the end of the symlink
1699 # test helper will likely be at the end of the symlink
1698 realfile = os.path.realpath(__file__)
1700 realfile = os.path.realpath(__file__)
1699 realdir = os.path.abspath(os.path.dirname(realfile))
1701 realdir = os.path.abspath(os.path.dirname(realfile))
1700 path.insert(2, realdir)
1702 path.insert(2, realdir)
1701 if self._tmpbindir != self._bindir:
1703 if self._tmpbindir != self._bindir:
1702 path = [self._tmpbindir] + path
1704 path = [self._tmpbindir] + path
1703 os.environ["PATH"] = os.pathsep.join(path)
1705 os.environ["PATH"] = os.pathsep.join(path)
1704
1706
1705 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1707 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1706 # can run .../tests/run-tests.py test-foo where test-foo
1708 # can run .../tests/run-tests.py test-foo where test-foo
1707 # adds an extension to HGRC. Also include run-test.py directory to
1709 # adds an extension to HGRC. Also include run-test.py directory to
1708 # import modules like heredoctest.
1710 # import modules like heredoctest.
1709 pypath = [self._pythondir, self._testdir, runtestdir]
1711 pypath = [self._pythondir, self._testdir, runtestdir]
1710 # We have to augment PYTHONPATH, rather than simply replacing
1712 # We have to augment PYTHONPATH, rather than simply replacing
1711 # it, in case external libraries are only available via current
1713 # it, in case external libraries are only available via current
1712 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1714 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1713 # are in /opt/subversion.)
1715 # are in /opt/subversion.)
1714 oldpypath = os.environ.get(IMPL_PATH)
1716 oldpypath = os.environ.get(IMPL_PATH)
1715 if oldpypath:
1717 if oldpypath:
1716 pypath.append(oldpypath)
1718 pypath.append(oldpypath)
1717 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1719 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1718
1720
1719 if self.options.pure:
1721 if self.options.pure:
1720 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
1722 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
1721
1723
1722 self._coveragefile = os.path.join(self._testdir, '.coverage')
1724 self._coveragefile = os.path.join(self._testdir, '.coverage')
1723
1725
1724 vlog("# Using TESTDIR", self._testdir)
1726 vlog("# Using TESTDIR", self._testdir)
1725 vlog("# Using HGTMP", self._hgtmp)
1727 vlog("# Using HGTMP", self._hgtmp)
1726 vlog("# Using PATH", os.environ["PATH"])
1728 vlog("# Using PATH", os.environ["PATH"])
1727 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1729 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1728
1730
1729 try:
1731 try:
1730 return self._runtests(tests) or 0
1732 return self._runtests(tests) or 0
1731 finally:
1733 finally:
1732 time.sleep(.1)
1734 time.sleep(.1)
1733 self._cleanup()
1735 self._cleanup()
1734
1736
1735 def findtests(self, args):
1737 def findtests(self, args):
1736 """Finds possible test files from arguments.
1738 """Finds possible test files from arguments.
1737
1739
1738 If you wish to inject custom tests into the test harness, this would
1740 If you wish to inject custom tests into the test harness, this would
1739 be a good function to monkeypatch or override in a derived class.
1741 be a good function to monkeypatch or override in a derived class.
1740 """
1742 """
1741 if not args:
1743 if not args:
1742 if self.options.changed:
1744 if self.options.changed:
1743 proc = Popen4('hg st --rev "%s" -man0 .' %
1745 proc = Popen4('hg st --rev "%s" -man0 .' %
1744 self.options.changed, None, 0)
1746 self.options.changed, None, 0)
1745 stdout, stderr = proc.communicate()
1747 stdout, stderr = proc.communicate()
1746 args = stdout.strip('\0').split('\0')
1748 args = stdout.strip('\0').split('\0')
1747 else:
1749 else:
1748 args = os.listdir('.')
1750 args = os.listdir('.')
1749
1751
1750 return [t for t in args
1752 return [t for t in args
1751 if os.path.basename(t).startswith('test-')
1753 if os.path.basename(t).startswith('test-')
1752 and (t.endswith('.py') or t.endswith('.t'))]
1754 and (t.endswith('.py') or t.endswith('.t'))]
1753
1755
1754 def _runtests(self, tests):
1756 def _runtests(self, tests):
1755 try:
1757 try:
1756 if self._installdir:
1758 if self._installdir:
1757 self._installhg()
1759 self._installhg()
1758 self._checkhglib("Testing")
1760 self._checkhglib("Testing")
1759 else:
1761 else:
1760 self._usecorrectpython()
1762 self._usecorrectpython()
1761
1763
1762 if self.options.restart:
1764 if self.options.restart:
1763 orig = list(tests)
1765 orig = list(tests)
1764 while tests:
1766 while tests:
1765 if os.path.exists(tests[0] + ".err"):
1767 if os.path.exists(tests[0] + ".err"):
1766 break
1768 break
1767 tests.pop(0)
1769 tests.pop(0)
1768 if not tests:
1770 if not tests:
1769 print "running all tests"
1771 print "running all tests"
1770 tests = orig
1772 tests = orig
1771
1773
1772 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1774 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1773
1775
1774 failed = False
1776 failed = False
1775 warned = False
1777 warned = False
1776
1778
1777 suite = TestSuite(self._testdir,
1779 suite = TestSuite(self._testdir,
1778 jobs=self.options.jobs,
1780 jobs=self.options.jobs,
1779 whitelist=self.options.whitelisted,
1781 whitelist=self.options.whitelisted,
1780 blacklist=self.options.blacklist,
1782 blacklist=self.options.blacklist,
1781 retest=self.options.retest,
1783 retest=self.options.retest,
1782 keywords=self.options.keywords,
1784 keywords=self.options.keywords,
1783 loop=self.options.loop,
1785 loop=self.options.loop,
1784 runs_per_test=self.options.runs_per_test,
1786 runs_per_test=self.options.runs_per_test,
1785 tests=tests, loadtest=self._gettest)
1787 tests=tests, loadtest=self._gettest)
1786 verbosity = 1
1788 verbosity = 1
1787 if self.options.verbose:
1789 if self.options.verbose:
1788 verbosity = 2
1790 verbosity = 2
1789 runner = TextTestRunner(self, verbosity=verbosity)
1791 runner = TextTestRunner(self, verbosity=verbosity)
1790 result = runner.run(suite)
1792 result = runner.run(suite)
1791
1793
1792 if result.failures:
1794 if result.failures:
1793 failed = True
1795 failed = True
1794 if result.warned:
1796 if result.warned:
1795 warned = True
1797 warned = True
1796
1798
1797 if self.options.anycoverage:
1799 if self.options.anycoverage:
1798 self._outputcoverage()
1800 self._outputcoverage()
1799 except KeyboardInterrupt:
1801 except KeyboardInterrupt:
1800 failed = True
1802 failed = True
1801 print "\ninterrupted!"
1803 print "\ninterrupted!"
1802
1804
1803 if failed:
1805 if failed:
1804 return 1
1806 return 1
1805 if warned:
1807 if warned:
1806 return 80
1808 return 80
1807
1809
1808 def _gettest(self, test, count):
1810 def _gettest(self, test, count):
1809 """Obtain a Test by looking at its filename.
1811 """Obtain a Test by looking at its filename.
1810
1812
1811 Returns a Test instance. The Test may not be runnable if it doesn't
1813 Returns a Test instance. The Test may not be runnable if it doesn't
1812 map to a known type.
1814 map to a known type.
1813 """
1815 """
1814 lctest = test.lower()
1816 lctest = test.lower()
1815 testcls = Test
1817 testcls = Test
1816
1818
1817 for ext, cls in self.TESTTYPES:
1819 for ext, cls in self.TESTTYPES:
1818 if lctest.endswith(ext):
1820 if lctest.endswith(ext):
1819 testcls = cls
1821 testcls = cls
1820 break
1822 break
1821
1823
1822 refpath = os.path.join(self._testdir, test)
1824 refpath = os.path.join(self._testdir, test)
1823 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1825 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1824
1826
1825 t = testcls(refpath, tmpdir,
1827 t = testcls(refpath, tmpdir,
1826 keeptmpdir=self.options.keep_tmpdir,
1828 keeptmpdir=self.options.keep_tmpdir,
1827 debug=self.options.debug,
1829 debug=self.options.debug,
1828 timeout=self.options.timeout,
1830 timeout=self.options.timeout,
1829 startport=self.options.port + count * 3,
1831 startport=self.options.port + count * 3,
1830 extraconfigopts=self.options.extra_config_opt,
1832 extraconfigopts=self.options.extra_config_opt,
1831 py3kwarnings=self.options.py3k_warnings,
1833 py3kwarnings=self.options.py3k_warnings,
1832 shell=self.options.shell)
1834 shell=self.options.shell)
1833 t.should_reload = True
1835 t.should_reload = True
1834 return t
1836 return t
1835
1837
1836 def _cleanup(self):
1838 def _cleanup(self):
1837 """Clean up state from this test invocation."""
1839 """Clean up state from this test invocation."""
1838
1840
1839 if self.options.keep_tmpdir:
1841 if self.options.keep_tmpdir:
1840 return
1842 return
1841
1843
1842 vlog("# Cleaning up HGTMP", self._hgtmp)
1844 vlog("# Cleaning up HGTMP", self._hgtmp)
1843 shutil.rmtree(self._hgtmp, True)
1845 shutil.rmtree(self._hgtmp, True)
1844 for f in self._createdfiles:
1846 for f in self._createdfiles:
1845 try:
1847 try:
1846 os.remove(f)
1848 os.remove(f)
1847 except OSError:
1849 except OSError:
1848 pass
1850 pass
1849
1851
1850 def _usecorrectpython(self):
1852 def _usecorrectpython(self):
1851 """Configure the environment to use the appropriate Python in tests."""
1853 """Configure the environment to use the appropriate Python in tests."""
1852 # Tests must use the same interpreter as us or bad things will happen.
1854 # Tests must use the same interpreter as us or bad things will happen.
1853 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1855 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1854 if getattr(os, 'symlink', None):
1856 if getattr(os, 'symlink', None):
1855 vlog("# Making python executable in test path a symlink to '%s'" %
1857 vlog("# Making python executable in test path a symlink to '%s'" %
1856 sys.executable)
1858 sys.executable)
1857 mypython = os.path.join(self._tmpbindir, pyexename)
1859 mypython = os.path.join(self._tmpbindir, pyexename)
1858 try:
1860 try:
1859 if os.readlink(mypython) == sys.executable:
1861 if os.readlink(mypython) == sys.executable:
1860 return
1862 return
1861 os.unlink(mypython)
1863 os.unlink(mypython)
1862 except OSError, err:
1864 except OSError, err:
1863 if err.errno != errno.ENOENT:
1865 if err.errno != errno.ENOENT:
1864 raise
1866 raise
1865 if self._findprogram(pyexename) != sys.executable:
1867 if self._findprogram(pyexename) != sys.executable:
1866 try:
1868 try:
1867 os.symlink(sys.executable, mypython)
1869 os.symlink(sys.executable, mypython)
1868 self._createdfiles.append(mypython)
1870 self._createdfiles.append(mypython)
1869 except OSError, err:
1871 except OSError, err:
1870 # child processes may race, which is harmless
1872 # child processes may race, which is harmless
1871 if err.errno != errno.EEXIST:
1873 if err.errno != errno.EEXIST:
1872 raise
1874 raise
1873 else:
1875 else:
1874 exedir, exename = os.path.split(sys.executable)
1876 exedir, exename = os.path.split(sys.executable)
1875 vlog("# Modifying search path to find %s as %s in '%s'" %
1877 vlog("# Modifying search path to find %s as %s in '%s'" %
1876 (exename, pyexename, exedir))
1878 (exename, pyexename, exedir))
1877 path = os.environ['PATH'].split(os.pathsep)
1879 path = os.environ['PATH'].split(os.pathsep)
1878 while exedir in path:
1880 while exedir in path:
1879 path.remove(exedir)
1881 path.remove(exedir)
1880 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1882 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1881 if not self._findprogram(pyexename):
1883 if not self._findprogram(pyexename):
1882 print "WARNING: Cannot find %s in search path" % pyexename
1884 print "WARNING: Cannot find %s in search path" % pyexename
1883
1885
1884 def _installhg(self):
1886 def _installhg(self):
1885 """Install hg into the test environment.
1887 """Install hg into the test environment.
1886
1888
1887 This will also configure hg with the appropriate testing settings.
1889 This will also configure hg with the appropriate testing settings.
1888 """
1890 """
1889 vlog("# Performing temporary installation of HG")
1891 vlog("# Performing temporary installation of HG")
1890 installerrs = os.path.join("tests", "install.err")
1892 installerrs = os.path.join("tests", "install.err")
1891 compiler = ''
1893 compiler = ''
1892 if self.options.compiler:
1894 if self.options.compiler:
1893 compiler = '--compiler ' + self.options.compiler
1895 compiler = '--compiler ' + self.options.compiler
1894 if self.options.pure:
1896 if self.options.pure:
1895 pure = "--pure"
1897 pure = "--pure"
1896 else:
1898 else:
1897 pure = ""
1899 pure = ""
1898 py3 = ''
1900 py3 = ''
1899 if sys.version_info[0] == 3:
1901 if sys.version_info[0] == 3:
1900 py3 = '--c2to3'
1902 py3 = '--c2to3'
1901
1903
1902 # Run installer in hg root
1904 # Run installer in hg root
1903 script = os.path.realpath(sys.argv[0])
1905 script = os.path.realpath(sys.argv[0])
1904 hgroot = os.path.dirname(os.path.dirname(script))
1906 hgroot = os.path.dirname(os.path.dirname(script))
1905 self._hgroot = hgroot
1907 self._hgroot = hgroot
1906 os.chdir(hgroot)
1908 os.chdir(hgroot)
1907 nohome = '--home=""'
1909 nohome = '--home=""'
1908 if os.name == 'nt':
1910 if os.name == 'nt':
1909 # The --home="" trick works only on OS where os.sep == '/'
1911 # The --home="" trick works only on OS where os.sep == '/'
1910 # because of a distutils convert_path() fast-path. Avoid it at
1912 # because of a distutils convert_path() fast-path. Avoid it at
1911 # least on Windows for now, deal with .pydistutils.cfg bugs
1913 # least on Windows for now, deal with .pydistutils.cfg bugs
1912 # when they happen.
1914 # when they happen.
1913 nohome = ''
1915 nohome = ''
1914 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1916 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1915 ' build %(compiler)s --build-base="%(base)s"'
1917 ' build %(compiler)s --build-base="%(base)s"'
1916 ' install --force --prefix="%(prefix)s"'
1918 ' install --force --prefix="%(prefix)s"'
1917 ' --install-lib="%(libdir)s"'
1919 ' --install-lib="%(libdir)s"'
1918 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1920 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1919 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1921 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1920 'compiler': compiler,
1922 'compiler': compiler,
1921 'base': os.path.join(self._hgtmp, "build"),
1923 'base': os.path.join(self._hgtmp, "build"),
1922 'prefix': self._installdir, 'libdir': self._pythondir,
1924 'prefix': self._installdir, 'libdir': self._pythondir,
1923 'bindir': self._bindir,
1925 'bindir': self._bindir,
1924 'nohome': nohome, 'logfile': installerrs})
1926 'nohome': nohome, 'logfile': installerrs})
1925
1927
1926 # setuptools requires install directories to exist.
1928 # setuptools requires install directories to exist.
1927 def makedirs(p):
1929 def makedirs(p):
1928 try:
1930 try:
1929 os.makedirs(p)
1931 os.makedirs(p)
1930 except OSError, e:
1932 except OSError, e:
1931 if e.errno != errno.EEXIST:
1933 if e.errno != errno.EEXIST:
1932 raise
1934 raise
1933 makedirs(self._pythondir)
1935 makedirs(self._pythondir)
1934 makedirs(self._bindir)
1936 makedirs(self._bindir)
1935
1937
1936 vlog("# Running", cmd)
1938 vlog("# Running", cmd)
1937 if os.system(cmd) == 0:
1939 if os.system(cmd) == 0:
1938 if not self.options.verbose:
1940 if not self.options.verbose:
1939 os.remove(installerrs)
1941 os.remove(installerrs)
1940 else:
1942 else:
1941 f = open(installerrs, 'rb')
1943 f = open(installerrs, 'rb')
1942 for line in f:
1944 for line in f:
1943 sys.stdout.write(line)
1945 sys.stdout.write(line)
1944 f.close()
1946 f.close()
1945 sys.exit(1)
1947 sys.exit(1)
1946 os.chdir(self._testdir)
1948 os.chdir(self._testdir)
1947
1949
1948 self._usecorrectpython()
1950 self._usecorrectpython()
1949
1951
1950 if self.options.py3k_warnings and not self.options.anycoverage:
1952 if self.options.py3k_warnings and not self.options.anycoverage:
1951 vlog("# Updating hg command to enable Py3k Warnings switch")
1953 vlog("# Updating hg command to enable Py3k Warnings switch")
1952 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1954 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1953 lines = [line.rstrip() for line in f]
1955 lines = [line.rstrip() for line in f]
1954 lines[0] += ' -3'
1956 lines[0] += ' -3'
1955 f.close()
1957 f.close()
1956 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1958 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1957 for line in lines:
1959 for line in lines:
1958 f.write(line + '\n')
1960 f.write(line + '\n')
1959 f.close()
1961 f.close()
1960
1962
1961 hgbat = os.path.join(self._bindir, 'hg.bat')
1963 hgbat = os.path.join(self._bindir, 'hg.bat')
1962 if os.path.isfile(hgbat):
1964 if os.path.isfile(hgbat):
1963 # hg.bat expects to be put in bin/scripts while run-tests.py
1965 # hg.bat expects to be put in bin/scripts while run-tests.py
1964 # installation layout put it in bin/ directly. Fix it
1966 # installation layout put it in bin/ directly. Fix it
1965 f = open(hgbat, 'rb')
1967 f = open(hgbat, 'rb')
1966 data = f.read()
1968 data = f.read()
1967 f.close()
1969 f.close()
1968 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1970 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1969 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1971 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1970 '"%~dp0python" "%~dp0hg" %*')
1972 '"%~dp0python" "%~dp0hg" %*')
1971 f = open(hgbat, 'wb')
1973 f = open(hgbat, 'wb')
1972 f.write(data)
1974 f.write(data)
1973 f.close()
1975 f.close()
1974 else:
1976 else:
1975 print 'WARNING: cannot fix hg.bat reference to python.exe'
1977 print 'WARNING: cannot fix hg.bat reference to python.exe'
1976
1978
1977 if self.options.anycoverage:
1979 if self.options.anycoverage:
1978 custom = os.path.join(self._testdir, 'sitecustomize.py')
1980 custom = os.path.join(self._testdir, 'sitecustomize.py')
1979 target = os.path.join(self._pythondir, 'sitecustomize.py')
1981 target = os.path.join(self._pythondir, 'sitecustomize.py')
1980 vlog('# Installing coverage trigger to %s' % target)
1982 vlog('# Installing coverage trigger to %s' % target)
1981 shutil.copyfile(custom, target)
1983 shutil.copyfile(custom, target)
1982 rc = os.path.join(self._testdir, '.coveragerc')
1984 rc = os.path.join(self._testdir, '.coveragerc')
1983 vlog('# Installing coverage rc to %s' % rc)
1985 vlog('# Installing coverage rc to %s' % rc)
1984 os.environ['COVERAGE_PROCESS_START'] = rc
1986 os.environ['COVERAGE_PROCESS_START'] = rc
1985 covdir = os.path.join(self._installdir, '..', 'coverage')
1987 covdir = os.path.join(self._installdir, '..', 'coverage')
1986 try:
1988 try:
1987 os.mkdir(covdir)
1989 os.mkdir(covdir)
1988 except OSError, e:
1990 except OSError, e:
1989 if e.errno != errno.EEXIST:
1991 if e.errno != errno.EEXIST:
1990 raise
1992 raise
1991
1993
1992 os.environ['COVERAGE_DIR'] = covdir
1994 os.environ['COVERAGE_DIR'] = covdir
1993
1995
1994 def _checkhglib(self, verb):
1996 def _checkhglib(self, verb):
1995 """Ensure that the 'mercurial' package imported by python is
1997 """Ensure that the 'mercurial' package imported by python is
1996 the one we expect it to be. If not, print a warning to stderr."""
1998 the one we expect it to be. If not, print a warning to stderr."""
1997 if ((self._bindir == self._pythondir) and
1999 if ((self._bindir == self._pythondir) and
1998 (self._bindir != self._tmpbindir)):
2000 (self._bindir != self._tmpbindir)):
1999 # The pythondir has been inferred from --with-hg flag.
2001 # The pythondir has been inferred from --with-hg flag.
2000 # We cannot expect anything sensible here.
2002 # We cannot expect anything sensible here.
2001 return
2003 return
2002 expecthg = os.path.join(self._pythondir, 'mercurial')
2004 expecthg = os.path.join(self._pythondir, 'mercurial')
2003 actualhg = self._gethgpath()
2005 actualhg = self._gethgpath()
2004 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2006 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2005 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2007 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2006 ' (expected %s)\n'
2008 ' (expected %s)\n'
2007 % (verb, actualhg, expecthg))
2009 % (verb, actualhg, expecthg))
2008 def _gethgpath(self):
2010 def _gethgpath(self):
2009 """Return the path to the mercurial package that is actually found by
2011 """Return the path to the mercurial package that is actually found by
2010 the current Python interpreter."""
2012 the current Python interpreter."""
2011 if self._hgpath is not None:
2013 if self._hgpath is not None:
2012 return self._hgpath
2014 return self._hgpath
2013
2015
2014 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
2016 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
2015 pipe = os.popen(cmd % PYTHON)
2017 pipe = os.popen(cmd % PYTHON)
2016 try:
2018 try:
2017 self._hgpath = pipe.read().strip()
2019 self._hgpath = pipe.read().strip()
2018 finally:
2020 finally:
2019 pipe.close()
2021 pipe.close()
2020
2022
2021 return self._hgpath
2023 return self._hgpath
2022
2024
2023 def _outputcoverage(self):
2025 def _outputcoverage(self):
2024 """Produce code coverage output."""
2026 """Produce code coverage output."""
2025 from coverage import coverage
2027 from coverage import coverage
2026
2028
2027 vlog('# Producing coverage report')
2029 vlog('# Producing coverage report')
2028 # chdir is the easiest way to get short, relative paths in the
2030 # chdir is the easiest way to get short, relative paths in the
2029 # output.
2031 # output.
2030 os.chdir(self._hgroot)
2032 os.chdir(self._hgroot)
2031 covdir = os.path.join(self._installdir, '..', 'coverage')
2033 covdir = os.path.join(self._installdir, '..', 'coverage')
2032 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2034 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2033
2035
2034 # Map install directory paths back to source directory.
2036 # Map install directory paths back to source directory.
2035 cov.config.paths['srcdir'] = ['.', self._pythondir]
2037 cov.config.paths['srcdir'] = ['.', self._pythondir]
2036
2038
2037 cov.combine()
2039 cov.combine()
2038
2040
2039 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2041 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2040 cov.report(ignore_errors=True, omit=omit)
2042 cov.report(ignore_errors=True, omit=omit)
2041
2043
2042 if self.options.htmlcov:
2044 if self.options.htmlcov:
2043 htmldir = os.path.join(self._testdir, 'htmlcov')
2045 htmldir = os.path.join(self._testdir, 'htmlcov')
2044 cov.html_report(directory=htmldir, omit=omit)
2046 cov.html_report(directory=htmldir, omit=omit)
2045 if self.options.annotate:
2047 if self.options.annotate:
2046 adir = os.path.join(self._testdir, 'annotated')
2048 adir = os.path.join(self._testdir, 'annotated')
2047 if not os.path.isdir(adir):
2049 if not os.path.isdir(adir):
2048 os.mkdir(adir)
2050 os.mkdir(adir)
2049 cov.annotate(directory=adir, omit=omit)
2051 cov.annotate(directory=adir, omit=omit)
2050
2052
2051 def _findprogram(self, program):
2053 def _findprogram(self, program):
2052 """Search PATH for a executable program"""
2054 """Search PATH for a executable program"""
2053 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
2055 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
2054 name = os.path.join(p, program)
2056 name = os.path.join(p, program)
2055 if os.name == 'nt' or os.access(name, os.X_OK):
2057 if os.name == 'nt' or os.access(name, os.X_OK):
2056 return name
2058 return name
2057 return None
2059 return None
2058
2060
2059 def _checktools(self):
2061 def _checktools(self):
2060 """Ensure tools required to run tests are present."""
2062 """Ensure tools required to run tests are present."""
2061 for p in self.REQUIREDTOOLS:
2063 for p in self.REQUIREDTOOLS:
2062 if os.name == 'nt' and not p.endswith('.exe'):
2064 if os.name == 'nt' and not p.endswith('.exe'):
2063 p += '.exe'
2065 p += '.exe'
2064 found = self._findprogram(p)
2066 found = self._findprogram(p)
2065 if found:
2067 if found:
2066 vlog("# Found prerequisite", p, "at", found)
2068 vlog("# Found prerequisite", p, "at", found)
2067 else:
2069 else:
2068 print "WARNING: Did not find prerequisite tool: %s " % p
2070 print "WARNING: Did not find prerequisite tool: %s " % p
2069
2071
2070 if __name__ == '__main__':
2072 if __name__ == '__main__':
2071 runner = TestRunner()
2073 runner = TestRunner()
2072
2074
2073 try:
2075 try:
2074 import msvcrt
2076 import msvcrt
2075 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2077 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2076 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2078 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2077 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2079 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2078 except ImportError:
2080 except ImportError:
2079 pass
2081 pass
2080
2082
2081 sys.exit(runner.run(sys.argv[1:]))
2083 sys.exit(runner.run(sys.argv[1:]))
@@ -1,67 +1,68 b''
1 Create a repository:
1 Create a repository:
2
2
3 $ hg config
3 $ hg config
4 defaults.backout=-d "0 0"
4 defaults.backout=-d "0 0"
5 defaults.commit=-d "0 0"
5 defaults.commit=-d "0 0"
6 defaults.shelve=--date "0 0"
6 defaults.shelve=--date "0 0"
7 defaults.tag=-d "0 0"
7 defaults.tag=-d "0 0"
8 devel.all=true
8 largefiles.usercache=$TESTTMP/.cache/largefiles (glob)
9 largefiles.usercache=$TESTTMP/.cache/largefiles (glob)
9 ui.slash=True
10 ui.slash=True
10 ui.interactive=False
11 ui.interactive=False
11 ui.mergemarkers=detailed
12 ui.mergemarkers=detailed
12 ui.promptecho=True
13 ui.promptecho=True
13 $ hg init t
14 $ hg init t
14 $ cd t
15 $ cd t
15
16
16 Make a changeset:
17 Make a changeset:
17
18
18 $ echo a > a
19 $ echo a > a
19 $ hg add a
20 $ hg add a
20 $ hg commit -m test
21 $ hg commit -m test
21
22
22 This command is ancient:
23 This command is ancient:
23
24
24 $ hg history
25 $ hg history
25 changeset: 0:acb14030fe0a
26 changeset: 0:acb14030fe0a
26 tag: tip
27 tag: tip
27 user: test
28 user: test
28 date: Thu Jan 01 00:00:00 1970 +0000
29 date: Thu Jan 01 00:00:00 1970 +0000
29 summary: test
30 summary: test
30
31
31
32
32 Verify that updating to revision 0 via commands.update() works properly
33 Verify that updating to revision 0 via commands.update() works properly
33
34
34 $ cat <<EOF > update_to_rev0.py
35 $ cat <<EOF > update_to_rev0.py
35 > from mercurial import ui, hg, commands
36 > from mercurial import ui, hg, commands
36 > myui = ui.ui()
37 > myui = ui.ui()
37 > repo = hg.repository(myui, path='.')
38 > repo = hg.repository(myui, path='.')
38 > commands.update(myui, repo, rev=0)
39 > commands.update(myui, repo, rev=0)
39 > EOF
40 > EOF
40 $ hg up null
41 $ hg up null
41 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
42 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
42 $ python ./update_to_rev0.py
43 $ python ./update_to_rev0.py
43 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
44 $ hg identify -n
45 $ hg identify -n
45 0
46 0
46
47
47
48
48 Poke around at hashes:
49 Poke around at hashes:
49
50
50 $ hg manifest --debug
51 $ hg manifest --debug
51 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
52 b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3 644 a
52
53
53 $ hg cat a
54 $ hg cat a
54 a
55 a
55
56
56 Verify should succeed:
57 Verify should succeed:
57
58
58 $ hg verify
59 $ hg verify
59 checking changesets
60 checking changesets
60 checking manifests
61 checking manifests
61 crosschecking files in changesets and manifests
62 crosschecking files in changesets and manifests
62 checking files
63 checking files
63 1 files, 1 changesets, 1 total revisions
64 1 files, 1 changesets, 1 total revisions
64
65
65 At the end...
66 At the end...
66
67
67 $ cd ..
68 $ cd ..
@@ -1,590 +1,593 b''
1 Test exchange of common information using bundle2
1 Test exchange of common information using bundle2
2
2
3
3
4 $ getmainid() {
4 $ getmainid() {
5 > hg -R main log --template '{node}\n' --rev "$1"
5 > hg -R main log --template '{node}\n' --rev "$1"
6 > }
6 > }
7
7
8 enable obsolescence
8 enable obsolescence
9
9
10 $ cat > $TESTTMP/bundle2-pushkey-hook.sh << EOF
10 $ cat > $TESTTMP/bundle2-pushkey-hook.sh << EOF
11 > echo pushkey: lock state after \"\$HG_NAMESPACE\"
11 > echo pushkey: lock state after \"\$HG_NAMESPACE\"
12 > hg debuglock
12 > hg debuglock
13 > EOF
13 > EOF
14
14
15 $ cat >> $HGRCPATH << EOF
15 $ cat >> $HGRCPATH << EOF
16 > [experimental]
16 > [experimental]
17 > evolution=createmarkers,exchange
17 > evolution=createmarkers,exchange
18 > bundle2-exp=True
18 > bundle2-exp=True
19 > [ui]
19 > [ui]
20 > ssh=python "$TESTDIR/dummyssh"
20 > ssh=python "$TESTDIR/dummyssh"
21 > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
21 > logtemplate={rev}:{node|short} {phase} {author} {bookmarks} {desc|firstline}
22 > [web]
22 > [web]
23 > push_ssl = false
23 > push_ssl = false
24 > allow_push = *
24 > allow_push = *
25 > [phases]
25 > [phases]
26 > publish=False
26 > publish=False
27 > [hooks]
27 > [hooks]
28 > pretxnclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
28 > pretxnclose.tip = hg log -r tip -T "pre-close-tip:{node|short} {phase} {bookmarks}\n"
29 > txnclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
29 > txnclose.tip = hg log -r tip -T "postclose-tip:{node|short} {phase} {bookmarks}\n"
30 > txnclose.env = sh -c "HG_LOCAL= python \"$TESTDIR/printenv.py\" txnclose"
30 > txnclose.env = sh -c "HG_LOCAL= python \"$TESTDIR/printenv.py\" txnclose"
31 > pushkey= sh "$TESTTMP/bundle2-pushkey-hook.sh"
31 > pushkey= sh "$TESTTMP/bundle2-pushkey-hook.sh"
32 > EOF
32 > EOF
33
33
34 The extension requires a repo (currently unused)
34 The extension requires a repo (currently unused)
35
35
36 $ hg init main
36 $ hg init main
37 $ cd main
37 $ cd main
38 $ touch a
38 $ touch a
39 $ hg add a
39 $ hg add a
40 $ hg commit -m 'a'
40 $ hg commit -m 'a'
41 pre-close-tip:3903775176ed draft
41 pre-close-tip:3903775176ed draft
42 postclose-tip:3903775176ed draft
42 postclose-tip:3903775176ed draft
43 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
43 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
44
44
45 $ hg unbundle $TESTDIR/bundles/rebase.hg
45 $ hg unbundle $TESTDIR/bundles/rebase.hg
46 adding changesets
46 adding changesets
47 adding manifests
47 adding manifests
48 adding file changes
48 adding file changes
49 added 8 changesets with 7 changes to 7 files (+3 heads)
49 added 8 changesets with 7 changes to 7 files (+3 heads)
50 pre-close-tip:02de42196ebe draft
50 pre-close-tip:02de42196ebe draft
51 postclose-tip:02de42196ebe draft
51 postclose-tip:02de42196ebe draft
52 txnclose hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:* HG_TXNNAME=unbundle (glob)
52 txnclose hook: HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=unbundle HG_TXNID=TXN:* HG_TXNNAME=unbundle (glob)
53 bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
53 bundle:*/tests/bundles/rebase.hg HG_URL=bundle:*/tests/bundles/rebase.hg (glob)
54 (run 'hg heads' to see heads, 'hg merge' to merge)
54 (run 'hg heads' to see heads, 'hg merge' to merge)
55
55
56 $ cd ..
56 $ cd ..
57
57
58 Real world exchange
58 Real world exchange
59 =====================
59 =====================
60
60
61 Add more obsolescence information
61 Add more obsolescence information
62
62
63 $ hg -R main debugobsolete -d '0 0' 1111111111111111111111111111111111111111 `getmainid 9520eea781bc`
63 $ hg -R main debugobsolete -d '0 0' 1111111111111111111111111111111111111111 `getmainid 9520eea781bc`
64 pre-close-tip:02de42196ebe draft
64 pre-close-tip:02de42196ebe draft
65 postclose-tip:02de42196ebe draft
65 postclose-tip:02de42196ebe draft
66 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
66 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
67 $ hg -R main debugobsolete -d '0 0' 2222222222222222222222222222222222222222 `getmainid 24b6387c8c8c`
67 $ hg -R main debugobsolete -d '0 0' 2222222222222222222222222222222222222222 `getmainid 24b6387c8c8c`
68 pre-close-tip:02de42196ebe draft
68 pre-close-tip:02de42196ebe draft
69 postclose-tip:02de42196ebe draft
69 postclose-tip:02de42196ebe draft
70 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
70 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
71
71
72 clone --pull
72 clone --pull
73
73
74 $ hg -R main phase --public cd010b8cd998
74 $ hg -R main phase --public cd010b8cd998
75 pre-close-tip:000000000000 public
75 pre-close-tip:000000000000 public
76 postclose-tip:02de42196ebe draft
76 postclose-tip:02de42196ebe draft
77 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
77 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
78 $ hg clone main other --pull --rev 9520eea781bc
78 $ hg clone main other --pull --rev 9520eea781bc
79 adding changesets
79 adding changesets
80 adding manifests
80 adding manifests
81 adding file changes
81 adding file changes
82 added 2 changesets with 2 changes to 2 files
82 added 2 changesets with 2 changes to 2 files
83 1 new obsolescence markers
83 1 new obsolescence markers
84 pre-close-tip:9520eea781bc draft
84 pre-close-tip:9520eea781bc draft
85 postclose-tip:9520eea781bc draft
85 postclose-tip:9520eea781bc draft
86 txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
86 txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=cd010b8cd998f3981a5a8115f94f8da4ab506089 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
87 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
87 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
88 updating to branch default
88 updating to branch default
89 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
89 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
90 $ hg -R other log -G
90 $ hg -R other log -G
91 @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
91 @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
92 |
92 |
93 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
93 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
94
94
95 $ hg -R other debugobsolete
95 $ hg -R other debugobsolete
96 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
96 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
97
97
98 pull
98 pull
99
99
100 $ hg -R main phase --public 9520eea781bc
100 $ hg -R main phase --public 9520eea781bc
101 pre-close-tip:000000000000 public
101 pre-close-tip:000000000000 public
102 postclose-tip:02de42196ebe draft
102 postclose-tip:02de42196ebe draft
103 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
103 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
104 $ hg -R other pull -r 24b6387c8c8c
104 $ hg -R other pull -r 24b6387c8c8c
105 pulling from $TESTTMP/main (glob)
105 pulling from $TESTTMP/main (glob)
106 searching for changes
106 searching for changes
107 adding changesets
107 adding changesets
108 adding manifests
108 adding manifests
109 adding file changes
109 adding file changes
110 added 1 changesets with 1 changes to 1 files (+1 heads)
110 added 1 changesets with 1 changes to 1 files (+1 heads)
111 1 new obsolescence markers
111 1 new obsolescence markers
112 pre-close-tip:24b6387c8c8c draft
112 pre-close-tip:24b6387c8c8c draft
113 postclose-tip:24b6387c8c8c draft
113 postclose-tip:24b6387c8c8c draft
114 txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
114 txnclose hook: HG_NEW_OBSMARKERS=1 HG_NODE=24b6387c8c8cae37178880f3fa95ded3cb1cf785 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
115 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
115 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
116 (run 'hg heads' to see heads, 'hg merge' to merge)
116 (run 'hg heads' to see heads, 'hg merge' to merge)
117 $ hg -R other log -G
117 $ hg -R other log -G
118 o 2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
118 o 2:24b6387c8c8c draft Nicolas Dumazet <nicdumz.commits@gmail.com> F
119 |
119 |
120 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
120 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
121 |/
121 |/
122 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
122 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
123
123
124 $ hg -R other debugobsolete
124 $ hg -R other debugobsolete
125 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
125 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
126 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
126 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
127
127
128 pull empty (with phase movement)
128 pull empty (with phase movement)
129
129
130 $ hg -R main phase --public 24b6387c8c8c
130 $ hg -R main phase --public 24b6387c8c8c
131 pre-close-tip:000000000000 public
131 pre-close-tip:000000000000 public
132 postclose-tip:02de42196ebe draft
132 postclose-tip:02de42196ebe draft
133 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
133 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
134 $ hg -R other pull -r 24b6387c8c8c
134 $ hg -R other pull -r 24b6387c8c8c
135 pulling from $TESTTMP/main (glob)
135 pulling from $TESTTMP/main (glob)
136 no changes found
136 no changes found
137 pre-close-tip:000000000000 public
137 pre-close-tip:000000000000 public
138 postclose-tip:24b6387c8c8c public
138 postclose-tip:24b6387c8c8c public
139 txnclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
139 txnclose hook: HG_NEW_OBSMARKERS=0 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
140 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
140 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
141 $ hg -R other log -G
141 $ hg -R other log -G
142 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
142 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
143 |
143 |
144 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
144 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
145 |/
145 |/
146 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
146 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
147
147
148 $ hg -R other debugobsolete
148 $ hg -R other debugobsolete
149 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
149 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
150 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
150 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
151
151
152 pull empty
152 pull empty
153
153
154 $ hg -R other pull -r 24b6387c8c8c
154 $ hg -R other pull -r 24b6387c8c8c
155 pulling from $TESTTMP/main (glob)
155 pulling from $TESTTMP/main (glob)
156 no changes found
156 no changes found
157 pre-close-tip:24b6387c8c8c public
157 pre-close-tip:24b6387c8c8c public
158 postclose-tip:24b6387c8c8c public
158 postclose-tip:24b6387c8c8c public
159 txnclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
159 txnclose hook: HG_NEW_OBSMARKERS=0 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
160 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
160 file:/*/$TESTTMP/main HG_URL=file:$TESTTMP/main (glob)
161 $ hg -R other log -G
161 $ hg -R other log -G
162 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
162 o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
163 |
163 |
164 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
164 | @ 1:9520eea781bc draft Nicolas Dumazet <nicdumz.commits@gmail.com> E
165 |/
165 |/
166 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
166 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
167
167
168 $ hg -R other debugobsolete
168 $ hg -R other debugobsolete
169 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
169 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
170 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
170 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
171
171
172 add extra data to test their exchange during push
172 add extra data to test their exchange during push
173
173
174 $ hg -R main bookmark --rev eea13746799a book_eea1
174 $ hg -R main bookmark --rev eea13746799a book_eea1
175 $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
175 $ hg -R main debugobsolete -d '0 0' 3333333333333333333333333333333333333333 `getmainid eea13746799a`
176 pre-close-tip:02de42196ebe draft
176 pre-close-tip:02de42196ebe draft
177 postclose-tip:02de42196ebe draft
177 postclose-tip:02de42196ebe draft
178 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
178 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
179 $ hg -R main bookmark --rev 02de42196ebe book_02de
179 $ hg -R main bookmark --rev 02de42196ebe book_02de
180 $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
180 $ hg -R main debugobsolete -d '0 0' 4444444444444444444444444444444444444444 `getmainid 02de42196ebe`
181 pre-close-tip:02de42196ebe draft book_02de
181 pre-close-tip:02de42196ebe draft book_02de
182 postclose-tip:02de42196ebe draft book_02de
182 postclose-tip:02de42196ebe draft book_02de
183 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
183 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
184 $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
184 $ hg -R main bookmark --rev 42ccdea3bb16 book_42cc
185 $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
185 $ hg -R main debugobsolete -d '0 0' 5555555555555555555555555555555555555555 `getmainid 42ccdea3bb16`
186 pre-close-tip:02de42196ebe draft book_02de
186 pre-close-tip:02de42196ebe draft book_02de
187 postclose-tip:02de42196ebe draft book_02de
187 postclose-tip:02de42196ebe draft book_02de
188 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
188 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
189 $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
189 $ hg -R main bookmark --rev 5fddd98957c8 book_5fdd
190 $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
190 $ hg -R main debugobsolete -d '0 0' 6666666666666666666666666666666666666666 `getmainid 5fddd98957c8`
191 pre-close-tip:02de42196ebe draft book_02de
191 pre-close-tip:02de42196ebe draft book_02de
192 postclose-tip:02de42196ebe draft book_02de
192 postclose-tip:02de42196ebe draft book_02de
193 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
193 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
194 $ hg -R main bookmark --rev 32af7686d403 book_32af
194 $ hg -R main bookmark --rev 32af7686d403 book_32af
195 $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
195 $ hg -R main debugobsolete -d '0 0' 7777777777777777777777777777777777777777 `getmainid 32af7686d403`
196 pre-close-tip:02de42196ebe draft book_02de
196 pre-close-tip:02de42196ebe draft book_02de
197 postclose-tip:02de42196ebe draft book_02de
197 postclose-tip:02de42196ebe draft book_02de
198 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
198 txnclose hook: HG_NEW_OBSMARKERS=1 HG_TXNID=TXN:* HG_TXNNAME=debugobsolete (glob)
199
199
200 $ hg -R other bookmark --rev cd010b8cd998 book_eea1
200 $ hg -R other bookmark --rev cd010b8cd998 book_eea1
201 $ hg -R other bookmark --rev cd010b8cd998 book_02de
201 $ hg -R other bookmark --rev cd010b8cd998 book_02de
202 $ hg -R other bookmark --rev cd010b8cd998 book_42cc
202 $ hg -R other bookmark --rev cd010b8cd998 book_42cc
203 $ hg -R other bookmark --rev cd010b8cd998 book_5fdd
203 $ hg -R other bookmark --rev cd010b8cd998 book_5fdd
204 $ hg -R other bookmark --rev cd010b8cd998 book_32af
204 $ hg -R other bookmark --rev cd010b8cd998 book_32af
205
205
206 $ hg -R main phase --public eea13746799a
206 $ hg -R main phase --public eea13746799a
207 pre-close-tip:000000000000 public
207 pre-close-tip:000000000000 public
208 postclose-tip:02de42196ebe draft book_02de
208 postclose-tip:02de42196ebe draft book_02de
209 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
209 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
210
210
211 push
211 push
212 $ hg -R main push other --rev eea13746799a --bookmark book_eea1
212 $ hg -R main push other --rev eea13746799a --bookmark book_eea1
213 pushing to other
213 pushing to other
214 searching for changes
214 searching for changes
215 pre-close-tip:eea13746799a public book_eea1
215 pre-close-tip:eea13746799a public book_eea1
216 pushkey: lock state after "phases"
216 pushkey: lock state after "phases"
217 lock: free
217 lock: free
218 wlock: free
218 wlock: free
219 pushkey: lock state after "bookmarks"
219 pushkey: lock state after "bookmarks"
220 lock: free
220 lock: free
221 wlock: free
221 wlock: free
222 postclose-tip:eea13746799a public book_eea1
222 postclose-tip:eea13746799a public book_eea1
223 txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob)
223 txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=eea13746799a9e0bfd88f29d3c2e9dc9389f524f HG_PHASES_MOVED=1 HG_SOURCE=push HG_TXNID=TXN:* HG_TXNNAME=push HG_URL=push (glob)
224 remote: adding changesets
224 remote: adding changesets
225 remote: adding manifests
225 remote: adding manifests
226 remote: adding file changes
226 remote: adding file changes
227 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
227 remote: added 1 changesets with 0 changes to 0 files (-1 heads)
228 remote: 1 new obsolescence markers
228 remote: 1 new obsolescence markers
229 remote: "wlock" acquired after "lock" at: */mercurial/bookmarks.py:259 (pushbookmark) (glob)
229 updating bookmark book_eea1
230 updating bookmark book_eea1
230 pre-close-tip:02de42196ebe draft book_02de
231 pre-close-tip:02de42196ebe draft book_02de
231 postclose-tip:02de42196ebe draft book_02de
232 postclose-tip:02de42196ebe draft book_02de
232 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
233 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
233 file:/*/$TESTTMP/other HG_URL=file:$TESTTMP/other (glob)
234 file:/*/$TESTTMP/other HG_URL=file:$TESTTMP/other (glob)
234 $ hg -R other log -G
235 $ hg -R other log -G
235 o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
236 o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
236 |\
237 |\
237 | o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
238 | o 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
238 | |
239 | |
239 @ | 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
240 @ | 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
240 |/
241 |/
241 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de book_32af book_42cc book_5fdd A
242 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de book_32af book_42cc book_5fdd A
242
243
243 $ hg -R other debugobsolete
244 $ hg -R other debugobsolete
244 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
245 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
245 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
246 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
246 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
247 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
247
248
248 pull over ssh
249 pull over ssh
249
250
250 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --bookmark book_02de
251 $ hg -R other pull ssh://user@dummy/main -r 02de42196ebe --bookmark book_02de
251 pulling from ssh://user@dummy/main
252 pulling from ssh://user@dummy/main
252 searching for changes
253 searching for changes
253 adding changesets
254 adding changesets
254 adding manifests
255 adding manifests
255 adding file changes
256 adding file changes
256 added 1 changesets with 1 changes to 1 files (+1 heads)
257 added 1 changesets with 1 changes to 1 files (+1 heads)
257 1 new obsolescence markers
258 1 new obsolescence markers
258 updating bookmark book_02de
259 updating bookmark book_02de
259 pre-close-tip:02de42196ebe draft book_02de
260 pre-close-tip:02de42196ebe draft book_02de
260 postclose-tip:02de42196ebe draft book_02de
261 postclose-tip:02de42196ebe draft book_02de
261 txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
262 txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=02de42196ebee42ef284b6780a87cdc96e8eaab6 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
262 ssh://user@dummy/main HG_URL=ssh://user@dummy/main
263 ssh://user@dummy/main HG_URL=ssh://user@dummy/main
263 (run 'hg heads' to see heads, 'hg merge' to merge)
264 (run 'hg heads' to see heads, 'hg merge' to merge)
264 $ hg -R other debugobsolete
265 $ hg -R other debugobsolete
265 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
266 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
266 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
267 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
267 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
268 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
268 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
269 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
269
270
270 pull over http
271 pull over http
271
272
272 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
273 $ hg -R main serve -p $HGPORT -d --pid-file=main.pid -E main-error.log
273 $ cat main.pid >> $DAEMON_PIDS
274 $ cat main.pid >> $DAEMON_PIDS
274
275
275 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16 --bookmark book_42cc
276 $ hg -R other pull http://localhost:$HGPORT/ -r 42ccdea3bb16 --bookmark book_42cc
276 pulling from http://localhost:$HGPORT/
277 pulling from http://localhost:$HGPORT/
277 searching for changes
278 searching for changes
278 adding changesets
279 adding changesets
279 adding manifests
280 adding manifests
280 adding file changes
281 adding file changes
281 added 1 changesets with 1 changes to 1 files (+1 heads)
282 added 1 changesets with 1 changes to 1 files (+1 heads)
282 1 new obsolescence markers
283 1 new obsolescence markers
283 updating bookmark book_42cc
284 updating bookmark book_42cc
284 pre-close-tip:42ccdea3bb16 draft book_42cc
285 pre-close-tip:42ccdea3bb16 draft book_42cc
285 postclose-tip:42ccdea3bb16 draft book_42cc
286 postclose-tip:42ccdea3bb16 draft book_42cc
286 txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
287 txnclose hook: HG_BOOKMARK_MOVED=1 HG_NEW_OBSMARKERS=1 HG_NODE=42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 HG_PHASES_MOVED=1 HG_SOURCE=pull HG_TXNID=TXN:* HG_TXNNAME=pull (glob)
287 http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
288 http://localhost:$HGPORT/ HG_URL=http://localhost:$HGPORT/
288 (run 'hg heads .' to see heads, 'hg merge' to merge)
289 (run 'hg heads .' to see heads, 'hg merge' to merge)
289 $ cat main-error.log
290 $ cat main-error.log
290 $ hg -R other debugobsolete
291 $ hg -R other debugobsolete
291 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
292 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
292 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
293 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
293 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
294 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
294 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
295 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
295 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
296 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
296
297
297 push over ssh
298 push over ssh
298
299
299 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8 --bookmark book_5fdd
300 $ hg -R main push ssh://user@dummy/other -r 5fddd98957c8 --bookmark book_5fdd
300 pushing to ssh://user@dummy/other
301 pushing to ssh://user@dummy/other
301 searching for changes
302 searching for changes
302 remote: adding changesets
303 remote: adding changesets
303 remote: adding manifests
304 remote: adding manifests
304 remote: adding file changes
305 remote: adding file changes
305 remote: added 1 changesets with 1 changes to 1 files
306 remote: added 1 changesets with 1 changes to 1 files
306 remote: 1 new obsolescence markers
307 remote: 1 new obsolescence markers
308 remote: "wlock" acquired after "lock" at: */mercurial/bookmarks.py:259 (pushbookmark) (glob)
307 updating bookmark book_5fdd
309 updating bookmark book_5fdd
308 remote: pre-close-tip:5fddd98957c8 draft book_5fdd
310 remote: pre-close-tip:5fddd98957c8 draft book_5fdd
309 remote: pushkey: lock state after "bookmarks"
311 remote: pushkey: lock state after "bookmarks"
310 remote: lock: free
312 remote: lock: free
311 remote: wlock: free
313 remote: wlock: free
312 remote: postclose-tip:5fddd98957c8 draft book_5fdd
314 remote: postclose-tip:5fddd98957c8 draft book_5fdd
313 remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_TXNID=TXN:* HG_TXNNAME=serve HG_URL=remote:ssh:127.0.0.1 (glob)
315 remote: txnclose hook: HG_BOOKMARK_MOVED=1 HG_BUNDLE2=1 HG_NEW_OBSMARKERS=1 HG_NODE=5fddd98957c8a54a4d436dfe1da9d87f21a1b97b HG_SOURCE=serve HG_TXNID=TXN:* HG_TXNNAME=serve HG_URL=remote:ssh:127.0.0.1 (glob)
314 pre-close-tip:02de42196ebe draft book_02de
316 pre-close-tip:02de42196ebe draft book_02de
315 postclose-tip:02de42196ebe draft book_02de
317 postclose-tip:02de42196ebe draft book_02de
316 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
318 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
317 ssh://user@dummy/other HG_URL=ssh://user@dummy/other
319 ssh://user@dummy/other HG_URL=ssh://user@dummy/other
318 $ hg -R other log -G
320 $ hg -R other log -G
319 o 6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
321 o 6:5fddd98957c8 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
320 |
322 |
321 o 5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
323 o 5:42ccdea3bb16 draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
322 |
324 |
323 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
325 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
324 | |
326 | |
325 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
327 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
326 | |/|
328 | |/|
327 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
329 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
328 |/ /
330 |/ /
329 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
331 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
330 |/
332 |/
331 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af A
333 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af A
332
334
333 $ hg -R other debugobsolete
335 $ hg -R other debugobsolete
334 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
336 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
335 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
337 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
336 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
338 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
337 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
339 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
338 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
340 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
339 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
341 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
340
342
341 push over http
343 push over http
342
344
343 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
345 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
344 $ cat other.pid >> $DAEMON_PIDS
346 $ cat other.pid >> $DAEMON_PIDS
345
347
346 $ hg -R main phase --public 32af7686d403
348 $ hg -R main phase --public 32af7686d403
347 pre-close-tip:000000000000 public
349 pre-close-tip:000000000000 public
348 postclose-tip:02de42196ebe draft book_02de
350 postclose-tip:02de42196ebe draft book_02de
349 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
351 txnclose hook: HG_PHASES_MOVED=1 HG_TXNID=TXN:* HG_TXNNAME=phase (glob)
350 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 --bookmark book_32af
352 $ hg -R main push http://localhost:$HGPORT2/ -r 32af7686d403 --bookmark book_32af
351 pushing to http://localhost:$HGPORT2/
353 pushing to http://localhost:$HGPORT2/
352 searching for changes
354 searching for changes
353 remote: adding changesets
355 remote: adding changesets
354 remote: adding manifests
356 remote: adding manifests
355 remote: adding file changes
357 remote: adding file changes
356 remote: added 1 changesets with 1 changes to 1 files
358 remote: added 1 changesets with 1 changes to 1 files
357 remote: 1 new obsolescence markers
359 remote: 1 new obsolescence markers
360 remote: "wlock" acquired after "lock" at: */mercurial/bookmarks.py:259 (pushbookmark) (glob)
358 updating bookmark book_32af
361 updating bookmark book_32af
359 pre-close-tip:02de42196ebe draft book_02de
362 pre-close-tip:02de42196ebe draft book_02de
360 postclose-tip:02de42196ebe draft book_02de
363 postclose-tip:02de42196ebe draft book_02de
361 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
364 txnclose hook: HG_SOURCE=push-response HG_TXNID=TXN:* HG_TXNNAME=push-response (glob)
362 http://localhost:$HGPORT2/ HG_URL=http://localhost:$HGPORT2/
365 http://localhost:$HGPORT2/ HG_URL=http://localhost:$HGPORT2/
363 $ cat other-error.log
366 $ cat other-error.log
364
367
365 Check final content.
368 Check final content.
366
369
367 $ hg -R other log -G
370 $ hg -R other log -G
368 o 7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af D
371 o 7:32af7686d403 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_32af D
369 |
372 |
370 o 6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
373 o 6:5fddd98957c8 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_5fdd C
371 |
374 |
372 o 5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
375 o 5:42ccdea3bb16 public Nicolas Dumazet <nicdumz.commits@gmail.com> book_42cc B
373 |
376 |
374 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
377 | o 4:02de42196ebe draft Nicolas Dumazet <nicdumz.commits@gmail.com> book_02de H
375 | |
378 | |
376 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
379 | | o 3:eea13746799a public Nicolas Dumazet <nicdumz.commits@gmail.com> book_eea1 G
377 | |/|
380 | |/|
378 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
381 | o | 2:24b6387c8c8c public Nicolas Dumazet <nicdumz.commits@gmail.com> F
379 |/ /
382 |/ /
380 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
383 | @ 1:9520eea781bc public Nicolas Dumazet <nicdumz.commits@gmail.com> E
381 |/
384 |/
382 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
385 o 0:cd010b8cd998 public Nicolas Dumazet <nicdumz.commits@gmail.com> A
383
386
384 $ hg -R other debugobsolete
387 $ hg -R other debugobsolete
385 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
388 1111111111111111111111111111111111111111 9520eea781bcca16c1e15acc0ba14335a0e8e5ba 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
386 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
389 2222222222222222222222222222222222222222 24b6387c8c8cae37178880f3fa95ded3cb1cf785 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
387 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
390 3333333333333333333333333333333333333333 eea13746799a9e0bfd88f29d3c2e9dc9389f524f 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
388 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
391 4444444444444444444444444444444444444444 02de42196ebee42ef284b6780a87cdc96e8eaab6 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
389 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
392 5555555555555555555555555555555555555555 42ccdea3bb16d28e1848c95fe2e44c000f3f21b1 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
390 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
393 6666666666666666666666666666666666666666 5fddd98957c8a54a4d436dfe1da9d87f21a1b97b 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
391 7777777777777777777777777777777777777777 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
394 7777777777777777777777777777777777777777 32af7686d403cf45b5d95f2d70cebea587ac806a 0 (Thu Jan 01 00:00:00 1970 +0000) {'user': 'test'}
392
395
393 (check that no 'pending' files remain)
396 (check that no 'pending' files remain)
394
397
395 $ ls -1 other/.hg/bookmarks*
398 $ ls -1 other/.hg/bookmarks*
396 other/.hg/bookmarks
399 other/.hg/bookmarks
397 $ ls -1 other/.hg/store/phaseroots*
400 $ ls -1 other/.hg/store/phaseroots*
398 other/.hg/store/phaseroots
401 other/.hg/store/phaseroots
399 $ ls -1 other/.hg/store/00changelog.i*
402 $ ls -1 other/.hg/store/00changelog.i*
400 other/.hg/store/00changelog.i
403 other/.hg/store/00changelog.i
401
404
402 Error Handling
405 Error Handling
403 ==============
406 ==============
404
407
405 Check that errors are properly returned to the client during push.
408 Check that errors are properly returned to the client during push.
406
409
407 Setting up
410 Setting up
408
411
409 $ cat > failpush.py << EOF
412 $ cat > failpush.py << EOF
410 > """A small extension that makes push fails when using bundle2
413 > """A small extension that makes push fails when using bundle2
411 >
414 >
412 > used to test error handling in bundle2
415 > used to test error handling in bundle2
413 > """
416 > """
414 >
417 >
415 > from mercurial import util
418 > from mercurial import util
416 > from mercurial import bundle2
419 > from mercurial import bundle2
417 > from mercurial import exchange
420 > from mercurial import exchange
418 > from mercurial import extensions
421 > from mercurial import extensions
419 >
422 >
420 > def _pushbundle2failpart(pushop, bundler):
423 > def _pushbundle2failpart(pushop, bundler):
421 > reason = pushop.ui.config('failpush', 'reason', None)
424 > reason = pushop.ui.config('failpush', 'reason', None)
422 > part = None
425 > part = None
423 > if reason == 'abort':
426 > if reason == 'abort':
424 > bundler.newpart('test:abort')
427 > bundler.newpart('test:abort')
425 > if reason == 'unknown':
428 > if reason == 'unknown':
426 > bundler.newpart('test:unknown')
429 > bundler.newpart('test:unknown')
427 > if reason == 'race':
430 > if reason == 'race':
428 > # 20 Bytes of crap
431 > # 20 Bytes of crap
429 > bundler.newpart('check:heads', data='01234567890123456789')
432 > bundler.newpart('check:heads', data='01234567890123456789')
430 >
433 >
431 > @bundle2.parthandler("test:abort")
434 > @bundle2.parthandler("test:abort")
432 > def handleabort(op, part):
435 > def handleabort(op, part):
433 > raise util.Abort('Abandon ship!', hint="don't panic")
436 > raise util.Abort('Abandon ship!', hint="don't panic")
434 >
437 >
435 > def uisetup(ui):
438 > def uisetup(ui):
436 > exchange.b2partsgenmapping['failpart'] = _pushbundle2failpart
439 > exchange.b2partsgenmapping['failpart'] = _pushbundle2failpart
437 > exchange.b2partsgenorder.insert(0, 'failpart')
440 > exchange.b2partsgenorder.insert(0, 'failpart')
438 >
441 >
439 > EOF
442 > EOF
440
443
441 $ cd main
444 $ cd main
442 $ hg up tip
445 $ hg up tip
443 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
446 3 files updated, 0 files merged, 1 files removed, 0 files unresolved
444 $ echo 'I' > I
447 $ echo 'I' > I
445 $ hg add I
448 $ hg add I
446 $ hg ci -m 'I'
449 $ hg ci -m 'I'
447 pre-close-tip:e7ec4e813ba6 draft
450 pre-close-tip:e7ec4e813ba6 draft
448 postclose-tip:e7ec4e813ba6 draft
451 postclose-tip:e7ec4e813ba6 draft
449 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
452 txnclose hook: HG_TXNID=TXN:* HG_TXNNAME=commit (glob)
450 $ hg id
453 $ hg id
451 e7ec4e813ba6 tip
454 e7ec4e813ba6 tip
452 $ cd ..
455 $ cd ..
453
456
454 $ cat << EOF >> $HGRCPATH
457 $ cat << EOF >> $HGRCPATH
455 > [extensions]
458 > [extensions]
456 > failpush=$TESTTMP/failpush.py
459 > failpush=$TESTTMP/failpush.py
457 > EOF
460 > EOF
458
461
459 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
462 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
460 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
463 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
461 $ cat other.pid >> $DAEMON_PIDS
464 $ cat other.pid >> $DAEMON_PIDS
462
465
463 Doing the actual push: Abort error
466 Doing the actual push: Abort error
464
467
465 $ cat << EOF >> $HGRCPATH
468 $ cat << EOF >> $HGRCPATH
466 > [failpush]
469 > [failpush]
467 > reason = abort
470 > reason = abort
468 > EOF
471 > EOF
469
472
470 $ hg -R main push other -r e7ec4e813ba6
473 $ hg -R main push other -r e7ec4e813ba6
471 pushing to other
474 pushing to other
472 searching for changes
475 searching for changes
473 abort: Abandon ship!
476 abort: Abandon ship!
474 (don't panic)
477 (don't panic)
475 [255]
478 [255]
476
479
477 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
480 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
478 pushing to ssh://user@dummy/other
481 pushing to ssh://user@dummy/other
479 searching for changes
482 searching for changes
480 abort: Abandon ship!
483 abort: Abandon ship!
481 (don't panic)
484 (don't panic)
482 [255]
485 [255]
483
486
484 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
487 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
485 pushing to http://localhost:$HGPORT2/
488 pushing to http://localhost:$HGPORT2/
486 searching for changes
489 searching for changes
487 abort: Abandon ship!
490 abort: Abandon ship!
488 (don't panic)
491 (don't panic)
489 [255]
492 [255]
490
493
491
494
492 Doing the actual push: unknown mandatory parts
495 Doing the actual push: unknown mandatory parts
493
496
494 $ cat << EOF >> $HGRCPATH
497 $ cat << EOF >> $HGRCPATH
495 > [failpush]
498 > [failpush]
496 > reason = unknown
499 > reason = unknown
497 > EOF
500 > EOF
498
501
499 $ hg -R main push other -r e7ec4e813ba6
502 $ hg -R main push other -r e7ec4e813ba6
500 pushing to other
503 pushing to other
501 searching for changes
504 searching for changes
502 abort: missing support for test:unknown
505 abort: missing support for test:unknown
503 [255]
506 [255]
504
507
505 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
508 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
506 pushing to ssh://user@dummy/other
509 pushing to ssh://user@dummy/other
507 searching for changes
510 searching for changes
508 abort: missing support for test:unknown
511 abort: missing support for test:unknown
509 [255]
512 [255]
510
513
511 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
514 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
512 pushing to http://localhost:$HGPORT2/
515 pushing to http://localhost:$HGPORT2/
513 searching for changes
516 searching for changes
514 abort: missing support for test:unknown
517 abort: missing support for test:unknown
515 [255]
518 [255]
516
519
517 Doing the actual push: race
520 Doing the actual push: race
518
521
519 $ cat << EOF >> $HGRCPATH
522 $ cat << EOF >> $HGRCPATH
520 > [failpush]
523 > [failpush]
521 > reason = race
524 > reason = race
522 > EOF
525 > EOF
523
526
524 $ hg -R main push other -r e7ec4e813ba6
527 $ hg -R main push other -r e7ec4e813ba6
525 pushing to other
528 pushing to other
526 searching for changes
529 searching for changes
527 abort: push failed:
530 abort: push failed:
528 'repository changed while pushing - please try again'
531 'repository changed while pushing - please try again'
529 [255]
532 [255]
530
533
531 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
534 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
532 pushing to ssh://user@dummy/other
535 pushing to ssh://user@dummy/other
533 searching for changes
536 searching for changes
534 abort: push failed:
537 abort: push failed:
535 'repository changed while pushing - please try again'
538 'repository changed while pushing - please try again'
536 [255]
539 [255]
537
540
538 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
541 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
539 pushing to http://localhost:$HGPORT2/
542 pushing to http://localhost:$HGPORT2/
540 searching for changes
543 searching for changes
541 abort: push failed:
544 abort: push failed:
542 'repository changed while pushing - please try again'
545 'repository changed while pushing - please try again'
543 [255]
546 [255]
544
547
545 Doing the actual push: hook abort
548 Doing the actual push: hook abort
546
549
547 $ cat << EOF >> $HGRCPATH
550 $ cat << EOF >> $HGRCPATH
548 > [failpush]
551 > [failpush]
549 > reason =
552 > reason =
550 > [hooks]
553 > [hooks]
551 > pretxnclose.failpush = false
554 > pretxnclose.failpush = false
552 > EOF
555 > EOF
553
556
554 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
557 $ "$TESTDIR/killdaemons.py" $DAEMON_PIDS
555 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
558 $ hg -R other serve -p $HGPORT2 -d --pid-file=other.pid -E other-error.log
556 $ cat other.pid >> $DAEMON_PIDS
559 $ cat other.pid >> $DAEMON_PIDS
557
560
558 $ hg -R main push other -r e7ec4e813ba6
561 $ hg -R main push other -r e7ec4e813ba6
559 pushing to other
562 pushing to other
560 searching for changes
563 searching for changes
561 pre-close-tip:e7ec4e813ba6 draft
564 pre-close-tip:e7ec4e813ba6 draft
562 transaction abort!
565 transaction abort!
563 rollback completed
566 rollback completed
564 abort: pretxnclose.failpush hook exited with status 1
567 abort: pretxnclose.failpush hook exited with status 1
565 [255]
568 [255]
566
569
567 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
570 $ hg -R main push ssh://user@dummy/other -r e7ec4e813ba6
568 pushing to ssh://user@dummy/other
571 pushing to ssh://user@dummy/other
569 searching for changes
572 searching for changes
570 abort: pretxnclose.failpush hook exited with status 1
573 abort: pretxnclose.failpush hook exited with status 1
571 remote: pre-close-tip:e7ec4e813ba6 draft
574 remote: pre-close-tip:e7ec4e813ba6 draft
572 remote: transaction abort!
575 remote: transaction abort!
573 remote: rollback completed
576 remote: rollback completed
574 [255]
577 [255]
575
578
576 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
579 $ hg -R main push http://localhost:$HGPORT2/ -r e7ec4e813ba6
577 pushing to http://localhost:$HGPORT2/
580 pushing to http://localhost:$HGPORT2/
578 searching for changes
581 searching for changes
579 abort: pretxnclose.failpush hook exited with status 1
582 abort: pretxnclose.failpush hook exited with status 1
580 [255]
583 [255]
581
584
582 (check that no 'pending' files remain)
585 (check that no 'pending' files remain)
583
586
584 $ ls -1 other/.hg/bookmarks*
587 $ ls -1 other/.hg/bookmarks*
585 other/.hg/bookmarks
588 other/.hg/bookmarks
586 $ ls -1 other/.hg/store/phaseroots*
589 $ ls -1 other/.hg/store/phaseroots*
587 other/.hg/store/phaseroots
590 other/.hg/store/phaseroots
588 $ ls -1 other/.hg/store/00changelog.i*
591 $ ls -1 other/.hg/store/00changelog.i*
589 other/.hg/store/00changelog.i
592 other/.hg/store/00changelog.i
590
593
@@ -1,111 +1,112 b''
1 $ cat > bundle2.py << EOF
1 $ cat > bundle2.py << EOF
2 > """A small extension to test bundle2 pushback parts.
2 > """A small extension to test bundle2 pushback parts.
3 > Current bundle2 implementation doesn't provide a way to generate those
3 > Current bundle2 implementation doesn't provide a way to generate those
4 > parts, so they must be created by extensions.
4 > parts, so they must be created by extensions.
5 > """
5 > """
6 > from mercurial import bundle2, pushkey, exchange, util
6 > from mercurial import bundle2, pushkey, exchange, util
7 > def _newhandlechangegroup(op, inpart):
7 > def _newhandlechangegroup(op, inpart):
8 > """This function wraps the changegroup part handler for getbundle.
8 > """This function wraps the changegroup part handler for getbundle.
9 > It issues an additional pushkey part to send a new
9 > It issues an additional pushkey part to send a new
10 > bookmark back to the client"""
10 > bookmark back to the client"""
11 > result = bundle2.handlechangegroup(op, inpart)
11 > result = bundle2.handlechangegroup(op, inpart)
12 > if 'pushback' in op.reply.capabilities:
12 > if 'pushback' in op.reply.capabilities:
13 > params = {'namespace': 'bookmarks',
13 > params = {'namespace': 'bookmarks',
14 > 'key': 'new-server-mark',
14 > 'key': 'new-server-mark',
15 > 'old': '',
15 > 'old': '',
16 > 'new': 'tip'}
16 > 'new': 'tip'}
17 > encodedparams = [(k, pushkey.encode(v)) for (k,v) in params.items()]
17 > encodedparams = [(k, pushkey.encode(v)) for (k,v) in params.items()]
18 > op.reply.newpart('pushkey', mandatoryparams=encodedparams)
18 > op.reply.newpart('pushkey', mandatoryparams=encodedparams)
19 > else:
19 > else:
20 > op.reply.newpart('output', data='pushback not enabled')
20 > op.reply.newpart('output', data='pushback not enabled')
21 > return result
21 > return result
22 > _newhandlechangegroup.params = bundle2.handlechangegroup.params
22 > _newhandlechangegroup.params = bundle2.handlechangegroup.params
23 > bundle2.parthandlermapping['changegroup'] = _newhandlechangegroup
23 > bundle2.parthandlermapping['changegroup'] = _newhandlechangegroup
24 > EOF
24 > EOF
25
25
26 $ cat >> $HGRCPATH <<EOF
26 $ cat >> $HGRCPATH <<EOF
27 > [ui]
27 > [ui]
28 > ssh = python "$TESTDIR/dummyssh"
28 > ssh = python "$TESTDIR/dummyssh"
29 > username = nobody <no.reply@example.com>
29 > username = nobody <no.reply@example.com>
30 >
30 >
31 > [alias]
31 > [alias]
32 > tglog = log -G -T "{desc} [{phase}:{node|short}]"
32 > tglog = log -G -T "{desc} [{phase}:{node|short}]"
33 > EOF
33 > EOF
34
34
35 Set up server repository
35 Set up server repository
36
36
37 $ hg init server
37 $ hg init server
38 $ cd server
38 $ cd server
39 $ echo c0 > f0
39 $ echo c0 > f0
40 $ hg commit -Am 0
40 $ hg commit -Am 0
41 adding f0
41 adding f0
42
42
43 Set up client repository
43 Set up client repository
44
44
45 $ cd ..
45 $ cd ..
46 $ hg clone ssh://user@dummy/server client -q
46 $ hg clone ssh://user@dummy/server client -q
47 $ cd client
47 $ cd client
48
48
49 Enable extension
49 Enable extension
50 $ cat >> $HGRCPATH <<EOF
50 $ cat >> $HGRCPATH <<EOF
51 > [extensions]
51 > [extensions]
52 > bundle2=$TESTTMP/bundle2.py
52 > bundle2=$TESTTMP/bundle2.py
53 > [experimental]
53 > [experimental]
54 > bundle2-exp = True
54 > bundle2-exp = True
55 > EOF
55 > EOF
56
56
57 Without config
57 Without config
58
58
59 $ cd ../client
59 $ cd ../client
60 $ echo c1 > f1
60 $ echo c1 > f1
61 $ hg commit -Am 1
61 $ hg commit -Am 1
62 adding f1
62 adding f1
63 $ hg push
63 $ hg push
64 pushing to ssh://user@dummy/server
64 pushing to ssh://user@dummy/server
65 searching for changes
65 searching for changes
66 remote: pushback not enabled
66 remote: pushback not enabled
67 remote: adding changesets
67 remote: adding changesets
68 remote: adding manifests
68 remote: adding manifests
69 remote: adding file changes
69 remote: adding file changes
70 remote: added 1 changesets with 1 changes to 1 files
70 remote: added 1 changesets with 1 changes to 1 files
71 $ hg bookmark
71 $ hg bookmark
72 no bookmarks set
72 no bookmarks set
73
73
74 $ cd ../server
74 $ cd ../server
75 $ hg tglog
75 $ hg tglog
76 o 1 [public:2b9c7234e035]
76 o 1 [public:2b9c7234e035]
77 |
77 |
78 @ 0 [public:6cee5c8f3e5b]
78 @ 0 [public:6cee5c8f3e5b]
79
79
80
80
81
81
82
82
83 With config
83 With config
84
84
85 $ cd ../client
85 $ cd ../client
86 $ echo '[experimental]' >> .hg/hgrc
86 $ echo '[experimental]' >> .hg/hgrc
87 $ echo 'bundle2.pushback = True' >> .hg/hgrc
87 $ echo 'bundle2.pushback = True' >> .hg/hgrc
88 $ echo c2 > f2
88 $ echo c2 > f2
89 $ hg commit -Am 2
89 $ hg commit -Am 2
90 adding f2
90 adding f2
91 $ hg push
91 $ hg push
92 pushing to ssh://user@dummy/server
92 pushing to ssh://user@dummy/server
93 searching for changes
93 searching for changes
94 "wlock" acquired after "lock" at: */mercurial/bookmarks.py:259 (pushbookmark) (glob)
94 remote: adding changesets
95 remote: adding changesets
95 remote: adding manifests
96 remote: adding manifests
96 remote: adding file changes
97 remote: adding file changes
97 remote: added 1 changesets with 1 changes to 1 files
98 remote: added 1 changesets with 1 changes to 1 files
98 $ hg bookmark
99 $ hg bookmark
99 new-server-mark 2:0a76dfb2e179
100 new-server-mark 2:0a76dfb2e179
100
101
101 $ cd ../server
102 $ cd ../server
102 $ hg tglog
103 $ hg tglog
103 o 2 [public:0a76dfb2e179]
104 o 2 [public:0a76dfb2e179]
104 |
105 |
105 o 1 [public:2b9c7234e035]
106 o 1 [public:2b9c7234e035]
106 |
107 |
107 @ 0 [public:6cee5c8f3e5b]
108 @ 0 [public:6cee5c8f3e5b]
108
109
109
110
110
111
111
112
@@ -1,648 +1,649 b''
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
8 typical client does not want echo-back messages, so test without it:
9
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
11 $ mv $HGRCPATH.new $HGRCPATH
12
12
13 $ hg init repo
13 $ hg init repo
14 $ cd repo
14 $ cd repo
15
15
16 >>> from hgclient import readchannel, runcommand, check
16 >>> from hgclient import readchannel, runcommand, check
17 >>> @check
17 >>> @check
18 ... def hellomessage(server):
18 ... def hellomessage(server):
19 ... ch, data = readchannel(server)
19 ... ch, data = readchannel(server)
20 ... print '%c, %r' % (ch, data)
20 ... print '%c, %r' % (ch, data)
21 ... # run an arbitrary command to make sure the next thing the server
21 ... # run an arbitrary command to make sure the next thing the server
22 ... # sends isn't part of the hello message
22 ... # sends isn't part of the hello message
23 ... runcommand(server, ['id'])
23 ... runcommand(server, ['id'])
24 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
24 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
25 *** runcommand id
25 *** runcommand id
26 000000000000 tip
26 000000000000 tip
27
27
28 >>> from hgclient import check
28 >>> from hgclient import check
29 >>> @check
29 >>> @check
30 ... def unknowncommand(server):
30 ... def unknowncommand(server):
31 ... server.stdin.write('unknowncommand\n')
31 ... server.stdin.write('unknowncommand\n')
32 abort: unknown command unknowncommand
32 abort: unknown command unknowncommand
33
33
34 >>> from hgclient import readchannel, runcommand, check
34 >>> from hgclient import readchannel, runcommand, check
35 >>> @check
35 >>> @check
36 ... def checkruncommand(server):
36 ... def checkruncommand(server):
37 ... # hello block
37 ... # hello block
38 ... readchannel(server)
38 ... readchannel(server)
39 ...
39 ...
40 ... # no args
40 ... # no args
41 ... runcommand(server, [])
41 ... runcommand(server, [])
42 ...
42 ...
43 ... # global options
43 ... # global options
44 ... runcommand(server, ['id', '--quiet'])
44 ... runcommand(server, ['id', '--quiet'])
45 ...
45 ...
46 ... # make sure global options don't stick through requests
46 ... # make sure global options don't stick through requests
47 ... runcommand(server, ['id'])
47 ... runcommand(server, ['id'])
48 ...
48 ...
49 ... # --config
49 ... # --config
50 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
50 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
51 ...
51 ...
52 ... # make sure --config doesn't stick
52 ... # make sure --config doesn't stick
53 ... runcommand(server, ['id'])
53 ... runcommand(server, ['id'])
54 ...
54 ...
55 ... # negative return code should be masked
55 ... # negative return code should be masked
56 ... runcommand(server, ['id', '-runknown'])
56 ... runcommand(server, ['id', '-runknown'])
57 *** runcommand
57 *** runcommand
58 Mercurial Distributed SCM
58 Mercurial Distributed SCM
59
59
60 basic commands:
60 basic commands:
61
61
62 add add the specified files on the next commit
62 add add the specified files on the next commit
63 annotate show changeset information by line for each file
63 annotate show changeset information by line for each file
64 clone make a copy of an existing repository
64 clone make a copy of an existing repository
65 commit commit the specified files or all outstanding changes
65 commit commit the specified files or all outstanding changes
66 diff diff repository (or selected files)
66 diff diff repository (or selected files)
67 export dump the header and diffs for one or more changesets
67 export dump the header and diffs for one or more changesets
68 forget forget the specified files on the next commit
68 forget forget the specified files on the next commit
69 init create a new repository in the given directory
69 init create a new repository in the given directory
70 log show revision history of entire repository or files
70 log show revision history of entire repository or files
71 merge merge another revision into working directory
71 merge merge another revision into working directory
72 pull pull changes from the specified source
72 pull pull changes from the specified source
73 push push changes to the specified destination
73 push push changes to the specified destination
74 remove remove the specified files on the next commit
74 remove remove the specified files on the next commit
75 serve start stand-alone webserver
75 serve start stand-alone webserver
76 status show changed files in the working directory
76 status show changed files in the working directory
77 summary summarize working directory state
77 summary summarize working directory state
78 update update working directory (or switch revisions)
78 update update working directory (or switch revisions)
79
79
80 (use "hg help" for the full list of commands or "hg -v" for details)
80 (use "hg help" for the full list of commands or "hg -v" for details)
81 *** runcommand id --quiet
81 *** runcommand id --quiet
82 000000000000
82 000000000000
83 *** runcommand id
83 *** runcommand id
84 000000000000 tip
84 000000000000 tip
85 *** runcommand id --config ui.quiet=True
85 *** runcommand id --config ui.quiet=True
86 000000000000
86 000000000000
87 *** runcommand id
87 *** runcommand id
88 000000000000 tip
88 000000000000 tip
89 *** runcommand id -runknown
89 *** runcommand id -runknown
90 abort: unknown revision 'unknown'!
90 abort: unknown revision 'unknown'!
91 [255]
91 [255]
92
92
93 >>> from hgclient import readchannel, check
93 >>> from hgclient import readchannel, check
94 >>> @check
94 >>> @check
95 ... def inputeof(server):
95 ... def inputeof(server):
96 ... readchannel(server)
96 ... readchannel(server)
97 ... server.stdin.write('runcommand\n')
97 ... server.stdin.write('runcommand\n')
98 ... # close stdin while server is waiting for input
98 ... # close stdin while server is waiting for input
99 ... server.stdin.close()
99 ... server.stdin.close()
100 ...
100 ...
101 ... # server exits with 1 if the pipe closed while reading the command
101 ... # server exits with 1 if the pipe closed while reading the command
102 ... print 'server exit code =', server.wait()
102 ... print 'server exit code =', server.wait()
103 server exit code = 1
103 server exit code = 1
104
104
105 >>> import cStringIO
105 >>> import cStringIO
106 >>> from hgclient import readchannel, runcommand, check
106 >>> from hgclient import readchannel, runcommand, check
107 >>> @check
107 >>> @check
108 ... def serverinput(server):
108 ... def serverinput(server):
109 ... readchannel(server)
109 ... readchannel(server)
110 ...
110 ...
111 ... patch = """
111 ... patch = """
112 ... # HG changeset patch
112 ... # HG changeset patch
113 ... # User test
113 ... # User test
114 ... # Date 0 0
114 ... # Date 0 0
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
115 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
116 ... # Parent 0000000000000000000000000000000000000000
116 ... # Parent 0000000000000000000000000000000000000000
117 ... 1
117 ... 1
118 ...
118 ...
119 ... diff -r 000000000000 -r c103a3dec114 a
119 ... diff -r 000000000000 -r c103a3dec114 a
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
120 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
121 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
122 ... @@ -0,0 +1,1 @@
122 ... @@ -0,0 +1,1 @@
123 ... +1
123 ... +1
124 ... """
124 ... """
125 ...
125 ...
126 ... runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
126 ... runcommand(server, ['import', '-'], input=cStringIO.StringIO(patch))
127 ... runcommand(server, ['log'])
127 ... runcommand(server, ['log'])
128 *** runcommand import -
128 *** runcommand import -
129 applying patch from stdin
129 applying patch from stdin
130 *** runcommand log
130 *** runcommand log
131 changeset: 0:eff892de26ec
131 changeset: 0:eff892de26ec
132 tag: tip
132 tag: tip
133 user: test
133 user: test
134 date: Thu Jan 01 00:00:00 1970 +0000
134 date: Thu Jan 01 00:00:00 1970 +0000
135 summary: 1
135 summary: 1
136
136
137
137
138 check that --cwd doesn't persist between requests:
138 check that --cwd doesn't persist between requests:
139
139
140 $ mkdir foo
140 $ mkdir foo
141 $ touch foo/bar
141 $ touch foo/bar
142 >>> from hgclient import readchannel, runcommand, check
142 >>> from hgclient import readchannel, runcommand, check
143 >>> @check
143 >>> @check
144 ... def cwd(server):
144 ... def cwd(server):
145 ... readchannel(server)
145 ... readchannel(server)
146 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
146 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
147 ... runcommand(server, ['st', 'foo/bar'])
147 ... runcommand(server, ['st', 'foo/bar'])
148 *** runcommand --cwd foo st bar
148 *** runcommand --cwd foo st bar
149 ? bar
149 ? bar
150 *** runcommand st foo/bar
150 *** runcommand st foo/bar
151 ? foo/bar
151 ? foo/bar
152
152
153 $ rm foo/bar
153 $ rm foo/bar
154
154
155
155
156 check that local configs for the cached repo aren't inherited when -R is used:
156 check that local configs for the cached repo aren't inherited when -R is used:
157
157
158 $ cat <<EOF >> .hg/hgrc
158 $ cat <<EOF >> .hg/hgrc
159 > [ui]
159 > [ui]
160 > foo = bar
160 > foo = bar
161 > EOF
161 > EOF
162
162
163 >>> from hgclient import readchannel, sep, runcommand, check
163 >>> from hgclient import readchannel, sep, runcommand, check
164 >>> @check
164 >>> @check
165 ... def localhgrc(server):
165 ... def localhgrc(server):
166 ... readchannel(server)
166 ... readchannel(server)
167 ...
167 ...
168 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
168 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
169 ... # show it
169 ... # show it
170 ... runcommand(server, ['showconfig'], outfilter=sep)
170 ... runcommand(server, ['showconfig'], outfilter=sep)
171 ...
171 ...
172 ... # but not for this repo
172 ... # but not for this repo
173 ... runcommand(server, ['init', 'foo'])
173 ... runcommand(server, ['init', 'foo'])
174 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
174 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
175 *** runcommand showconfig
175 *** runcommand showconfig
176 bundle.mainreporoot=$TESTTMP/repo
176 bundle.mainreporoot=$TESTTMP/repo
177 defaults.backout=-d "0 0"
177 defaults.backout=-d "0 0"
178 defaults.commit=-d "0 0"
178 defaults.commit=-d "0 0"
179 defaults.shelve=--date "0 0"
179 defaults.shelve=--date "0 0"
180 defaults.tag=-d "0 0"
180 defaults.tag=-d "0 0"
181 devel.all=true
181 largefiles.usercache=$TESTTMP/.cache/largefiles
182 largefiles.usercache=$TESTTMP/.cache/largefiles
182 ui.slash=True
183 ui.slash=True
183 ui.interactive=False
184 ui.interactive=False
184 ui.mergemarkers=detailed
185 ui.mergemarkers=detailed
185 ui.foo=bar
186 ui.foo=bar
186 ui.nontty=true
187 ui.nontty=true
187 *** runcommand init foo
188 *** runcommand init foo
188 *** runcommand -R foo showconfig ui defaults
189 *** runcommand -R foo showconfig ui defaults
189 defaults.backout=-d "0 0"
190 defaults.backout=-d "0 0"
190 defaults.commit=-d "0 0"
191 defaults.commit=-d "0 0"
191 defaults.shelve=--date "0 0"
192 defaults.shelve=--date "0 0"
192 defaults.tag=-d "0 0"
193 defaults.tag=-d "0 0"
193 ui.slash=True
194 ui.slash=True
194 ui.interactive=False
195 ui.interactive=False
195 ui.mergemarkers=detailed
196 ui.mergemarkers=detailed
196 ui.nontty=true
197 ui.nontty=true
197
198
198 $ rm -R foo
199 $ rm -R foo
199
200
200 #if windows
201 #if windows
201 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
202 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
202 #else
203 #else
203 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
204 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
204 #endif
205 #endif
205
206
206 $ cat <<EOF > hook.py
207 $ cat <<EOF > hook.py
207 > import sys
208 > import sys
208 > def hook(**args):
209 > def hook(**args):
209 > print 'hook talking'
210 > print 'hook talking'
210 > print 'now try to read something: %r' % sys.stdin.read()
211 > print 'now try to read something: %r' % sys.stdin.read()
211 > EOF
212 > EOF
212
213
213 >>> import cStringIO
214 >>> import cStringIO
214 >>> from hgclient import readchannel, runcommand, check
215 >>> from hgclient import readchannel, runcommand, check
215 >>> @check
216 >>> @check
216 ... def hookoutput(server):
217 ... def hookoutput(server):
217 ... readchannel(server)
218 ... readchannel(server)
218 ... runcommand(server, ['--config',
219 ... runcommand(server, ['--config',
219 ... 'hooks.pre-identify=python:hook.hook',
220 ... 'hooks.pre-identify=python:hook.hook',
220 ... 'id'],
221 ... 'id'],
221 ... input=cStringIO.StringIO('some input'))
222 ... input=cStringIO.StringIO('some input'))
222 *** runcommand --config hooks.pre-identify=python:hook.hook id
223 *** runcommand --config hooks.pre-identify=python:hook.hook id
223 hook talking
224 hook talking
224 now try to read something: 'some input'
225 now try to read something: 'some input'
225 eff892de26ec tip
226 eff892de26ec tip
226
227
227 $ rm hook.py*
228 $ rm hook.py*
228
229
229 $ echo a >> a
230 $ echo a >> a
230 >>> import os
231 >>> import os
231 >>> from hgclient import readchannel, runcommand, check
232 >>> from hgclient import readchannel, runcommand, check
232 >>> @check
233 >>> @check
233 ... def outsidechanges(server):
234 ... def outsidechanges(server):
234 ... readchannel(server)
235 ... readchannel(server)
235 ... runcommand(server, ['status'])
236 ... runcommand(server, ['status'])
236 ... os.system('hg ci -Am2')
237 ... os.system('hg ci -Am2')
237 ... runcommand(server, ['tip'])
238 ... runcommand(server, ['tip'])
238 ... runcommand(server, ['status'])
239 ... runcommand(server, ['status'])
239 *** runcommand status
240 *** runcommand status
240 M a
241 M a
241 *** runcommand tip
242 *** runcommand tip
242 changeset: 1:d3a0a68be6de
243 changeset: 1:d3a0a68be6de
243 tag: tip
244 tag: tip
244 user: test
245 user: test
245 date: Thu Jan 01 00:00:00 1970 +0000
246 date: Thu Jan 01 00:00:00 1970 +0000
246 summary: 2
247 summary: 2
247
248
248 *** runcommand status
249 *** runcommand status
249
250
250 >>> import os
251 >>> import os
251 >>> from hgclient import readchannel, runcommand, check
252 >>> from hgclient import readchannel, runcommand, check
252 >>> @check
253 >>> @check
253 ... def bookmarks(server):
254 ... def bookmarks(server):
254 ... readchannel(server)
255 ... readchannel(server)
255 ... runcommand(server, ['bookmarks'])
256 ... runcommand(server, ['bookmarks'])
256 ...
257 ...
257 ... # changes .hg/bookmarks
258 ... # changes .hg/bookmarks
258 ... os.system('hg bookmark -i bm1')
259 ... os.system('hg bookmark -i bm1')
259 ... os.system('hg bookmark -i bm2')
260 ... os.system('hg bookmark -i bm2')
260 ... runcommand(server, ['bookmarks'])
261 ... runcommand(server, ['bookmarks'])
261 ...
262 ...
262 ... # changes .hg/bookmarks.current
263 ... # changes .hg/bookmarks.current
263 ... os.system('hg upd bm1 -q')
264 ... os.system('hg upd bm1 -q')
264 ... runcommand(server, ['bookmarks'])
265 ... runcommand(server, ['bookmarks'])
265 ...
266 ...
266 ... runcommand(server, ['bookmarks', 'bm3'])
267 ... runcommand(server, ['bookmarks', 'bm3'])
267 ... f = open('a', 'ab')
268 ... f = open('a', 'ab')
268 ... f.write('a\n')
269 ... f.write('a\n')
269 ... f.close()
270 ... f.close()
270 ... runcommand(server, ['commit', '-Amm'])
271 ... runcommand(server, ['commit', '-Amm'])
271 ... runcommand(server, ['bookmarks'])
272 ... runcommand(server, ['bookmarks'])
272 *** runcommand bookmarks
273 *** runcommand bookmarks
273 no bookmarks set
274 no bookmarks set
274 *** runcommand bookmarks
275 *** runcommand bookmarks
275 bm1 1:d3a0a68be6de
276 bm1 1:d3a0a68be6de
276 bm2 1:d3a0a68be6de
277 bm2 1:d3a0a68be6de
277 *** runcommand bookmarks
278 *** runcommand bookmarks
278 * bm1 1:d3a0a68be6de
279 * bm1 1:d3a0a68be6de
279 bm2 1:d3a0a68be6de
280 bm2 1:d3a0a68be6de
280 *** runcommand bookmarks bm3
281 *** runcommand bookmarks bm3
281 *** runcommand commit -Amm
282 *** runcommand commit -Amm
282 *** runcommand bookmarks
283 *** runcommand bookmarks
283 bm1 1:d3a0a68be6de
284 bm1 1:d3a0a68be6de
284 bm2 1:d3a0a68be6de
285 bm2 1:d3a0a68be6de
285 * bm3 2:aef17e88f5f0
286 * bm3 2:aef17e88f5f0
286
287
287 >>> import os
288 >>> import os
288 >>> from hgclient import readchannel, runcommand, check
289 >>> from hgclient import readchannel, runcommand, check
289 >>> @check
290 >>> @check
290 ... def tagscache(server):
291 ... def tagscache(server):
291 ... readchannel(server)
292 ... readchannel(server)
292 ... runcommand(server, ['id', '-t', '-r', '0'])
293 ... runcommand(server, ['id', '-t', '-r', '0'])
293 ... os.system('hg tag -r 0 foo')
294 ... os.system('hg tag -r 0 foo')
294 ... runcommand(server, ['id', '-t', '-r', '0'])
295 ... runcommand(server, ['id', '-t', '-r', '0'])
295 *** runcommand id -t -r 0
296 *** runcommand id -t -r 0
296
297
297 *** runcommand id -t -r 0
298 *** runcommand id -t -r 0
298 foo
299 foo
299
300
300 >>> import os
301 >>> import os
301 >>> from hgclient import readchannel, runcommand, check
302 >>> from hgclient import readchannel, runcommand, check
302 >>> @check
303 >>> @check
303 ... def setphase(server):
304 ... def setphase(server):
304 ... readchannel(server)
305 ... readchannel(server)
305 ... runcommand(server, ['phase', '-r', '.'])
306 ... runcommand(server, ['phase', '-r', '.'])
306 ... os.system('hg phase -r . -p')
307 ... os.system('hg phase -r . -p')
307 ... runcommand(server, ['phase', '-r', '.'])
308 ... runcommand(server, ['phase', '-r', '.'])
308 *** runcommand phase -r .
309 *** runcommand phase -r .
309 3: draft
310 3: draft
310 *** runcommand phase -r .
311 *** runcommand phase -r .
311 3: public
312 3: public
312
313
313 $ echo a >> a
314 $ echo a >> a
314 >>> from hgclient import readchannel, runcommand, check
315 >>> from hgclient import readchannel, runcommand, check
315 >>> @check
316 >>> @check
316 ... def rollback(server):
317 ... def rollback(server):
317 ... readchannel(server)
318 ... readchannel(server)
318 ... runcommand(server, ['phase', '-r', '.', '-p'])
319 ... runcommand(server, ['phase', '-r', '.', '-p'])
319 ... runcommand(server, ['commit', '-Am.'])
320 ... runcommand(server, ['commit', '-Am.'])
320 ... runcommand(server, ['rollback'])
321 ... runcommand(server, ['rollback'])
321 ... runcommand(server, ['phase', '-r', '.'])
322 ... runcommand(server, ['phase', '-r', '.'])
322 *** runcommand phase -r . -p
323 *** runcommand phase -r . -p
323 no phases changed
324 no phases changed
324 [1]
325 [1]
325 *** runcommand commit -Am.
326 *** runcommand commit -Am.
326 *** runcommand rollback
327 *** runcommand rollback
327 repository tip rolled back to revision 3 (undo commit)
328 repository tip rolled back to revision 3 (undo commit)
328 working directory now based on revision 3
329 working directory now based on revision 3
329 *** runcommand phase -r .
330 *** runcommand phase -r .
330 3: public
331 3: public
331
332
332 >>> import os
333 >>> import os
333 >>> from hgclient import readchannel, runcommand, check
334 >>> from hgclient import readchannel, runcommand, check
334 >>> @check
335 >>> @check
335 ... def branch(server):
336 ... def branch(server):
336 ... readchannel(server)
337 ... readchannel(server)
337 ... runcommand(server, ['branch'])
338 ... runcommand(server, ['branch'])
338 ... os.system('hg branch foo')
339 ... os.system('hg branch foo')
339 ... runcommand(server, ['branch'])
340 ... runcommand(server, ['branch'])
340 ... os.system('hg branch default')
341 ... os.system('hg branch default')
341 *** runcommand branch
342 *** runcommand branch
342 default
343 default
343 marked working directory as branch foo
344 marked working directory as branch foo
344 (branches are permanent and global, did you want a bookmark?)
345 (branches are permanent and global, did you want a bookmark?)
345 *** runcommand branch
346 *** runcommand branch
346 foo
347 foo
347 marked working directory as branch default
348 marked working directory as branch default
348 (branches are permanent and global, did you want a bookmark?)
349 (branches are permanent and global, did you want a bookmark?)
349
350
350 $ touch .hgignore
351 $ touch .hgignore
351 >>> import os
352 >>> import os
352 >>> from hgclient import readchannel, runcommand, check
353 >>> from hgclient import readchannel, runcommand, check
353 >>> @check
354 >>> @check
354 ... def hgignore(server):
355 ... def hgignore(server):
355 ... readchannel(server)
356 ... readchannel(server)
356 ... runcommand(server, ['commit', '-Am.'])
357 ... runcommand(server, ['commit', '-Am.'])
357 ... f = open('ignored-file', 'ab')
358 ... f = open('ignored-file', 'ab')
358 ... f.write('')
359 ... f.write('')
359 ... f.close()
360 ... f.close()
360 ... f = open('.hgignore', 'ab')
361 ... f = open('.hgignore', 'ab')
361 ... f.write('ignored-file')
362 ... f.write('ignored-file')
362 ... f.close()
363 ... f.close()
363 ... runcommand(server, ['status', '-i', '-u'])
364 ... runcommand(server, ['status', '-i', '-u'])
364 *** runcommand commit -Am.
365 *** runcommand commit -Am.
365 adding .hgignore
366 adding .hgignore
366 *** runcommand status -i -u
367 *** runcommand status -i -u
367 I ignored-file
368 I ignored-file
368
369
369 >>> import os
370 >>> import os
370 >>> from hgclient import readchannel, sep, runcommand, check
371 >>> from hgclient import readchannel, sep, runcommand, check
371 >>> @check
372 >>> @check
372 ... def phasecacheafterstrip(server):
373 ... def phasecacheafterstrip(server):
373 ... readchannel(server)
374 ... readchannel(server)
374 ...
375 ...
375 ... # create new head, 5:731265503d86
376 ... # create new head, 5:731265503d86
376 ... runcommand(server, ['update', '-C', '0'])
377 ... runcommand(server, ['update', '-C', '0'])
377 ... f = open('a', 'ab')
378 ... f = open('a', 'ab')
378 ... f.write('a\n')
379 ... f.write('a\n')
379 ... f.close()
380 ... f.close()
380 ... runcommand(server, ['commit', '-Am.', 'a'])
381 ... runcommand(server, ['commit', '-Am.', 'a'])
381 ... runcommand(server, ['log', '-Gq'])
382 ... runcommand(server, ['log', '-Gq'])
382 ...
383 ...
383 ... # make it public; draft marker moves to 4:7966c8e3734d
384 ... # make it public; draft marker moves to 4:7966c8e3734d
384 ... runcommand(server, ['phase', '-p', '.'])
385 ... runcommand(server, ['phase', '-p', '.'])
385 ... # load _phasecache.phaseroots
386 ... # load _phasecache.phaseroots
386 ... runcommand(server, ['phase', '.'], outfilter=sep)
387 ... runcommand(server, ['phase', '.'], outfilter=sep)
387 ...
388 ...
388 ... # strip 1::4 outside server
389 ... # strip 1::4 outside server
389 ... os.system('hg -q --config extensions.mq= strip 1')
390 ... os.system('hg -q --config extensions.mq= strip 1')
390 ...
391 ...
391 ... # shouldn't raise "7966c8e3734d: no node!"
392 ... # shouldn't raise "7966c8e3734d: no node!"
392 ... runcommand(server, ['branches'])
393 ... runcommand(server, ['branches'])
393 *** runcommand update -C 0
394 *** runcommand update -C 0
394 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
395 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
395 (leaving bookmark bm3)
396 (leaving bookmark bm3)
396 *** runcommand commit -Am. a
397 *** runcommand commit -Am. a
397 created new head
398 created new head
398 *** runcommand log -Gq
399 *** runcommand log -Gq
399 @ 5:731265503d86
400 @ 5:731265503d86
400 |
401 |
401 | o 4:7966c8e3734d
402 | o 4:7966c8e3734d
402 | |
403 | |
403 | o 3:b9b85890c400
404 | o 3:b9b85890c400
404 | |
405 | |
405 | o 2:aef17e88f5f0
406 | o 2:aef17e88f5f0
406 | |
407 | |
407 | o 1:d3a0a68be6de
408 | o 1:d3a0a68be6de
408 |/
409 |/
409 o 0:eff892de26ec
410 o 0:eff892de26ec
410
411
411 *** runcommand phase -p .
412 *** runcommand phase -p .
412 *** runcommand phase .
413 *** runcommand phase .
413 5: public
414 5: public
414 *** runcommand branches
415 *** runcommand branches
415 default 1:731265503d86
416 default 1:731265503d86
416
417
417 $ cat >> .hg/hgrc << EOF
418 $ cat >> .hg/hgrc << EOF
418 > [experimental]
419 > [experimental]
419 > evolution=createmarkers
420 > evolution=createmarkers
420 > EOF
421 > EOF
421
422
422 >>> import os
423 >>> import os
423 >>> from hgclient import readchannel, runcommand, check
424 >>> from hgclient import readchannel, runcommand, check
424 >>> @check
425 >>> @check
425 ... def obsolete(server):
426 ... def obsolete(server):
426 ... readchannel(server)
427 ... readchannel(server)
427 ...
428 ...
428 ... runcommand(server, ['up', 'null'])
429 ... runcommand(server, ['up', 'null'])
429 ... runcommand(server, ['phase', '-df', 'tip'])
430 ... runcommand(server, ['phase', '-df', 'tip'])
430 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
431 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
431 ... if os.name == 'nt':
432 ... if os.name == 'nt':
432 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
433 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
433 ... os.system(cmd)
434 ... os.system(cmd)
434 ... runcommand(server, ['log', '--hidden'])
435 ... runcommand(server, ['log', '--hidden'])
435 ... runcommand(server, ['log'])
436 ... runcommand(server, ['log'])
436 *** runcommand up null
437 *** runcommand up null
437 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
438 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
438 *** runcommand phase -df tip
439 *** runcommand phase -df tip
439 *** runcommand log --hidden
440 *** runcommand log --hidden
440 changeset: 1:731265503d86
441 changeset: 1:731265503d86
441 tag: tip
442 tag: tip
442 user: test
443 user: test
443 date: Thu Jan 01 00:00:00 1970 +0000
444 date: Thu Jan 01 00:00:00 1970 +0000
444 summary: .
445 summary: .
445
446
446 changeset: 0:eff892de26ec
447 changeset: 0:eff892de26ec
447 bookmark: bm1
448 bookmark: bm1
448 bookmark: bm2
449 bookmark: bm2
449 bookmark: bm3
450 bookmark: bm3
450 user: test
451 user: test
451 date: Thu Jan 01 00:00:00 1970 +0000
452 date: Thu Jan 01 00:00:00 1970 +0000
452 summary: 1
453 summary: 1
453
454
454 *** runcommand log
455 *** runcommand log
455 changeset: 0:eff892de26ec
456 changeset: 0:eff892de26ec
456 bookmark: bm1
457 bookmark: bm1
457 bookmark: bm2
458 bookmark: bm2
458 bookmark: bm3
459 bookmark: bm3
459 tag: tip
460 tag: tip
460 user: test
461 user: test
461 date: Thu Jan 01 00:00:00 1970 +0000
462 date: Thu Jan 01 00:00:00 1970 +0000
462 summary: 1
463 summary: 1
463
464
464
465
465 $ cat <<EOF >> .hg/hgrc
466 $ cat <<EOF >> .hg/hgrc
466 > [extensions]
467 > [extensions]
467 > mq =
468 > mq =
468 > EOF
469 > EOF
469
470
470 >>> import os
471 >>> import os
471 >>> from hgclient import readchannel, runcommand, check
472 >>> from hgclient import readchannel, runcommand, check
472 >>> @check
473 >>> @check
473 ... def mqoutsidechanges(server):
474 ... def mqoutsidechanges(server):
474 ... readchannel(server)
475 ... readchannel(server)
475 ...
476 ...
476 ... # load repo.mq
477 ... # load repo.mq
477 ... runcommand(server, ['qapplied'])
478 ... runcommand(server, ['qapplied'])
478 ... os.system('hg qnew 0.diff')
479 ... os.system('hg qnew 0.diff')
479 ... # repo.mq should be invalidated
480 ... # repo.mq should be invalidated
480 ... runcommand(server, ['qapplied'])
481 ... runcommand(server, ['qapplied'])
481 ...
482 ...
482 ... runcommand(server, ['qpop', '--all'])
483 ... runcommand(server, ['qpop', '--all'])
483 ... os.system('hg qqueue --create foo')
484 ... os.system('hg qqueue --create foo')
484 ... # repo.mq should be recreated to point to new queue
485 ... # repo.mq should be recreated to point to new queue
485 ... runcommand(server, ['qqueue', '--active'])
486 ... runcommand(server, ['qqueue', '--active'])
486 *** runcommand qapplied
487 *** runcommand qapplied
487 *** runcommand qapplied
488 *** runcommand qapplied
488 0.diff
489 0.diff
489 *** runcommand qpop --all
490 *** runcommand qpop --all
490 popping 0.diff
491 popping 0.diff
491 patch queue now empty
492 patch queue now empty
492 *** runcommand qqueue --active
493 *** runcommand qqueue --active
493 foo
494 foo
494
495
495 $ cat <<EOF > dbgui.py
496 $ cat <<EOF > dbgui.py
496 > import os, sys
497 > import os, sys
497 > from mercurial import cmdutil, commands
498 > from mercurial import cmdutil, commands
498 > cmdtable = {}
499 > cmdtable = {}
499 > command = cmdutil.command(cmdtable)
500 > command = cmdutil.command(cmdtable)
500 > @command("debuggetpass", norepo=True)
501 > @command("debuggetpass", norepo=True)
501 > def debuggetpass(ui):
502 > def debuggetpass(ui):
502 > ui.write("%s\\n" % ui.getpass())
503 > ui.write("%s\\n" % ui.getpass())
503 > @command("debugprompt", norepo=True)
504 > @command("debugprompt", norepo=True)
504 > def debugprompt(ui):
505 > def debugprompt(ui):
505 > ui.write("%s\\n" % ui.prompt("prompt:"))
506 > ui.write("%s\\n" % ui.prompt("prompt:"))
506 > @command("debugreadstdin", norepo=True)
507 > @command("debugreadstdin", norepo=True)
507 > def debugreadstdin(ui):
508 > def debugreadstdin(ui):
508 > ui.write("read: %r\n" % sys.stdin.read(1))
509 > ui.write("read: %r\n" % sys.stdin.read(1))
509 > @command("debugwritestdout", norepo=True)
510 > @command("debugwritestdout", norepo=True)
510 > def debugwritestdout(ui):
511 > def debugwritestdout(ui):
511 > os.write(1, "low-level stdout fd and\n")
512 > os.write(1, "low-level stdout fd and\n")
512 > sys.stdout.write("stdout should be redirected to /dev/null\n")
513 > sys.stdout.write("stdout should be redirected to /dev/null\n")
513 > sys.stdout.flush()
514 > sys.stdout.flush()
514 > EOF
515 > EOF
515 $ cat <<EOF >> .hg/hgrc
516 $ cat <<EOF >> .hg/hgrc
516 > [extensions]
517 > [extensions]
517 > dbgui = dbgui.py
518 > dbgui = dbgui.py
518 > EOF
519 > EOF
519
520
520 >>> import cStringIO
521 >>> import cStringIO
521 >>> from hgclient import readchannel, runcommand, check
522 >>> from hgclient import readchannel, runcommand, check
522 >>> @check
523 >>> @check
523 ... def getpass(server):
524 ... def getpass(server):
524 ... readchannel(server)
525 ... readchannel(server)
525 ... runcommand(server, ['debuggetpass', '--config',
526 ... runcommand(server, ['debuggetpass', '--config',
526 ... 'ui.interactive=True'],
527 ... 'ui.interactive=True'],
527 ... input=cStringIO.StringIO('1234\n'))
528 ... input=cStringIO.StringIO('1234\n'))
528 ... runcommand(server, ['debugprompt', '--config',
529 ... runcommand(server, ['debugprompt', '--config',
529 ... 'ui.interactive=True'],
530 ... 'ui.interactive=True'],
530 ... input=cStringIO.StringIO('5678\n'))
531 ... input=cStringIO.StringIO('5678\n'))
531 ... runcommand(server, ['debugreadstdin'])
532 ... runcommand(server, ['debugreadstdin'])
532 ... runcommand(server, ['debugwritestdout'])
533 ... runcommand(server, ['debugwritestdout'])
533 *** runcommand debuggetpass --config ui.interactive=True
534 *** runcommand debuggetpass --config ui.interactive=True
534 password: 1234
535 password: 1234
535 *** runcommand debugprompt --config ui.interactive=True
536 *** runcommand debugprompt --config ui.interactive=True
536 prompt: 5678
537 prompt: 5678
537 *** runcommand debugreadstdin
538 *** runcommand debugreadstdin
538 read: ''
539 read: ''
539 *** runcommand debugwritestdout
540 *** runcommand debugwritestdout
540
541
541
542
542 run commandserver in commandserver, which is silly but should work:
543 run commandserver in commandserver, which is silly but should work:
543
544
544 >>> import cStringIO
545 >>> import cStringIO
545 >>> from hgclient import readchannel, runcommand, check
546 >>> from hgclient import readchannel, runcommand, check
546 >>> @check
547 >>> @check
547 ... def nested(server):
548 ... def nested(server):
548 ... print '%c, %r' % readchannel(server)
549 ... print '%c, %r' % readchannel(server)
549 ... class nestedserver(object):
550 ... class nestedserver(object):
550 ... stdin = cStringIO.StringIO('getencoding\n')
551 ... stdin = cStringIO.StringIO('getencoding\n')
551 ... stdout = cStringIO.StringIO()
552 ... stdout = cStringIO.StringIO()
552 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
553 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
553 ... output=nestedserver.stdout, input=nestedserver.stdin)
554 ... output=nestedserver.stdout, input=nestedserver.stdin)
554 ... nestedserver.stdout.seek(0)
555 ... nestedserver.stdout.seek(0)
555 ... print '%c, %r' % readchannel(nestedserver) # hello
556 ... print '%c, %r' % readchannel(nestedserver) # hello
556 ... print '%c, %r' % readchannel(nestedserver) # getencoding
557 ... print '%c, %r' % readchannel(nestedserver) # getencoding
557 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
558 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
558 *** runcommand serve --cmdserver pipe
559 *** runcommand serve --cmdserver pipe
559 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
560 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
560 r, '*' (glob)
561 r, '*' (glob)
561
562
562
563
563 start without repository:
564 start without repository:
564
565
565 $ cd ..
566 $ cd ..
566
567
567 >>> from hgclient import readchannel, runcommand, check
568 >>> from hgclient import readchannel, runcommand, check
568 >>> @check
569 >>> @check
569 ... def hellomessage(server):
570 ... def hellomessage(server):
570 ... ch, data = readchannel(server)
571 ... ch, data = readchannel(server)
571 ... print '%c, %r' % (ch, data)
572 ... print '%c, %r' % (ch, data)
572 ... # run an arbitrary command to make sure the next thing the server
573 ... # run an arbitrary command to make sure the next thing the server
573 ... # sends isn't part of the hello message
574 ... # sends isn't part of the hello message
574 ... runcommand(server, ['id'])
575 ... runcommand(server, ['id'])
575 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
576 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
576 *** runcommand id
577 *** runcommand id
577 abort: there is no Mercurial repository here (.hg not found)
578 abort: there is no Mercurial repository here (.hg not found)
578 [255]
579 [255]
579
580
580 >>> from hgclient import readchannel, runcommand, check
581 >>> from hgclient import readchannel, runcommand, check
581 >>> @check
582 >>> @check
582 ... def startwithoutrepo(server):
583 ... def startwithoutrepo(server):
583 ... readchannel(server)
584 ... readchannel(server)
584 ... runcommand(server, ['init', 'repo2'])
585 ... runcommand(server, ['init', 'repo2'])
585 ... runcommand(server, ['id', '-R', 'repo2'])
586 ... runcommand(server, ['id', '-R', 'repo2'])
586 *** runcommand init repo2
587 *** runcommand init repo2
587 *** runcommand id -R repo2
588 *** runcommand id -R repo2
588 000000000000 tip
589 000000000000 tip
589
590
590
591
591 unix domain socket:
592 unix domain socket:
592
593
593 $ cd repo
594 $ cd repo
594 $ hg update -q
595 $ hg update -q
595
596
596 #if unix-socket unix-permissions
597 #if unix-socket unix-permissions
597
598
598 >>> import cStringIO
599 >>> import cStringIO
599 >>> from hgclient import unixserver, readchannel, runcommand, check
600 >>> from hgclient import unixserver, readchannel, runcommand, check
600 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
601 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
601 >>> def hellomessage(conn):
602 >>> def hellomessage(conn):
602 ... ch, data = readchannel(conn)
603 ... ch, data = readchannel(conn)
603 ... print '%c, %r' % (ch, data)
604 ... print '%c, %r' % (ch, data)
604 ... runcommand(conn, ['id'])
605 ... runcommand(conn, ['id'])
605 >>> check(hellomessage, server.connect)
606 >>> check(hellomessage, server.connect)
606 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
607 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
607 *** runcommand id
608 *** runcommand id
608 eff892de26ec tip bm1/bm2/bm3
609 eff892de26ec tip bm1/bm2/bm3
609 >>> def unknowncommand(conn):
610 >>> def unknowncommand(conn):
610 ... readchannel(conn)
611 ... readchannel(conn)
611 ... conn.stdin.write('unknowncommand\n')
612 ... conn.stdin.write('unknowncommand\n')
612 >>> check(unknowncommand, server.connect) # error sent to server.log
613 >>> check(unknowncommand, server.connect) # error sent to server.log
613 >>> def serverinput(conn):
614 >>> def serverinput(conn):
614 ... readchannel(conn)
615 ... readchannel(conn)
615 ... patch = """
616 ... patch = """
616 ... # HG changeset patch
617 ... # HG changeset patch
617 ... # User test
618 ... # User test
618 ... # Date 0 0
619 ... # Date 0 0
619 ... 2
620 ... 2
620 ...
621 ...
621 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
622 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
622 ... --- a/a
623 ... --- a/a
623 ... +++ b/a
624 ... +++ b/a
624 ... @@ -1,1 +1,2 @@
625 ... @@ -1,1 +1,2 @@
625 ... 1
626 ... 1
626 ... +2
627 ... +2
627 ... """
628 ... """
628 ... runcommand(conn, ['import', '-'], input=cStringIO.StringIO(patch))
629 ... runcommand(conn, ['import', '-'], input=cStringIO.StringIO(patch))
629 ... runcommand(conn, ['log', '-rtip', '-q'])
630 ... runcommand(conn, ['log', '-rtip', '-q'])
630 >>> check(serverinput, server.connect)
631 >>> check(serverinput, server.connect)
631 *** runcommand import -
632 *** runcommand import -
632 applying patch from stdin
633 applying patch from stdin
633 *** runcommand log -rtip -q
634 *** runcommand log -rtip -q
634 2:1ed24be7e7a0
635 2:1ed24be7e7a0
635 >>> server.shutdown()
636 >>> server.shutdown()
636
637
637 $ cat .hg/server.log
638 $ cat .hg/server.log
638 listening at .hg/server.sock
639 listening at .hg/server.sock
639 abort: unknown command unknowncommand
640 abort: unknown command unknowncommand
640 killed!
641 killed!
641 #endif
642 #endif
642 #if no-unix-socket
643 #if no-unix-socket
643
644
644 $ hg serve --cmdserver unix -a .hg/server.sock
645 $ hg serve --cmdserver unix -a .hg/server.sock
645 abort: unsupported platform
646 abort: unsupported platform
646 [255]
647 [255]
647
648
648 #endif
649 #endif
General Comments 0
You need to be logged in to leave comments. Login now