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