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