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