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