##// END OF EJS Templates
run-tests: handle .tst not ending with an LF
Patrick Mezard -
r12621:9a2de8da default
parent child Browse files
Show More
@@ -1,1091 +1,1093 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 re
55 import re
56
56
57 closefds = os.name == 'posix'
57 closefds = os.name == 'posix'
58 def Popen4(cmd, bufsize=-1):
58 def Popen4(cmd, bufsize=-1):
59 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
59 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 stderr=subprocess.STDOUT)
62 stderr=subprocess.STDOUT)
63 p.fromchild = p.stdout
63 p.fromchild = p.stdout
64 p.tochild = p.stdin
64 p.tochild = p.stdin
65 p.childerr = p.stderr
65 p.childerr = p.stderr
66 return p
66 return p
67
67
68 # reserved exit code to skip test (used by hghave)
68 # reserved exit code to skip test (used by hghave)
69 SKIPPED_STATUS = 80
69 SKIPPED_STATUS = 80
70 SKIPPED_PREFIX = 'skipped: '
70 SKIPPED_PREFIX = 'skipped: '
71 FAILED_PREFIX = 'hghave check failed: '
71 FAILED_PREFIX = 'hghave check failed: '
72 PYTHON = sys.executable
72 PYTHON = sys.executable
73 IMPL_PATH = 'PYTHONPATH'
73 IMPL_PATH = 'PYTHONPATH'
74 if 'java' in sys.platform:
74 if 'java' in sys.platform:
75 IMPL_PATH = 'JYTHONPATH'
75 IMPL_PATH = 'JYTHONPATH'
76
76
77 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
77 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
78
78
79 defaults = {
79 defaults = {
80 'jobs': ('HGTEST_JOBS', 1),
80 'jobs': ('HGTEST_JOBS', 1),
81 'timeout': ('HGTEST_TIMEOUT', 180),
81 'timeout': ('HGTEST_TIMEOUT', 180),
82 'port': ('HGTEST_PORT', 20059),
82 'port': ('HGTEST_PORT', 20059),
83 }
83 }
84
84
85 def parseargs():
85 def parseargs():
86 parser = optparse.OptionParser("%prog [options] [tests]")
86 parser = optparse.OptionParser("%prog [options] [tests]")
87
87
88 # keep these sorted
88 # keep these sorted
89 parser.add_option("--blacklist", action="append",
89 parser.add_option("--blacklist", action="append",
90 help="skip tests listed in the specified blacklist file")
90 help="skip tests listed in the specified blacklist file")
91 parser.add_option("-C", "--annotate", action="store_true",
91 parser.add_option("-C", "--annotate", action="store_true",
92 help="output files annotated with coverage")
92 help="output files annotated with coverage")
93 parser.add_option("--child", type="int",
93 parser.add_option("--child", type="int",
94 help="run as child process, summary to given fd")
94 help="run as child process, summary to given fd")
95 parser.add_option("-c", "--cover", action="store_true",
95 parser.add_option("-c", "--cover", action="store_true",
96 help="print a test coverage report")
96 help="print a test coverage report")
97 parser.add_option("-d", "--debug", action="store_true",
97 parser.add_option("-d", "--debug", action="store_true",
98 help="debug mode: write output of test scripts to console"
98 help="debug mode: write output of test scripts to console"
99 " rather than capturing and diff'ing it (disables timeout)")
99 " rather than capturing and diff'ing it (disables timeout)")
100 parser.add_option("-f", "--first", action="store_true",
100 parser.add_option("-f", "--first", action="store_true",
101 help="exit on the first test failure")
101 help="exit on the first test failure")
102 parser.add_option("--inotify", action="store_true",
102 parser.add_option("--inotify", action="store_true",
103 help="enable inotify extension when running tests")
103 help="enable inotify extension when running tests")
104 parser.add_option("-i", "--interactive", action="store_true",
104 parser.add_option("-i", "--interactive", action="store_true",
105 help="prompt to accept changed output")
105 help="prompt to accept changed output")
106 parser.add_option("-j", "--jobs", type="int",
106 parser.add_option("-j", "--jobs", type="int",
107 help="number of jobs to run in parallel"
107 help="number of jobs to run in parallel"
108 " (default: $%s or %d)" % defaults['jobs'])
108 " (default: $%s or %d)" % defaults['jobs'])
109 parser.add_option("--keep-tmpdir", action="store_true",
109 parser.add_option("--keep-tmpdir", action="store_true",
110 help="keep temporary directory after running tests")
110 help="keep temporary directory after running tests")
111 parser.add_option("-k", "--keywords",
111 parser.add_option("-k", "--keywords",
112 help="run tests matching keywords")
112 help="run tests matching keywords")
113 parser.add_option("-l", "--local", action="store_true",
113 parser.add_option("-l", "--local", action="store_true",
114 help="shortcut for --with-hg=<testdir>/../hg")
114 help="shortcut for --with-hg=<testdir>/../hg")
115 parser.add_option("-n", "--nodiff", action="store_true",
115 parser.add_option("-n", "--nodiff", action="store_true",
116 help="skip showing test changes")
116 help="skip showing test changes")
117 parser.add_option("-p", "--port", type="int",
117 parser.add_option("-p", "--port", type="int",
118 help="port on which servers should listen"
118 help="port on which servers should listen"
119 " (default: $%s or %d)" % defaults['port'])
119 " (default: $%s or %d)" % defaults['port'])
120 parser.add_option("--pure", action="store_true",
120 parser.add_option("--pure", action="store_true",
121 help="use pure Python code instead of C extensions")
121 help="use pure Python code instead of C extensions")
122 parser.add_option("-R", "--restart", action="store_true",
122 parser.add_option("-R", "--restart", action="store_true",
123 help="restart at last error")
123 help="restart at last error")
124 parser.add_option("-r", "--retest", action="store_true",
124 parser.add_option("-r", "--retest", action="store_true",
125 help="retest failed tests")
125 help="retest failed tests")
126 parser.add_option("-S", "--noskips", action="store_true",
126 parser.add_option("-S", "--noskips", action="store_true",
127 help="don't report skip tests verbosely")
127 help="don't report skip tests verbosely")
128 parser.add_option("-t", "--timeout", type="int",
128 parser.add_option("-t", "--timeout", type="int",
129 help="kill errant tests after TIMEOUT seconds"
129 help="kill errant tests after TIMEOUT seconds"
130 " (default: $%s or %d)" % defaults['timeout'])
130 " (default: $%s or %d)" % defaults['timeout'])
131 parser.add_option("--tmpdir", type="string",
131 parser.add_option("--tmpdir", type="string",
132 help="run tests in the given temporary directory"
132 help="run tests in the given temporary directory"
133 " (implies --keep-tmpdir)")
133 " (implies --keep-tmpdir)")
134 parser.add_option("-v", "--verbose", action="store_true",
134 parser.add_option("-v", "--verbose", action="store_true",
135 help="output verbose messages")
135 help="output verbose messages")
136 parser.add_option("--view", type="string",
136 parser.add_option("--view", type="string",
137 help="external diff viewer")
137 help="external diff viewer")
138 parser.add_option("--with-hg", type="string",
138 parser.add_option("--with-hg", type="string",
139 metavar="HG",
139 metavar="HG",
140 help="test using specified hg script rather than a "
140 help="test using specified hg script rather than a "
141 "temporary installation")
141 "temporary installation")
142 parser.add_option("-3", "--py3k-warnings", action="store_true",
142 parser.add_option("-3", "--py3k-warnings", action="store_true",
143 help="enable Py3k warnings on Python 2.6+")
143 help="enable Py3k warnings on Python 2.6+")
144
144
145 for option, default in defaults.items():
145 for option, default in defaults.items():
146 defaults[option] = int(os.environ.get(*default))
146 defaults[option] = int(os.environ.get(*default))
147 parser.set_defaults(**defaults)
147 parser.set_defaults(**defaults)
148 (options, args) = parser.parse_args()
148 (options, args) = parser.parse_args()
149
149
150 # jython is always pure
150 # jython is always pure
151 if 'java' in sys.platform or '__pypy__' in sys.modules:
151 if 'java' in sys.platform or '__pypy__' in sys.modules:
152 options.pure = True
152 options.pure = True
153
153
154 if options.with_hg:
154 if options.with_hg:
155 if not (os.path.isfile(options.with_hg) and
155 if not (os.path.isfile(options.with_hg) and
156 os.access(options.with_hg, os.X_OK)):
156 os.access(options.with_hg, os.X_OK)):
157 parser.error('--with-hg must specify an executable hg script')
157 parser.error('--with-hg must specify an executable hg script')
158 if not os.path.basename(options.with_hg) == 'hg':
158 if not os.path.basename(options.with_hg) == 'hg':
159 sys.stderr.write('warning: --with-hg should specify an hg script')
159 sys.stderr.write('warning: --with-hg should specify an hg script')
160 if options.local:
160 if options.local:
161 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
161 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
162 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
162 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
163 if not os.access(hgbin, os.X_OK):
163 if not os.access(hgbin, os.X_OK):
164 parser.error('--local specified, but %r not found or not executable'
164 parser.error('--local specified, but %r not found or not executable'
165 % hgbin)
165 % hgbin)
166 options.with_hg = hgbin
166 options.with_hg = hgbin
167
167
168 options.anycoverage = options.cover or options.annotate
168 options.anycoverage = options.cover or options.annotate
169 if options.anycoverage:
169 if options.anycoverage:
170 try:
170 try:
171 import coverage
171 import coverage
172 covver = version.StrictVersion(coverage.__version__).version
172 covver = version.StrictVersion(coverage.__version__).version
173 if covver < (3, 3):
173 if covver < (3, 3):
174 parser.error('coverage options require coverage 3.3 or later')
174 parser.error('coverage options require coverage 3.3 or later')
175 except ImportError:
175 except ImportError:
176 parser.error('coverage options now require the coverage package')
176 parser.error('coverage options now require the coverage package')
177
177
178 if options.anycoverage and options.local:
178 if options.anycoverage and options.local:
179 # this needs some path mangling somewhere, I guess
179 # this needs some path mangling somewhere, I guess
180 parser.error("sorry, coverage options do not work when --local "
180 parser.error("sorry, coverage options do not work when --local "
181 "is specified")
181 "is specified")
182
182
183 global vlog
183 global vlog
184 if options.verbose:
184 if options.verbose:
185 if options.jobs > 1 or options.child is not None:
185 if options.jobs > 1 or options.child is not None:
186 pid = "[%d]" % os.getpid()
186 pid = "[%d]" % os.getpid()
187 else:
187 else:
188 pid = None
188 pid = None
189 def vlog(*msg):
189 def vlog(*msg):
190 if pid:
190 if pid:
191 print pid,
191 print pid,
192 for m in msg:
192 for m in msg:
193 print m,
193 print m,
194 print
194 print
195 sys.stdout.flush()
195 sys.stdout.flush()
196 else:
196 else:
197 vlog = lambda *msg: None
197 vlog = lambda *msg: None
198
198
199 if options.tmpdir:
199 if options.tmpdir:
200 options.tmpdir = os.path.expanduser(options.tmpdir)
200 options.tmpdir = os.path.expanduser(options.tmpdir)
201
201
202 if options.jobs < 1:
202 if options.jobs < 1:
203 parser.error('--jobs must be positive')
203 parser.error('--jobs must be positive')
204 if options.interactive and options.jobs > 1:
204 if options.interactive and options.jobs > 1:
205 print '(--interactive overrides --jobs)'
205 print '(--interactive overrides --jobs)'
206 options.jobs = 1
206 options.jobs = 1
207 if options.interactive and options.debug:
207 if options.interactive and options.debug:
208 parser.error("-i/--interactive and -d/--debug are incompatible")
208 parser.error("-i/--interactive and -d/--debug are incompatible")
209 if options.debug:
209 if options.debug:
210 if options.timeout != defaults['timeout']:
210 if options.timeout != defaults['timeout']:
211 sys.stderr.write(
211 sys.stderr.write(
212 'warning: --timeout option ignored with --debug\n')
212 'warning: --timeout option ignored with --debug\n')
213 options.timeout = 0
213 options.timeout = 0
214 if options.py3k_warnings:
214 if options.py3k_warnings:
215 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
215 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
216 parser.error('--py3k-warnings can only be used on Python 2.6+')
216 parser.error('--py3k-warnings can only be used on Python 2.6+')
217 if options.blacklist:
217 if options.blacklist:
218 blacklist = dict()
218 blacklist = dict()
219 for filename in options.blacklist:
219 for filename in options.blacklist:
220 try:
220 try:
221 path = os.path.expanduser(os.path.expandvars(filename))
221 path = os.path.expanduser(os.path.expandvars(filename))
222 f = open(path, "r")
222 f = open(path, "r")
223 except IOError, err:
223 except IOError, err:
224 if err.errno != errno.ENOENT:
224 if err.errno != errno.ENOENT:
225 raise
225 raise
226 print "warning: no such blacklist file: %s" % filename
226 print "warning: no such blacklist file: %s" % filename
227 continue
227 continue
228
228
229 for line in f.readlines():
229 for line in f.readlines():
230 line = line.strip()
230 line = line.strip()
231 if line and not line.startswith('#'):
231 if line and not line.startswith('#'):
232 blacklist[line] = filename
232 blacklist[line] = filename
233
233
234 options.blacklist = blacklist
234 options.blacklist = blacklist
235
235
236 return (options, args)
236 return (options, args)
237
237
238 def rename(src, dst):
238 def rename(src, dst):
239 """Like os.rename(), trade atomicity and opened files friendliness
239 """Like os.rename(), trade atomicity and opened files friendliness
240 for existing destination support.
240 for existing destination support.
241 """
241 """
242 shutil.copy(src, dst)
242 shutil.copy(src, dst)
243 os.remove(src)
243 os.remove(src)
244
244
245 def splitnewlines(text):
245 def splitnewlines(text):
246 '''like str.splitlines, but only split on newlines.
246 '''like str.splitlines, but only split on newlines.
247 keep line endings.'''
247 keep line endings.'''
248 i = 0
248 i = 0
249 lines = []
249 lines = []
250 while True:
250 while True:
251 n = text.find('\n', i)
251 n = text.find('\n', i)
252 if n == -1:
252 if n == -1:
253 last = text[i:]
253 last = text[i:]
254 if last:
254 if last:
255 lines.append(last)
255 lines.append(last)
256 return lines
256 return lines
257 lines.append(text[i:n + 1])
257 lines.append(text[i:n + 1])
258 i = n + 1
258 i = n + 1
259
259
260 def parsehghaveoutput(lines):
260 def parsehghaveoutput(lines):
261 '''Parse hghave log lines.
261 '''Parse hghave log lines.
262 Return tuple of lists (missing, failed):
262 Return tuple of lists (missing, failed):
263 * the missing/unknown features
263 * the missing/unknown features
264 * the features for which existence check failed'''
264 * the features for which existence check failed'''
265 missing = []
265 missing = []
266 failed = []
266 failed = []
267 for line in lines:
267 for line in lines:
268 if line.startswith(SKIPPED_PREFIX):
268 if line.startswith(SKIPPED_PREFIX):
269 line = line.splitlines()[0]
269 line = line.splitlines()[0]
270 missing.append(line[len(SKIPPED_PREFIX):])
270 missing.append(line[len(SKIPPED_PREFIX):])
271 elif line.startswith(FAILED_PREFIX):
271 elif line.startswith(FAILED_PREFIX):
272 line = line.splitlines()[0]
272 line = line.splitlines()[0]
273 failed.append(line[len(FAILED_PREFIX):])
273 failed.append(line[len(FAILED_PREFIX):])
274
274
275 return missing, failed
275 return missing, failed
276
276
277 def showdiff(expected, output, ref, err):
277 def showdiff(expected, output, ref, err):
278 for line in difflib.unified_diff(expected, output, ref, err):
278 for line in difflib.unified_diff(expected, output, ref, err):
279 sys.stdout.write(line)
279 sys.stdout.write(line)
280
280
281 def findprogram(program):
281 def findprogram(program):
282 """Search PATH for a executable program"""
282 """Search PATH for a executable program"""
283 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
283 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
284 name = os.path.join(p, program)
284 name = os.path.join(p, program)
285 if os.access(name, os.X_OK):
285 if os.access(name, os.X_OK):
286 return name
286 return name
287 return None
287 return None
288
288
289 def checktools():
289 def checktools():
290 # Before we go any further, check for pre-requisite tools
290 # Before we go any further, check for pre-requisite tools
291 # stuff from coreutils (cat, rm, etc) are not tested
291 # stuff from coreutils (cat, rm, etc) are not tested
292 for p in requiredtools:
292 for p in requiredtools:
293 if os.name == 'nt':
293 if os.name == 'nt':
294 p += '.exe'
294 p += '.exe'
295 found = findprogram(p)
295 found = findprogram(p)
296 if found:
296 if found:
297 vlog("# Found prerequisite", p, "at", found)
297 vlog("# Found prerequisite", p, "at", found)
298 else:
298 else:
299 print "WARNING: Did not find prerequisite tool: "+p
299 print "WARNING: Did not find prerequisite tool: "+p
300
300
301 def killdaemons():
301 def killdaemons():
302 # Kill off any leftover daemon processes
302 # Kill off any leftover daemon processes
303 try:
303 try:
304 fp = open(DAEMON_PIDS)
304 fp = open(DAEMON_PIDS)
305 for line in fp:
305 for line in fp:
306 try:
306 try:
307 pid = int(line)
307 pid = int(line)
308 except ValueError:
308 except ValueError:
309 continue
309 continue
310 try:
310 try:
311 os.kill(pid, 0)
311 os.kill(pid, 0)
312 vlog('# Killing daemon process %d' % pid)
312 vlog('# Killing daemon process %d' % pid)
313 os.kill(pid, signal.SIGTERM)
313 os.kill(pid, signal.SIGTERM)
314 time.sleep(0.25)
314 time.sleep(0.25)
315 os.kill(pid, 0)
315 os.kill(pid, 0)
316 vlog('# Daemon process %d is stuck - really killing it' % pid)
316 vlog('# Daemon process %d is stuck - really killing it' % pid)
317 os.kill(pid, signal.SIGKILL)
317 os.kill(pid, signal.SIGKILL)
318 except OSError, err:
318 except OSError, err:
319 if err.errno != errno.ESRCH:
319 if err.errno != errno.ESRCH:
320 raise
320 raise
321 fp.close()
321 fp.close()
322 os.unlink(DAEMON_PIDS)
322 os.unlink(DAEMON_PIDS)
323 except IOError:
323 except IOError:
324 pass
324 pass
325
325
326 def cleanup(options):
326 def cleanup(options):
327 if not options.keep_tmpdir:
327 if not options.keep_tmpdir:
328 vlog("# Cleaning up HGTMP", HGTMP)
328 vlog("# Cleaning up HGTMP", HGTMP)
329 shutil.rmtree(HGTMP, True)
329 shutil.rmtree(HGTMP, True)
330
330
331 def usecorrectpython():
331 def usecorrectpython():
332 # some tests run python interpreter. they must use same
332 # some tests run python interpreter. they must use same
333 # interpreter we use or bad things will happen.
333 # interpreter we use or bad things will happen.
334 exedir, exename = os.path.split(sys.executable)
334 exedir, exename = os.path.split(sys.executable)
335 if exename == 'python':
335 if exename == 'python':
336 path = findprogram('python')
336 path = findprogram('python')
337 if os.path.dirname(path) == exedir:
337 if os.path.dirname(path) == exedir:
338 return
338 return
339 vlog('# Making python executable in test path use correct Python')
339 vlog('# Making python executable in test path use correct Python')
340 mypython = os.path.join(BINDIR, 'python')
340 mypython = os.path.join(BINDIR, 'python')
341 try:
341 try:
342 os.symlink(sys.executable, mypython)
342 os.symlink(sys.executable, mypython)
343 except AttributeError:
343 except AttributeError:
344 # windows fallback
344 # windows fallback
345 shutil.copyfile(sys.executable, mypython)
345 shutil.copyfile(sys.executable, mypython)
346 shutil.copymode(sys.executable, mypython)
346 shutil.copymode(sys.executable, mypython)
347
347
348 def installhg(options):
348 def installhg(options):
349 vlog("# Performing temporary installation of HG")
349 vlog("# Performing temporary installation of HG")
350 installerrs = os.path.join("tests", "install.err")
350 installerrs = os.path.join("tests", "install.err")
351 pure = options.pure and "--pure" or ""
351 pure = options.pure and "--pure" or ""
352
352
353 # Run installer in hg root
353 # Run installer in hg root
354 script = os.path.realpath(sys.argv[0])
354 script = os.path.realpath(sys.argv[0])
355 hgroot = os.path.dirname(os.path.dirname(script))
355 hgroot = os.path.dirname(os.path.dirname(script))
356 os.chdir(hgroot)
356 os.chdir(hgroot)
357 nohome = '--home=""'
357 nohome = '--home=""'
358 if os.name == 'nt':
358 if os.name == 'nt':
359 # The --home="" trick works only on OS where os.sep == '/'
359 # The --home="" trick works only on OS where os.sep == '/'
360 # because of a distutils convert_path() fast-path. Avoid it at
360 # because of a distutils convert_path() fast-path. Avoid it at
361 # least on Windows for now, deal with .pydistutils.cfg bugs
361 # least on Windows for now, deal with .pydistutils.cfg bugs
362 # when they happen.
362 # when they happen.
363 nohome = ''
363 nohome = ''
364 cmd = ('%s setup.py %s clean --all'
364 cmd = ('%s setup.py %s clean --all'
365 ' build --build-base="%s"'
365 ' build --build-base="%s"'
366 ' install --force --prefix="%s" --install-lib="%s"'
366 ' install --force --prefix="%s" --install-lib="%s"'
367 ' --install-scripts="%s" %s >%s 2>&1'
367 ' --install-scripts="%s" %s >%s 2>&1'
368 % (sys.executable, pure, os.path.join(HGTMP, "build"),
368 % (sys.executable, pure, os.path.join(HGTMP, "build"),
369 INST, PYTHONDIR, BINDIR, nohome, installerrs))
369 INST, PYTHONDIR, BINDIR, nohome, installerrs))
370 vlog("# Running", cmd)
370 vlog("# Running", cmd)
371 if os.system(cmd) == 0:
371 if os.system(cmd) == 0:
372 if not options.verbose:
372 if not options.verbose:
373 os.remove(installerrs)
373 os.remove(installerrs)
374 else:
374 else:
375 f = open(installerrs)
375 f = open(installerrs)
376 for line in f:
376 for line in f:
377 print line,
377 print line,
378 f.close()
378 f.close()
379 sys.exit(1)
379 sys.exit(1)
380 os.chdir(TESTDIR)
380 os.chdir(TESTDIR)
381
381
382 usecorrectpython()
382 usecorrectpython()
383
383
384 vlog("# Installing dummy diffstat")
384 vlog("# Installing dummy diffstat")
385 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
385 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
386 f.write('#!' + sys.executable + '\n'
386 f.write('#!' + sys.executable + '\n'
387 'import sys\n'
387 'import sys\n'
388 'files = 0\n'
388 'files = 0\n'
389 'for line in sys.stdin:\n'
389 'for line in sys.stdin:\n'
390 ' if line.startswith("diff "):\n'
390 ' if line.startswith("diff "):\n'
391 ' files += 1\n'
391 ' files += 1\n'
392 'sys.stdout.write("files patched: %d\\n" % files)\n')
392 'sys.stdout.write("files patched: %d\\n" % files)\n')
393 f.close()
393 f.close()
394 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
394 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
395
395
396 if options.py3k_warnings and not options.anycoverage:
396 if options.py3k_warnings and not options.anycoverage:
397 vlog("# Updating hg command to enable Py3k Warnings switch")
397 vlog("# Updating hg command to enable Py3k Warnings switch")
398 f = open(os.path.join(BINDIR, 'hg'), 'r')
398 f = open(os.path.join(BINDIR, 'hg'), 'r')
399 lines = [line.rstrip() for line in f]
399 lines = [line.rstrip() for line in f]
400 lines[0] += ' -3'
400 lines[0] += ' -3'
401 f.close()
401 f.close()
402 f = open(os.path.join(BINDIR, 'hg'), 'w')
402 f = open(os.path.join(BINDIR, 'hg'), 'w')
403 for line in lines:
403 for line in lines:
404 f.write(line + '\n')
404 f.write(line + '\n')
405 f.close()
405 f.close()
406
406
407 if options.anycoverage:
407 if options.anycoverage:
408 custom = os.path.join(TESTDIR, 'sitecustomize.py')
408 custom = os.path.join(TESTDIR, 'sitecustomize.py')
409 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
409 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
410 vlog('# Installing coverage trigger to %s' % target)
410 vlog('# Installing coverage trigger to %s' % target)
411 shutil.copyfile(custom, target)
411 shutil.copyfile(custom, target)
412 rc = os.path.join(TESTDIR, '.coveragerc')
412 rc = os.path.join(TESTDIR, '.coveragerc')
413 vlog('# Installing coverage rc to %s' % rc)
413 vlog('# Installing coverage rc to %s' % rc)
414 os.environ['COVERAGE_PROCESS_START'] = rc
414 os.environ['COVERAGE_PROCESS_START'] = rc
415 fn = os.path.join(INST, '..', '.coverage')
415 fn = os.path.join(INST, '..', '.coverage')
416 os.environ['COVERAGE_FILE'] = fn
416 os.environ['COVERAGE_FILE'] = fn
417
417
418 def outputcoverage(options):
418 def outputcoverage(options):
419
419
420 vlog('# Producing coverage report')
420 vlog('# Producing coverage report')
421 os.chdir(PYTHONDIR)
421 os.chdir(PYTHONDIR)
422
422
423 def covrun(*args):
423 def covrun(*args):
424 cmd = 'coverage %s' % ' '.join(args)
424 cmd = 'coverage %s' % ' '.join(args)
425 vlog('# Running: %s' % cmd)
425 vlog('# Running: %s' % cmd)
426 os.system(cmd)
426 os.system(cmd)
427
427
428 if options.child:
428 if options.child:
429 return
429 return
430
430
431 covrun('-c')
431 covrun('-c')
432 omit = ','.join([BINDIR, TESTDIR])
432 omit = ','.join([BINDIR, TESTDIR])
433 covrun('-i', '-r', '"--omit=%s"' % omit) # report
433 covrun('-i', '-r', '"--omit=%s"' % omit) # report
434 if options.annotate:
434 if options.annotate:
435 adir = os.path.join(TESTDIR, 'annotated')
435 adir = os.path.join(TESTDIR, 'annotated')
436 if not os.path.isdir(adir):
436 if not os.path.isdir(adir):
437 os.mkdir(adir)
437 os.mkdir(adir)
438 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
438 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
439
439
440 class Timeout(Exception):
440 class Timeout(Exception):
441 pass
441 pass
442
442
443 def alarmed(signum, frame):
443 def alarmed(signum, frame):
444 raise Timeout
444 raise Timeout
445
445
446 def pytest(test, options):
446 def pytest(test, options):
447 py3kswitch = options.py3k_warnings and ' -3' or ''
447 py3kswitch = options.py3k_warnings and ' -3' or ''
448 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
448 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
449 vlog("# Running", cmd)
449 vlog("# Running", cmd)
450 return run(cmd, options)
450 return run(cmd, options)
451
451
452 def shtest(test, options):
452 def shtest(test, options):
453 cmd = '"%s"' % test
453 cmd = '"%s"' % test
454 vlog("# Running", cmd)
454 vlog("# Running", cmd)
455 return run(cmd, options)
455 return run(cmd, options)
456
456
457 def battest(test, options):
457 def battest(test, options):
458 # To reliably get the error code from batch files on WinXP,
458 # To reliably get the error code from batch files on WinXP,
459 # the "cmd /c call" prefix is needed. Grrr
459 # the "cmd /c call" prefix is needed. Grrr
460 cmd = 'cmd /c call "%s"' % testpath
460 cmd = 'cmd /c call "%s"' % testpath
461 vlog("# Running", cmd)
461 vlog("# Running", cmd)
462 return run(cmd, options)
462 return run(cmd, options)
463
463
464 def tsttest(test, options):
464 def tsttest(test, options):
465 t = open(test)
465 t = open(test)
466 out = []
466 out = []
467 script = []
467 script = []
468 salt = "SALT" + str(time.time())
468 salt = "SALT" + str(time.time())
469
469
470 pos = prepos = -1
470 pos = prepos = -1
471 after = {}
471 after = {}
472 expected = {}
472 expected = {}
473 for n, l in enumerate(t):
473 for n, l in enumerate(t):
474 if l.startswith(' $ '): # commands
474 if l.startswith(' $ '): # commands
475 after.setdefault(pos, []).append(l)
475 after.setdefault(pos, []).append(l)
476 prepos = pos
476 prepos = pos
477 pos = n
477 pos = n
478 script.append('echo %s %s $?\n' % (salt, n))
478 script.append('echo %s %s $?\n' % (salt, n))
479 script.append(l[4:])
479 script.append(l[4:])
480 elif l.startswith(' > '): # continuations
480 elif l.startswith(' > '): # continuations
481 after.setdefault(prepos, []).append(l)
481 after.setdefault(prepos, []).append(l)
482 script.append(l[4:])
482 script.append(l[4:])
483 elif l.startswith(' '): # results
483 elif l.startswith(' '): # results
484 # queue up a list of expected results
484 # queue up a list of expected results
485 expected.setdefault(pos, []).append(l[2:])
485 expected.setdefault(pos, []).append(l[2:])
486 else:
486 else:
487 # non-command/result - queue up for merged output
487 # non-command/result - queue up for merged output
488 after.setdefault(pos, []).append(l)
488 after.setdefault(pos, []).append(l)
489
489
490 if script and not script[-1].endswith('\n'):
491 script[-1] = script[-1] + '\n'
490 script.append('echo %s %s $?\n' % (salt, n + 1))
492 script.append('echo %s %s $?\n' % (salt, n + 1))
491
493
492 fd, name = tempfile.mkstemp(suffix='hg-tst')
494 fd, name = tempfile.mkstemp(suffix='hg-tst')
493
495
494 try:
496 try:
495 for l in script:
497 for l in script:
496 os.write(fd, l)
498 os.write(fd, l)
497 os.close(fd)
499 os.close(fd)
498
500
499 cmd = '/bin/sh "%s"' % name
501 cmd = '/bin/sh "%s"' % name
500 vlog("# Running", cmd)
502 vlog("# Running", cmd)
501 exitcode, output = run(cmd, options)
503 exitcode, output = run(cmd, options)
502 # do not merge output if skipped, return hghave message instead
504 # do not merge output if skipped, return hghave message instead
503 if exitcode == SKIPPED_STATUS:
505 if exitcode == SKIPPED_STATUS:
504 return exitcode, output
506 return exitcode, output
505 finally:
507 finally:
506 os.remove(name)
508 os.remove(name)
507
509
508 def rematch(el, l):
510 def rematch(el, l):
509 try:
511 try:
510 # ensure that the regex matches to the end of the string
512 # ensure that the regex matches to the end of the string
511 return re.match(el + r'\Z', l)
513 return re.match(el + r'\Z', l)
512 except re.error:
514 except re.error:
513 # el is an invalid regex
515 # el is an invalid regex
514 return False
516 return False
515
517
516 def globmatch(el, l):
518 def globmatch(el, l):
517 # The only supported special characters are * and ?. Escaping is
519 # The only supported special characters are * and ?. Escaping is
518 # supported.
520 # supported.
519 i, n = 0, len(el)
521 i, n = 0, len(el)
520 res = ''
522 res = ''
521 while i < n:
523 while i < n:
522 c = el[i]
524 c = el[i]
523 i += 1
525 i += 1
524 if c == '\\' and el[i] in '*?\\':
526 if c == '\\' and el[i] in '*?\\':
525 res += el[i - 1:i + 1]
527 res += el[i - 1:i + 1]
526 i += 1
528 i += 1
527 elif c == '*':
529 elif c == '*':
528 res += '.*'
530 res += '.*'
529 elif c == '?':
531 elif c == '?':
530 res += '.'
532 res += '.'
531 else:
533 else:
532 res += re.escape(c)
534 res += re.escape(c)
533 return rematch(res, l)
535 return rematch(res, l)
534
536
535 pos = -1
537 pos = -1
536 postout = []
538 postout = []
537 ret = 0
539 ret = 0
538 for n, l in enumerate(output):
540 for n, l in enumerate(output):
539 if l.startswith(salt):
541 if l.startswith(salt):
540 # add on last return code
542 # add on last return code
541 ret = int(l.split()[2])
543 ret = int(l.split()[2])
542 if ret != 0:
544 if ret != 0:
543 postout.append(" [%s]\n" % ret)
545 postout.append(" [%s]\n" % ret)
544 if pos in after:
546 if pos in after:
545 postout += after.pop(pos)
547 postout += after.pop(pos)
546 pos = int(l.split()[1])
548 pos = int(l.split()[1])
547 else:
549 else:
548 el = None
550 el = None
549 if pos in expected and expected[pos]:
551 if pos in expected and expected[pos]:
550 el = expected[pos].pop(0)
552 el = expected[pos].pop(0)
551
553
552 if el == l: # perfect match (fast)
554 if el == l: # perfect match (fast)
553 postout.append(" " + l)
555 postout.append(" " + l)
554 elif (el and
556 elif (el and
555 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
557 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
556 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l))):
558 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l))):
557 postout.append(" " + el) # fallback regex/glob match
559 postout.append(" " + el) # fallback regex/glob match
558 else:
560 else:
559 postout.append(" " + l) # let diff deal with it
561 postout.append(" " + l) # let diff deal with it
560
562
561 if pos in after:
563 if pos in after:
562 postout += after.pop(pos)
564 postout += after.pop(pos)
563
565
564 return exitcode, postout
566 return exitcode, postout
565
567
566 def run(cmd, options):
568 def run(cmd, options):
567 """Run command in a sub-process, capturing the output (stdout and stderr).
569 """Run command in a sub-process, capturing the output (stdout and stderr).
568 Return a tuple (exitcode, output). output is None in debug mode."""
570 Return a tuple (exitcode, output). output is None in debug mode."""
569 # TODO: Use subprocess.Popen if we're running on Python 2.4
571 # TODO: Use subprocess.Popen if we're running on Python 2.4
570 if options.debug:
572 if options.debug:
571 proc = subprocess.Popen(cmd, shell=True)
573 proc = subprocess.Popen(cmd, shell=True)
572 ret = proc.wait()
574 ret = proc.wait()
573 return (ret, None)
575 return (ret, None)
574
576
575 if os.name == 'nt' or sys.platform.startswith('java'):
577 if os.name == 'nt' or sys.platform.startswith('java'):
576 tochild, fromchild = os.popen4(cmd)
578 tochild, fromchild = os.popen4(cmd)
577 tochild.close()
579 tochild.close()
578 output = fromchild.read()
580 output = fromchild.read()
579 ret = fromchild.close()
581 ret = fromchild.close()
580 if ret == None:
582 if ret == None:
581 ret = 0
583 ret = 0
582 else:
584 else:
583 proc = Popen4(cmd)
585 proc = Popen4(cmd)
584 def cleanup():
586 def cleanup():
585 os.kill(proc.pid, signal.SIGTERM)
587 os.kill(proc.pid, signal.SIGTERM)
586 ret = proc.wait()
588 ret = proc.wait()
587 if ret == 0:
589 if ret == 0:
588 ret = signal.SIGTERM << 8
590 ret = signal.SIGTERM << 8
589 killdaemons()
591 killdaemons()
590 return ret
592 return ret
591
593
592 try:
594 try:
593 output = ''
595 output = ''
594 proc.tochild.close()
596 proc.tochild.close()
595 output = proc.fromchild.read()
597 output = proc.fromchild.read()
596 ret = proc.wait()
598 ret = proc.wait()
597 if os.WIFEXITED(ret):
599 if os.WIFEXITED(ret):
598 ret = os.WEXITSTATUS(ret)
600 ret = os.WEXITSTATUS(ret)
599 except Timeout:
601 except Timeout:
600 vlog('# Process %d timed out - killing it' % proc.pid)
602 vlog('# Process %d timed out - killing it' % proc.pid)
601 ret = cleanup()
603 ret = cleanup()
602 output += ("\n### Abort: timeout after %d seconds.\n"
604 output += ("\n### Abort: timeout after %d seconds.\n"
603 % options.timeout)
605 % options.timeout)
604 except KeyboardInterrupt:
606 except KeyboardInterrupt:
605 vlog('# Handling keyboard interrupt')
607 vlog('# Handling keyboard interrupt')
606 cleanup()
608 cleanup()
607 raise
609 raise
608
610
609 return ret, splitnewlines(output)
611 return ret, splitnewlines(output)
610
612
611 def runone(options, test, skips, fails):
613 def runone(options, test, skips, fails):
612 '''tristate output:
614 '''tristate output:
613 None -> skipped
615 None -> skipped
614 True -> passed
616 True -> passed
615 False -> failed'''
617 False -> failed'''
616
618
617 def skip(msg):
619 def skip(msg):
618 if not options.verbose:
620 if not options.verbose:
619 skips.append((test, msg))
621 skips.append((test, msg))
620 else:
622 else:
621 print "\nSkipping %s: %s" % (testpath, msg)
623 print "\nSkipping %s: %s" % (testpath, msg)
622 return None
624 return None
623
625
624 def fail(msg):
626 def fail(msg):
625 fails.append((test, msg))
627 fails.append((test, msg))
626 if not options.nodiff:
628 if not options.nodiff:
627 print "\nERROR: %s %s" % (testpath, msg)
629 print "\nERROR: %s %s" % (testpath, msg)
628 return None
630 return None
629
631
630 vlog("# Test", test)
632 vlog("# Test", test)
631
633
632 # create a fresh hgrc
634 # create a fresh hgrc
633 hgrc = open(HGRCPATH, 'w+')
635 hgrc = open(HGRCPATH, 'w+')
634 hgrc.write('[ui]\n')
636 hgrc.write('[ui]\n')
635 hgrc.write('slash = True\n')
637 hgrc.write('slash = True\n')
636 hgrc.write('[defaults]\n')
638 hgrc.write('[defaults]\n')
637 hgrc.write('backout = -d "0 0"\n')
639 hgrc.write('backout = -d "0 0"\n')
638 hgrc.write('commit = -d "0 0"\n')
640 hgrc.write('commit = -d "0 0"\n')
639 hgrc.write('tag = -d "0 0"\n')
641 hgrc.write('tag = -d "0 0"\n')
640 if options.inotify:
642 if options.inotify:
641 hgrc.write('[extensions]\n')
643 hgrc.write('[extensions]\n')
642 hgrc.write('inotify=\n')
644 hgrc.write('inotify=\n')
643 hgrc.write('[inotify]\n')
645 hgrc.write('[inotify]\n')
644 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
646 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
645 hgrc.write('appendpid=True\n')
647 hgrc.write('appendpid=True\n')
646 hgrc.close()
648 hgrc.close()
647
649
648 testpath = os.path.join(TESTDIR, test)
650 testpath = os.path.join(TESTDIR, test)
649 ref = os.path.join(TESTDIR, test+".out")
651 ref = os.path.join(TESTDIR, test+".out")
650 err = os.path.join(TESTDIR, test+".err")
652 err = os.path.join(TESTDIR, test+".err")
651 if os.path.exists(err):
653 if os.path.exists(err):
652 os.remove(err) # Remove any previous output files
654 os.remove(err) # Remove any previous output files
653 try:
655 try:
654 tf = open(testpath)
656 tf = open(testpath)
655 firstline = tf.readline().rstrip()
657 firstline = tf.readline().rstrip()
656 tf.close()
658 tf.close()
657 except:
659 except:
658 firstline = ''
660 firstline = ''
659 lctest = test.lower()
661 lctest = test.lower()
660
662
661 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
663 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
662 runner = pytest
664 runner = pytest
663 elif lctest.endswith('.bat'):
665 elif lctest.endswith('.bat'):
664 # do not run batch scripts on non-windows
666 # do not run batch scripts on non-windows
665 if os.name != 'nt':
667 if os.name != 'nt':
666 return skip("batch script")
668 return skip("batch script")
667 runner = battest
669 runner = battest
668 elif lctest.endswith('.t'):
670 elif lctest.endswith('.t'):
669 runner = tsttest
671 runner = tsttest
670 ref = testpath
672 ref = testpath
671 else:
673 else:
672 # do not run shell scripts on windows
674 # do not run shell scripts on windows
673 if os.name == 'nt':
675 if os.name == 'nt':
674 return skip("shell script")
676 return skip("shell script")
675 # do not try to run non-executable programs
677 # do not try to run non-executable programs
676 if not os.path.exists(testpath):
678 if not os.path.exists(testpath):
677 return fail("does not exist")
679 return fail("does not exist")
678 elif not os.access(testpath, os.X_OK):
680 elif not os.access(testpath, os.X_OK):
679 return skip("not executable")
681 return skip("not executable")
680 runner = shtest
682 runner = shtest
681
683
682 # Make a tmp subdirectory to work in
684 # Make a tmp subdirectory to work in
683 tmpd = os.path.join(HGTMP, test)
685 tmpd = os.path.join(HGTMP, test)
684 os.mkdir(tmpd)
686 os.mkdir(tmpd)
685 os.chdir(tmpd)
687 os.chdir(tmpd)
686
688
687 if options.timeout > 0:
689 if options.timeout > 0:
688 signal.alarm(options.timeout)
690 signal.alarm(options.timeout)
689
691
690 ret, out = runner(testpath, options)
692 ret, out = runner(testpath, options)
691 vlog("# Ret was:", ret)
693 vlog("# Ret was:", ret)
692
694
693 if options.timeout > 0:
695 if options.timeout > 0:
694 signal.alarm(0)
696 signal.alarm(0)
695
697
696 mark = '.'
698 mark = '.'
697
699
698 skipped = (ret == SKIPPED_STATUS)
700 skipped = (ret == SKIPPED_STATUS)
699
701
700 # If we're not in --debug mode and reference output file exists,
702 # If we're not in --debug mode and reference output file exists,
701 # check test output against it.
703 # check test output against it.
702 if options.debug:
704 if options.debug:
703 refout = None # to match out == None
705 refout = None # to match out == None
704 elif os.path.exists(ref):
706 elif os.path.exists(ref):
705 f = open(ref, "r")
707 f = open(ref, "r")
706 refout = splitnewlines(f.read())
708 refout = splitnewlines(f.read())
707 f.close()
709 f.close()
708 else:
710 else:
709 refout = []
711 refout = []
710
712
711 if (ret != 0 or out != refout) and not skipped and not options.debug:
713 if (ret != 0 or out != refout) and not skipped and not options.debug:
712 # Save errors to a file for diagnosis
714 # Save errors to a file for diagnosis
713 f = open(err, "wb")
715 f = open(err, "wb")
714 for line in out:
716 for line in out:
715 f.write(line)
717 f.write(line)
716 f.close()
718 f.close()
717
719
718 if skipped:
720 if skipped:
719 mark = 's'
721 mark = 's'
720 if out is None: # debug mode: nothing to parse
722 if out is None: # debug mode: nothing to parse
721 missing = ['unknown']
723 missing = ['unknown']
722 failed = None
724 failed = None
723 else:
725 else:
724 missing, failed = parsehghaveoutput(out)
726 missing, failed = parsehghaveoutput(out)
725 if not missing:
727 if not missing:
726 missing = ['irrelevant']
728 missing = ['irrelevant']
727 if failed:
729 if failed:
728 fail("hghave failed checking for %s" % failed[-1])
730 fail("hghave failed checking for %s" % failed[-1])
729 skipped = False
731 skipped = False
730 else:
732 else:
731 skip(missing[-1])
733 skip(missing[-1])
732 elif out != refout:
734 elif out != refout:
733 mark = '!'
735 mark = '!'
734 if ret:
736 if ret:
735 fail("output changed and returned error code %d" % ret)
737 fail("output changed and returned error code %d" % ret)
736 else:
738 else:
737 fail("output changed")
739 fail("output changed")
738 if not options.nodiff:
740 if not options.nodiff:
739 if options.view:
741 if options.view:
740 os.system("%s %s %s" % (options.view, ref, err))
742 os.system("%s %s %s" % (options.view, ref, err))
741 else:
743 else:
742 showdiff(refout, out, ref, err)
744 showdiff(refout, out, ref, err)
743 ret = 1
745 ret = 1
744 elif ret:
746 elif ret:
745 mark = '!'
747 mark = '!'
746 fail("returned error code %d" % ret)
748 fail("returned error code %d" % ret)
747
749
748 if not options.verbose:
750 if not options.verbose:
749 sys.stdout.write(mark)
751 sys.stdout.write(mark)
750 sys.stdout.flush()
752 sys.stdout.flush()
751
753
752 killdaemons()
754 killdaemons()
753
755
754 os.chdir(TESTDIR)
756 os.chdir(TESTDIR)
755 if not options.keep_tmpdir:
757 if not options.keep_tmpdir:
756 shutil.rmtree(tmpd, True)
758 shutil.rmtree(tmpd, True)
757 if skipped:
759 if skipped:
758 return None
760 return None
759 return ret == 0
761 return ret == 0
760
762
761 _hgpath = None
763 _hgpath = None
762
764
763 def _gethgpath():
765 def _gethgpath():
764 """Return the path to the mercurial package that is actually found by
766 """Return the path to the mercurial package that is actually found by
765 the current Python interpreter."""
767 the current Python interpreter."""
766 global _hgpath
768 global _hgpath
767 if _hgpath is not None:
769 if _hgpath is not None:
768 return _hgpath
770 return _hgpath
769
771
770 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
772 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
771 pipe = os.popen(cmd % PYTHON)
773 pipe = os.popen(cmd % PYTHON)
772 try:
774 try:
773 _hgpath = pipe.read().strip()
775 _hgpath = pipe.read().strip()
774 finally:
776 finally:
775 pipe.close()
777 pipe.close()
776 return _hgpath
778 return _hgpath
777
779
778 def _checkhglib(verb):
780 def _checkhglib(verb):
779 """Ensure that the 'mercurial' package imported by python is
781 """Ensure that the 'mercurial' package imported by python is
780 the one we expect it to be. If not, print a warning to stderr."""
782 the one we expect it to be. If not, print a warning to stderr."""
781 expecthg = os.path.join(PYTHONDIR, 'mercurial')
783 expecthg = os.path.join(PYTHONDIR, 'mercurial')
782 actualhg = _gethgpath()
784 actualhg = _gethgpath()
783 if actualhg != expecthg:
785 if actualhg != expecthg:
784 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
786 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
785 ' (expected %s)\n'
787 ' (expected %s)\n'
786 % (verb, actualhg, expecthg))
788 % (verb, actualhg, expecthg))
787
789
788 def runchildren(options, tests):
790 def runchildren(options, tests):
789 if INST:
791 if INST:
790 installhg(options)
792 installhg(options)
791 _checkhglib("Testing")
793 _checkhglib("Testing")
792
794
793 optcopy = dict(options.__dict__)
795 optcopy = dict(options.__dict__)
794 optcopy['jobs'] = 1
796 optcopy['jobs'] = 1
795 del optcopy['blacklist']
797 del optcopy['blacklist']
796 if optcopy['with_hg'] is None:
798 if optcopy['with_hg'] is None:
797 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
799 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
798 optcopy.pop('anycoverage', None)
800 optcopy.pop('anycoverage', None)
799
801
800 opts = []
802 opts = []
801 for opt, value in optcopy.iteritems():
803 for opt, value in optcopy.iteritems():
802 name = '--' + opt.replace('_', '-')
804 name = '--' + opt.replace('_', '-')
803 if value is True:
805 if value is True:
804 opts.append(name)
806 opts.append(name)
805 elif value is not None:
807 elif value is not None:
806 opts.append(name + '=' + str(value))
808 opts.append(name + '=' + str(value))
807
809
808 tests.reverse()
810 tests.reverse()
809 jobs = [[] for j in xrange(options.jobs)]
811 jobs = [[] for j in xrange(options.jobs)]
810 while tests:
812 while tests:
811 for job in jobs:
813 for job in jobs:
812 if not tests:
814 if not tests:
813 break
815 break
814 job.append(tests.pop())
816 job.append(tests.pop())
815 fps = {}
817 fps = {}
816
818
817 for j, job in enumerate(jobs):
819 for j, job in enumerate(jobs):
818 if not job:
820 if not job:
819 continue
821 continue
820 rfd, wfd = os.pipe()
822 rfd, wfd = os.pipe()
821 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
823 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
822 childtmp = os.path.join(HGTMP, 'child%d' % j)
824 childtmp = os.path.join(HGTMP, 'child%d' % j)
823 childopts += ['--tmpdir', childtmp]
825 childopts += ['--tmpdir', childtmp]
824 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
826 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
825 vlog(' '.join(cmdline))
827 vlog(' '.join(cmdline))
826 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
828 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
827 os.close(wfd)
829 os.close(wfd)
828 signal.signal(signal.SIGINT, signal.SIG_IGN)
830 signal.signal(signal.SIGINT, signal.SIG_IGN)
829 failures = 0
831 failures = 0
830 tested, skipped, failed = 0, 0, 0
832 tested, skipped, failed = 0, 0, 0
831 skips = []
833 skips = []
832 fails = []
834 fails = []
833 while fps:
835 while fps:
834 pid, status = os.wait()
836 pid, status = os.wait()
835 fp = fps.pop(pid)
837 fp = fps.pop(pid)
836 l = fp.read().splitlines()
838 l = fp.read().splitlines()
837 try:
839 try:
838 test, skip, fail = map(int, l[:3])
840 test, skip, fail = map(int, l[:3])
839 except ValueError:
841 except ValueError:
840 test, skip, fail = 0, 0, 0
842 test, skip, fail = 0, 0, 0
841 split = -fail or len(l)
843 split = -fail or len(l)
842 for s in l[3:split]:
844 for s in l[3:split]:
843 skips.append(s.split(" ", 1))
845 skips.append(s.split(" ", 1))
844 for s in l[split:]:
846 for s in l[split:]:
845 fails.append(s.split(" ", 1))
847 fails.append(s.split(" ", 1))
846 tested += test
848 tested += test
847 skipped += skip
849 skipped += skip
848 failed += fail
850 failed += fail
849 vlog('pid %d exited, status %d' % (pid, status))
851 vlog('pid %d exited, status %d' % (pid, status))
850 failures |= status
852 failures |= status
851 print
853 print
852 if not options.noskips:
854 if not options.noskips:
853 for s in skips:
855 for s in skips:
854 print "Skipped %s: %s" % (s[0], s[1])
856 print "Skipped %s: %s" % (s[0], s[1])
855 for s in fails:
857 for s in fails:
856 print "Failed %s: %s" % (s[0], s[1])
858 print "Failed %s: %s" % (s[0], s[1])
857
859
858 _checkhglib("Tested")
860 _checkhglib("Tested")
859 print "# Ran %d tests, %d skipped, %d failed." % (
861 print "# Ran %d tests, %d skipped, %d failed." % (
860 tested, skipped, failed)
862 tested, skipped, failed)
861
863
862 if options.anycoverage:
864 if options.anycoverage:
863 outputcoverage(options)
865 outputcoverage(options)
864 sys.exit(failures != 0)
866 sys.exit(failures != 0)
865
867
866 def runtests(options, tests):
868 def runtests(options, tests):
867 global DAEMON_PIDS, HGRCPATH
869 global DAEMON_PIDS, HGRCPATH
868 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
870 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
869 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
871 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
870
872
871 try:
873 try:
872 if INST:
874 if INST:
873 installhg(options)
875 installhg(options)
874 _checkhglib("Testing")
876 _checkhglib("Testing")
875
877
876 if options.timeout > 0:
878 if options.timeout > 0:
877 try:
879 try:
878 signal.signal(signal.SIGALRM, alarmed)
880 signal.signal(signal.SIGALRM, alarmed)
879 vlog('# Running each test with %d second timeout' %
881 vlog('# Running each test with %d second timeout' %
880 options.timeout)
882 options.timeout)
881 except AttributeError:
883 except AttributeError:
882 print 'WARNING: cannot run tests with timeouts'
884 print 'WARNING: cannot run tests with timeouts'
883 options.timeout = 0
885 options.timeout = 0
884
886
885 tested = 0
887 tested = 0
886 failed = 0
888 failed = 0
887 skipped = 0
889 skipped = 0
888
890
889 if options.restart:
891 if options.restart:
890 orig = list(tests)
892 orig = list(tests)
891 while tests:
893 while tests:
892 if os.path.exists(tests[0] + ".err"):
894 if os.path.exists(tests[0] + ".err"):
893 break
895 break
894 tests.pop(0)
896 tests.pop(0)
895 if not tests:
897 if not tests:
896 print "running all tests"
898 print "running all tests"
897 tests = orig
899 tests = orig
898
900
899 skips = []
901 skips = []
900 fails = []
902 fails = []
901
903
902 for test in tests:
904 for test in tests:
903 if options.blacklist:
905 if options.blacklist:
904 filename = options.blacklist.get(test)
906 filename = options.blacklist.get(test)
905 if filename is not None:
907 if filename is not None:
906 skips.append((test, "blacklisted (%s)" % filename))
908 skips.append((test, "blacklisted (%s)" % filename))
907 skipped += 1
909 skipped += 1
908 continue
910 continue
909
911
910 if options.retest and not os.path.exists(test + ".err"):
912 if options.retest and not os.path.exists(test + ".err"):
911 skipped += 1
913 skipped += 1
912 continue
914 continue
913
915
914 if options.keywords:
916 if options.keywords:
915 t = open(test).read().lower() + test.lower()
917 t = open(test).read().lower() + test.lower()
916 for k in options.keywords.lower().split():
918 for k in options.keywords.lower().split():
917 if k in t:
919 if k in t:
918 break
920 break
919 else:
921 else:
920 skipped += 1
922 skipped += 1
921 continue
923 continue
922
924
923 ret = runone(options, test, skips, fails)
925 ret = runone(options, test, skips, fails)
924 if ret is None:
926 if ret is None:
925 skipped += 1
927 skipped += 1
926 elif not ret:
928 elif not ret:
927 if options.interactive:
929 if options.interactive:
928 print "Accept this change? [n] ",
930 print "Accept this change? [n] ",
929 answer = sys.stdin.readline().strip()
931 answer = sys.stdin.readline().strip()
930 if answer.lower() in "y yes".split():
932 if answer.lower() in "y yes".split():
931 if test.endswith(".t"):
933 if test.endswith(".t"):
932 rename(test + ".err", test)
934 rename(test + ".err", test)
933 else:
935 else:
934 rename(test + ".err", test + ".out")
936 rename(test + ".err", test + ".out")
935 tested += 1
937 tested += 1
936 fails.pop()
938 fails.pop()
937 continue
939 continue
938 failed += 1
940 failed += 1
939 if options.first:
941 if options.first:
940 break
942 break
941 tested += 1
943 tested += 1
942
944
943 if options.child:
945 if options.child:
944 fp = os.fdopen(options.child, 'w')
946 fp = os.fdopen(options.child, 'w')
945 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
947 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
946 for s in skips:
948 for s in skips:
947 fp.write("%s %s\n" % s)
949 fp.write("%s %s\n" % s)
948 for s in fails:
950 for s in fails:
949 fp.write("%s %s\n" % s)
951 fp.write("%s %s\n" % s)
950 fp.close()
952 fp.close()
951 else:
953 else:
952 print
954 print
953 for s in skips:
955 for s in skips:
954 print "Skipped %s: %s" % s
956 print "Skipped %s: %s" % s
955 for s in fails:
957 for s in fails:
956 print "Failed %s: %s" % s
958 print "Failed %s: %s" % s
957 _checkhglib("Tested")
959 _checkhglib("Tested")
958 print "# Ran %d tests, %d skipped, %d failed." % (
960 print "# Ran %d tests, %d skipped, %d failed." % (
959 tested, skipped, failed)
961 tested, skipped, failed)
960
962
961 if options.anycoverage:
963 if options.anycoverage:
962 outputcoverage(options)
964 outputcoverage(options)
963 except KeyboardInterrupt:
965 except KeyboardInterrupt:
964 failed = True
966 failed = True
965 print "\ninterrupted!"
967 print "\ninterrupted!"
966
968
967 if failed:
969 if failed:
968 sys.exit(1)
970 sys.exit(1)
969
971
970 def main():
972 def main():
971 (options, args) = parseargs()
973 (options, args) = parseargs()
972 if not options.child:
974 if not options.child:
973 os.umask(022)
975 os.umask(022)
974
976
975 checktools()
977 checktools()
976
978
977 # Reset some environment variables to well-known values so that
979 # Reset some environment variables to well-known values so that
978 # the tests produce repeatable output.
980 # the tests produce repeatable output.
979 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
981 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
980 os.environ['TZ'] = 'GMT'
982 os.environ['TZ'] = 'GMT'
981 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
983 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
982 os.environ['CDPATH'] = ''
984 os.environ['CDPATH'] = ''
983 os.environ['COLUMNS'] = '80'
985 os.environ['COLUMNS'] = '80'
984 os.environ['GREP_OPTIONS'] = ''
986 os.environ['GREP_OPTIONS'] = ''
985 os.environ['http_proxy'] = ''
987 os.environ['http_proxy'] = ''
986
988
987 # unset env related to hooks
989 # unset env related to hooks
988 for k in os.environ.keys():
990 for k in os.environ.keys():
989 if k.startswith('HG_'):
991 if k.startswith('HG_'):
990 # can't remove on solaris
992 # can't remove on solaris
991 os.environ[k] = ''
993 os.environ[k] = ''
992 del os.environ[k]
994 del os.environ[k]
993
995
994 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
996 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
995 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
997 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
996 if options.tmpdir:
998 if options.tmpdir:
997 options.keep_tmpdir = True
999 options.keep_tmpdir = True
998 tmpdir = options.tmpdir
1000 tmpdir = options.tmpdir
999 if os.path.exists(tmpdir):
1001 if os.path.exists(tmpdir):
1000 # Meaning of tmpdir has changed since 1.3: we used to create
1002 # Meaning of tmpdir has changed since 1.3: we used to create
1001 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1003 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1002 # tmpdir already exists.
1004 # tmpdir already exists.
1003 sys.exit("error: temp dir %r already exists" % tmpdir)
1005 sys.exit("error: temp dir %r already exists" % tmpdir)
1004
1006
1005 # Automatically removing tmpdir sounds convenient, but could
1007 # Automatically removing tmpdir sounds convenient, but could
1006 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1008 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1007 # or "--tmpdir=$HOME".
1009 # or "--tmpdir=$HOME".
1008 #vlog("# Removing temp dir", tmpdir)
1010 #vlog("# Removing temp dir", tmpdir)
1009 #shutil.rmtree(tmpdir)
1011 #shutil.rmtree(tmpdir)
1010 os.makedirs(tmpdir)
1012 os.makedirs(tmpdir)
1011 else:
1013 else:
1012 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1014 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1013 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1015 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1014 DAEMON_PIDS = None
1016 DAEMON_PIDS = None
1015 HGRCPATH = None
1017 HGRCPATH = None
1016
1018
1017 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1019 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1018 os.environ["HGMERGE"] = "internal:merge"
1020 os.environ["HGMERGE"] = "internal:merge"
1019 os.environ["HGUSER"] = "test"
1021 os.environ["HGUSER"] = "test"
1020 os.environ["HGENCODING"] = "ascii"
1022 os.environ["HGENCODING"] = "ascii"
1021 os.environ["HGENCODINGMODE"] = "strict"
1023 os.environ["HGENCODINGMODE"] = "strict"
1022 os.environ["HGPORT"] = str(options.port)
1024 os.environ["HGPORT"] = str(options.port)
1023 os.environ["HGPORT1"] = str(options.port + 1)
1025 os.environ["HGPORT1"] = str(options.port + 1)
1024 os.environ["HGPORT2"] = str(options.port + 2)
1026 os.environ["HGPORT2"] = str(options.port + 2)
1025
1027
1026 if options.with_hg:
1028 if options.with_hg:
1027 INST = None
1029 INST = None
1028 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1030 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1029
1031
1030 # This looks redundant with how Python initializes sys.path from
1032 # This looks redundant with how Python initializes sys.path from
1031 # the location of the script being executed. Needed because the
1033 # the location of the script being executed. Needed because the
1032 # "hg" specified by --with-hg is not the only Python script
1034 # "hg" specified by --with-hg is not the only Python script
1033 # executed in the test suite that needs to import 'mercurial'
1035 # executed in the test suite that needs to import 'mercurial'
1034 # ... which means it's not really redundant at all.
1036 # ... which means it's not really redundant at all.
1035 PYTHONDIR = BINDIR
1037 PYTHONDIR = BINDIR
1036 else:
1038 else:
1037 INST = os.path.join(HGTMP, "install")
1039 INST = os.path.join(HGTMP, "install")
1038 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1040 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1039 PYTHONDIR = os.path.join(INST, "lib", "python")
1041 PYTHONDIR = os.path.join(INST, "lib", "python")
1040
1042
1041 os.environ["BINDIR"] = BINDIR
1043 os.environ["BINDIR"] = BINDIR
1042 os.environ["PYTHON"] = PYTHON
1044 os.environ["PYTHON"] = PYTHON
1043
1045
1044 if not options.child:
1046 if not options.child:
1045 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1047 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1046 os.environ["PATH"] = os.pathsep.join(path)
1048 os.environ["PATH"] = os.pathsep.join(path)
1047
1049
1048 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1050 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1049 # can run .../tests/run-tests.py test-foo where test-foo
1051 # can run .../tests/run-tests.py test-foo where test-foo
1050 # adds an extension to HGRC
1052 # adds an extension to HGRC
1051 pypath = [PYTHONDIR, TESTDIR]
1053 pypath = [PYTHONDIR, TESTDIR]
1052 # We have to augment PYTHONPATH, rather than simply replacing
1054 # We have to augment PYTHONPATH, rather than simply replacing
1053 # it, in case external libraries are only available via current
1055 # it, in case external libraries are only available via current
1054 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1056 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1055 # are in /opt/subversion.)
1057 # are in /opt/subversion.)
1056 oldpypath = os.environ.get(IMPL_PATH)
1058 oldpypath = os.environ.get(IMPL_PATH)
1057 if oldpypath:
1059 if oldpypath:
1058 pypath.append(oldpypath)
1060 pypath.append(oldpypath)
1059 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1061 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1060
1062
1061 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1063 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1062
1064
1063 if len(args) == 0:
1065 if len(args) == 0:
1064 args = os.listdir(".")
1066 args = os.listdir(".")
1065 args.sort()
1067 args.sort()
1066
1068
1067 tests = []
1069 tests = []
1068 for test in args:
1070 for test in args:
1069 if (test.startswith("test-") and '~' not in test and
1071 if (test.startswith("test-") and '~' not in test and
1070 ('.' not in test or test.endswith('.py') or
1072 ('.' not in test or test.endswith('.py') or
1071 test.endswith('.bat') or test.endswith('.t'))):
1073 test.endswith('.bat') or test.endswith('.t'))):
1072 tests.append(test)
1074 tests.append(test)
1073 if not tests:
1075 if not tests:
1074 print "# Ran 0 tests, 0 skipped, 0 failed."
1076 print "# Ran 0 tests, 0 skipped, 0 failed."
1075 return
1077 return
1076
1078
1077 vlog("# Using TESTDIR", TESTDIR)
1079 vlog("# Using TESTDIR", TESTDIR)
1078 vlog("# Using HGTMP", HGTMP)
1080 vlog("# Using HGTMP", HGTMP)
1079 vlog("# Using PATH", os.environ["PATH"])
1081 vlog("# Using PATH", os.environ["PATH"])
1080 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1082 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1081
1083
1082 try:
1084 try:
1083 if len(tests) > 1 and options.jobs > 1:
1085 if len(tests) > 1 and options.jobs > 1:
1084 runchildren(options, tests)
1086 runchildren(options, tests)
1085 else:
1087 else:
1086 runtests(options, tests)
1088 runtests(options, tests)
1087 finally:
1089 finally:
1088 time.sleep(1)
1090 time.sleep(1)
1089 cleanup(options)
1091 cleanup(options)
1090
1092
1091 main()
1093 main()
General Comments 0
You need to be logged in to leave comments. Login now