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