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