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