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