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