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