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