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