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