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