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