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