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