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