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