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