##// END OF EJS Templates
tests: show skip reason instead of "irrelevant" with unified tests, too...
Thomas Arendsen Hein -
r12573:be4b0a39 default
parent child Browse files
Show More
@@ -1,1088 +1,1091 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 script.append('echo %s %s $?\n' % (salt, n + 1))
490 script.append('echo %s %s $?\n' % (salt, n + 1))
491
491
492 fd, name = tempfile.mkstemp(suffix='hg-tst')
492 fd, name = tempfile.mkstemp(suffix='hg-tst')
493
493
494 try:
494 try:
495 for l in script:
495 for l in script:
496 os.write(fd, l)
496 os.write(fd, l)
497 os.close(fd)
497 os.close(fd)
498
498
499 cmd = '/bin/sh "%s"' % name
499 cmd = '/bin/sh "%s"' % name
500 vlog("# Running", cmd)
500 vlog("# Running", cmd)
501 exitcode, output = run(cmd, options)
501 exitcode, output = run(cmd, options)
502 # do not merge output if skipped, return hghave message instead
503 if exitcode == SKIPPED_STATUS:
504 return exitcode, output
502 finally:
505 finally:
503 os.remove(name)
506 os.remove(name)
504
507
505 def rematch(el, l):
508 def rematch(el, l):
506 try:
509 try:
507 # ensure that the regex matches to the end of the string
510 # ensure that the regex matches to the end of the string
508 return re.match(el + r'\Z', l)
511 return re.match(el + r'\Z', l)
509 except re.error:
512 except re.error:
510 # el is an invalid regex
513 # el is an invalid regex
511 return False
514 return False
512
515
513 def globmatch(el, l):
516 def globmatch(el, l):
514 # The only supported special characters are * and ?. Escaping is
517 # The only supported special characters are * and ?. Escaping is
515 # supported.
518 # supported.
516 i, n = 0, len(el)
519 i, n = 0, len(el)
517 res = ''
520 res = ''
518 while i < n:
521 while i < n:
519 c = el[i]
522 c = el[i]
520 i += 1
523 i += 1
521 if c == '\\' and el[i] in '*?\\':
524 if c == '\\' and el[i] in '*?\\':
522 res += el[i - 1:i + 1]
525 res += el[i - 1:i + 1]
523 i += 1
526 i += 1
524 elif c == '*':
527 elif c == '*':
525 res += '.*'
528 res += '.*'
526 elif c == '?':
529 elif c == '?':
527 res += '.'
530 res += '.'
528 else:
531 else:
529 res += re.escape(c)
532 res += re.escape(c)
530 return rematch(res, l)
533 return rematch(res, l)
531
534
532 pos = -1
535 pos = -1
533 postout = []
536 postout = []
534 ret = 0
537 ret = 0
535 for n, l in enumerate(output):
538 for n, l in enumerate(output):
536 if l.startswith(salt):
539 if l.startswith(salt):
537 # add on last return code
540 # add on last return code
538 ret = int(l.split()[2])
541 ret = int(l.split()[2])
539 if ret != 0:
542 if ret != 0:
540 postout.append(" [%s]\n" % ret)
543 postout.append(" [%s]\n" % ret)
541 if pos in after:
544 if pos in after:
542 postout += after.pop(pos)
545 postout += after.pop(pos)
543 pos = int(l.split()[1])
546 pos = int(l.split()[1])
544 else:
547 else:
545 el = None
548 el = None
546 if pos in expected and expected[pos]:
549 if pos in expected and expected[pos]:
547 el = expected[pos].pop(0)
550 el = expected[pos].pop(0)
548
551
549 if el == l: # perfect match (fast)
552 if el == l: # perfect match (fast)
550 postout.append(" " + l)
553 postout.append(" " + l)
551 elif (el and
554 elif (el and
552 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
555 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
553 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l))):
556 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l))):
554 postout.append(" " + el) # fallback regex/glob match
557 postout.append(" " + el) # fallback regex/glob match
555 else:
558 else:
556 postout.append(" " + l) # let diff deal with it
559 postout.append(" " + l) # let diff deal with it
557
560
558 if pos in after:
561 if pos in after:
559 postout += after.pop(pos)
562 postout += after.pop(pos)
560
563
561 return exitcode, postout
564 return exitcode, postout
562
565
563 def run(cmd, options):
566 def run(cmd, options):
564 """Run command in a sub-process, capturing the output (stdout and stderr).
567 """Run command in a sub-process, capturing the output (stdout and stderr).
565 Return a tuple (exitcode, output). output is None in debug mode."""
568 Return a tuple (exitcode, output). output is None in debug mode."""
566 # TODO: Use subprocess.Popen if we're running on Python 2.4
569 # TODO: Use subprocess.Popen if we're running on Python 2.4
567 if options.debug:
570 if options.debug:
568 proc = subprocess.Popen(cmd, shell=True)
571 proc = subprocess.Popen(cmd, shell=True)
569 ret = proc.wait()
572 ret = proc.wait()
570 return (ret, None)
573 return (ret, None)
571
574
572 if os.name == 'nt' or sys.platform.startswith('java'):
575 if os.name == 'nt' or sys.platform.startswith('java'):
573 tochild, fromchild = os.popen4(cmd)
576 tochild, fromchild = os.popen4(cmd)
574 tochild.close()
577 tochild.close()
575 output = fromchild.read()
578 output = fromchild.read()
576 ret = fromchild.close()
579 ret = fromchild.close()
577 if ret == None:
580 if ret == None:
578 ret = 0
581 ret = 0
579 else:
582 else:
580 proc = Popen4(cmd)
583 proc = Popen4(cmd)
581 def cleanup():
584 def cleanup():
582 os.kill(proc.pid, signal.SIGTERM)
585 os.kill(proc.pid, signal.SIGTERM)
583 ret = proc.wait()
586 ret = proc.wait()
584 if ret == 0:
587 if ret == 0:
585 ret = signal.SIGTERM << 8
588 ret = signal.SIGTERM << 8
586 killdaemons()
589 killdaemons()
587 return ret
590 return ret
588
591
589 try:
592 try:
590 output = ''
593 output = ''
591 proc.tochild.close()
594 proc.tochild.close()
592 output = proc.fromchild.read()
595 output = proc.fromchild.read()
593 ret = proc.wait()
596 ret = proc.wait()
594 if os.WIFEXITED(ret):
597 if os.WIFEXITED(ret):
595 ret = os.WEXITSTATUS(ret)
598 ret = os.WEXITSTATUS(ret)
596 except Timeout:
599 except Timeout:
597 vlog('# Process %d timed out - killing it' % proc.pid)
600 vlog('# Process %d timed out - killing it' % proc.pid)
598 ret = cleanup()
601 ret = cleanup()
599 output += ("\n### Abort: timeout after %d seconds.\n"
602 output += ("\n### Abort: timeout after %d seconds.\n"
600 % options.timeout)
603 % options.timeout)
601 except KeyboardInterrupt:
604 except KeyboardInterrupt:
602 vlog('# Handling keyboard interrupt')
605 vlog('# Handling keyboard interrupt')
603 cleanup()
606 cleanup()
604 raise
607 raise
605
608
606 return ret, splitnewlines(output)
609 return ret, splitnewlines(output)
607
610
608 def runone(options, test, skips, fails):
611 def runone(options, test, skips, fails):
609 '''tristate output:
612 '''tristate output:
610 None -> skipped
613 None -> skipped
611 True -> passed
614 True -> passed
612 False -> failed'''
615 False -> failed'''
613
616
614 def skip(msg):
617 def skip(msg):
615 if not options.verbose:
618 if not options.verbose:
616 skips.append((test, msg))
619 skips.append((test, msg))
617 else:
620 else:
618 print "\nSkipping %s: %s" % (testpath, msg)
621 print "\nSkipping %s: %s" % (testpath, msg)
619 return None
622 return None
620
623
621 def fail(msg):
624 def fail(msg):
622 fails.append((test, msg))
625 fails.append((test, msg))
623 if not options.nodiff:
626 if not options.nodiff:
624 print "\nERROR: %s %s" % (testpath, msg)
627 print "\nERROR: %s %s" % (testpath, msg)
625 return None
628 return None
626
629
627 vlog("# Test", test)
630 vlog("# Test", test)
628
631
629 # create a fresh hgrc
632 # create a fresh hgrc
630 hgrc = open(HGRCPATH, 'w+')
633 hgrc = open(HGRCPATH, 'w+')
631 hgrc.write('[ui]\n')
634 hgrc.write('[ui]\n')
632 hgrc.write('slash = True\n')
635 hgrc.write('slash = True\n')
633 hgrc.write('[defaults]\n')
636 hgrc.write('[defaults]\n')
634 hgrc.write('backout = -d "0 0"\n')
637 hgrc.write('backout = -d "0 0"\n')
635 hgrc.write('commit = -d "0 0"\n')
638 hgrc.write('commit = -d "0 0"\n')
636 hgrc.write('tag = -d "0 0"\n')
639 hgrc.write('tag = -d "0 0"\n')
637 if options.inotify:
640 if options.inotify:
638 hgrc.write('[extensions]\n')
641 hgrc.write('[extensions]\n')
639 hgrc.write('inotify=\n')
642 hgrc.write('inotify=\n')
640 hgrc.write('[inotify]\n')
643 hgrc.write('[inotify]\n')
641 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
644 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
642 hgrc.write('appendpid=True\n')
645 hgrc.write('appendpid=True\n')
643 hgrc.close()
646 hgrc.close()
644
647
645 testpath = os.path.join(TESTDIR, test)
648 testpath = os.path.join(TESTDIR, test)
646 ref = os.path.join(TESTDIR, test+".out")
649 ref = os.path.join(TESTDIR, test+".out")
647 err = os.path.join(TESTDIR, test+".err")
650 err = os.path.join(TESTDIR, test+".err")
648 if os.path.exists(err):
651 if os.path.exists(err):
649 os.remove(err) # Remove any previous output files
652 os.remove(err) # Remove any previous output files
650 try:
653 try:
651 tf = open(testpath)
654 tf = open(testpath)
652 firstline = tf.readline().rstrip()
655 firstline = tf.readline().rstrip()
653 tf.close()
656 tf.close()
654 except:
657 except:
655 firstline = ''
658 firstline = ''
656 lctest = test.lower()
659 lctest = test.lower()
657
660
658 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
661 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
659 runner = pytest
662 runner = pytest
660 elif lctest.endswith('.bat'):
663 elif lctest.endswith('.bat'):
661 # do not run batch scripts on non-windows
664 # do not run batch scripts on non-windows
662 if os.name != 'nt':
665 if os.name != 'nt':
663 return skip("batch script")
666 return skip("batch script")
664 runner = battest
667 runner = battest
665 elif lctest.endswith('.t'):
668 elif lctest.endswith('.t'):
666 runner = tsttest
669 runner = tsttest
667 ref = testpath
670 ref = testpath
668 else:
671 else:
669 # do not run shell scripts on windows
672 # do not run shell scripts on windows
670 if os.name == 'nt':
673 if os.name == 'nt':
671 return skip("shell script")
674 return skip("shell script")
672 # do not try to run non-executable programs
675 # do not try to run non-executable programs
673 if not os.path.exists(testpath):
676 if not os.path.exists(testpath):
674 return fail("does not exist")
677 return fail("does not exist")
675 elif not os.access(testpath, os.X_OK):
678 elif not os.access(testpath, os.X_OK):
676 return skip("not executable")
679 return skip("not executable")
677 runner = shtest
680 runner = shtest
678
681
679 # Make a tmp subdirectory to work in
682 # Make a tmp subdirectory to work in
680 tmpd = os.path.join(HGTMP, test)
683 tmpd = os.path.join(HGTMP, test)
681 os.mkdir(tmpd)
684 os.mkdir(tmpd)
682 os.chdir(tmpd)
685 os.chdir(tmpd)
683
686
684 if options.timeout > 0:
687 if options.timeout > 0:
685 signal.alarm(options.timeout)
688 signal.alarm(options.timeout)
686
689
687 ret, out = runner(testpath, options)
690 ret, out = runner(testpath, options)
688 vlog("# Ret was:", ret)
691 vlog("# Ret was:", ret)
689
692
690 if options.timeout > 0:
693 if options.timeout > 0:
691 signal.alarm(0)
694 signal.alarm(0)
692
695
693 mark = '.'
696 mark = '.'
694
697
695 skipped = (ret == SKIPPED_STATUS)
698 skipped = (ret == SKIPPED_STATUS)
696
699
697 # If we're not in --debug mode and reference output file exists,
700 # If we're not in --debug mode and reference output file exists,
698 # check test output against it.
701 # check test output against it.
699 if options.debug:
702 if options.debug:
700 refout = None # to match out == None
703 refout = None # to match out == None
701 elif os.path.exists(ref):
704 elif os.path.exists(ref):
702 f = open(ref, "r")
705 f = open(ref, "r")
703 refout = splitnewlines(f.read())
706 refout = splitnewlines(f.read())
704 f.close()
707 f.close()
705 else:
708 else:
706 refout = []
709 refout = []
707
710
708 if (ret != 0 or out != refout) and not skipped and not options.debug:
711 if (ret != 0 or out != refout) and not skipped and not options.debug:
709 # Save errors to a file for diagnosis
712 # Save errors to a file for diagnosis
710 f = open(err, "wb")
713 f = open(err, "wb")
711 for line in out:
714 for line in out:
712 f.write(line)
715 f.write(line)
713 f.close()
716 f.close()
714
717
715 if skipped:
718 if skipped:
716 mark = 's'
719 mark = 's'
717 if out is None: # debug mode: nothing to parse
720 if out is None: # debug mode: nothing to parse
718 missing = ['unknown']
721 missing = ['unknown']
719 failed = None
722 failed = None
720 else:
723 else:
721 missing, failed = parsehghaveoutput(out)
724 missing, failed = parsehghaveoutput(out)
722 if not missing:
725 if not missing:
723 missing = ['irrelevant']
726 missing = ['irrelevant']
724 if failed:
727 if failed:
725 fail("hghave failed checking for %s" % failed[-1])
728 fail("hghave failed checking for %s" % failed[-1])
726 skipped = False
729 skipped = False
727 else:
730 else:
728 skip(missing[-1])
731 skip(missing[-1])
729 elif out != refout:
732 elif out != refout:
730 mark = '!'
733 mark = '!'
731 if ret:
734 if ret:
732 fail("output changed and returned error code %d" % ret)
735 fail("output changed and returned error code %d" % ret)
733 else:
736 else:
734 fail("output changed")
737 fail("output changed")
735 if not options.nodiff:
738 if not options.nodiff:
736 if options.view:
739 if options.view:
737 os.system("%s %s %s" % (options.view, ref, err))
740 os.system("%s %s %s" % (options.view, ref, err))
738 else:
741 else:
739 showdiff(refout, out, ref, err)
742 showdiff(refout, out, ref, err)
740 ret = 1
743 ret = 1
741 elif ret:
744 elif ret:
742 mark = '!'
745 mark = '!'
743 fail("returned error code %d" % ret)
746 fail("returned error code %d" % ret)
744
747
745 if not options.verbose:
748 if not options.verbose:
746 sys.stdout.write(mark)
749 sys.stdout.write(mark)
747 sys.stdout.flush()
750 sys.stdout.flush()
748
751
749 killdaemons()
752 killdaemons()
750
753
751 os.chdir(TESTDIR)
754 os.chdir(TESTDIR)
752 if not options.keep_tmpdir:
755 if not options.keep_tmpdir:
753 shutil.rmtree(tmpd, True)
756 shutil.rmtree(tmpd, True)
754 if skipped:
757 if skipped:
755 return None
758 return None
756 return ret == 0
759 return ret == 0
757
760
758 _hgpath = None
761 _hgpath = None
759
762
760 def _gethgpath():
763 def _gethgpath():
761 """Return the path to the mercurial package that is actually found by
764 """Return the path to the mercurial package that is actually found by
762 the current Python interpreter."""
765 the current Python interpreter."""
763 global _hgpath
766 global _hgpath
764 if _hgpath is not None:
767 if _hgpath is not None:
765 return _hgpath
768 return _hgpath
766
769
767 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
770 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
768 pipe = os.popen(cmd % PYTHON)
771 pipe = os.popen(cmd % PYTHON)
769 try:
772 try:
770 _hgpath = pipe.read().strip()
773 _hgpath = pipe.read().strip()
771 finally:
774 finally:
772 pipe.close()
775 pipe.close()
773 return _hgpath
776 return _hgpath
774
777
775 def _checkhglib(verb):
778 def _checkhglib(verb):
776 """Ensure that the 'mercurial' package imported by python is
779 """Ensure that the 'mercurial' package imported by python is
777 the one we expect it to be. If not, print a warning to stderr."""
780 the one we expect it to be. If not, print a warning to stderr."""
778 expecthg = os.path.join(PYTHONDIR, 'mercurial')
781 expecthg = os.path.join(PYTHONDIR, 'mercurial')
779 actualhg = _gethgpath()
782 actualhg = _gethgpath()
780 if actualhg != expecthg:
783 if actualhg != expecthg:
781 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
784 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
782 ' (expected %s)\n'
785 ' (expected %s)\n'
783 % (verb, actualhg, expecthg))
786 % (verb, actualhg, expecthg))
784
787
785 def runchildren(options, tests):
788 def runchildren(options, tests):
786 if INST:
789 if INST:
787 installhg(options)
790 installhg(options)
788 _checkhglib("Testing")
791 _checkhglib("Testing")
789
792
790 optcopy = dict(options.__dict__)
793 optcopy = dict(options.__dict__)
791 optcopy['jobs'] = 1
794 optcopy['jobs'] = 1
792 del optcopy['blacklist']
795 del optcopy['blacklist']
793 if optcopy['with_hg'] is None:
796 if optcopy['with_hg'] is None:
794 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
797 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
795 optcopy.pop('anycoverage', None)
798 optcopy.pop('anycoverage', None)
796
799
797 opts = []
800 opts = []
798 for opt, value in optcopy.iteritems():
801 for opt, value in optcopy.iteritems():
799 name = '--' + opt.replace('_', '-')
802 name = '--' + opt.replace('_', '-')
800 if value is True:
803 if value is True:
801 opts.append(name)
804 opts.append(name)
802 elif value is not None:
805 elif value is not None:
803 opts.append(name + '=' + str(value))
806 opts.append(name + '=' + str(value))
804
807
805 tests.reverse()
808 tests.reverse()
806 jobs = [[] for j in xrange(options.jobs)]
809 jobs = [[] for j in xrange(options.jobs)]
807 while tests:
810 while tests:
808 for job in jobs:
811 for job in jobs:
809 if not tests:
812 if not tests:
810 break
813 break
811 job.append(tests.pop())
814 job.append(tests.pop())
812 fps = {}
815 fps = {}
813
816
814 for j, job in enumerate(jobs):
817 for j, job in enumerate(jobs):
815 if not job:
818 if not job:
816 continue
819 continue
817 rfd, wfd = os.pipe()
820 rfd, wfd = os.pipe()
818 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
821 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
819 childtmp = os.path.join(HGTMP, 'child%d' % j)
822 childtmp = os.path.join(HGTMP, 'child%d' % j)
820 childopts += ['--tmpdir', childtmp]
823 childopts += ['--tmpdir', childtmp]
821 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
824 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
822 vlog(' '.join(cmdline))
825 vlog(' '.join(cmdline))
823 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
826 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
824 os.close(wfd)
827 os.close(wfd)
825 signal.signal(signal.SIGINT, signal.SIG_IGN)
828 signal.signal(signal.SIGINT, signal.SIG_IGN)
826 failures = 0
829 failures = 0
827 tested, skipped, failed = 0, 0, 0
830 tested, skipped, failed = 0, 0, 0
828 skips = []
831 skips = []
829 fails = []
832 fails = []
830 while fps:
833 while fps:
831 pid, status = os.wait()
834 pid, status = os.wait()
832 fp = fps.pop(pid)
835 fp = fps.pop(pid)
833 l = fp.read().splitlines()
836 l = fp.read().splitlines()
834 try:
837 try:
835 test, skip, fail = map(int, l[:3])
838 test, skip, fail = map(int, l[:3])
836 except ValueError:
839 except ValueError:
837 test, skip, fail = 0, 0, 0
840 test, skip, fail = 0, 0, 0
838 split = -fail or len(l)
841 split = -fail or len(l)
839 for s in l[3:split]:
842 for s in l[3:split]:
840 skips.append(s.split(" ", 1))
843 skips.append(s.split(" ", 1))
841 for s in l[split:]:
844 for s in l[split:]:
842 fails.append(s.split(" ", 1))
845 fails.append(s.split(" ", 1))
843 tested += test
846 tested += test
844 skipped += skip
847 skipped += skip
845 failed += fail
848 failed += fail
846 vlog('pid %d exited, status %d' % (pid, status))
849 vlog('pid %d exited, status %d' % (pid, status))
847 failures |= status
850 failures |= status
848 print
851 print
849 if not options.noskips:
852 if not options.noskips:
850 for s in skips:
853 for s in skips:
851 print "Skipped %s: %s" % (s[0], s[1])
854 print "Skipped %s: %s" % (s[0], s[1])
852 for s in fails:
855 for s in fails:
853 print "Failed %s: %s" % (s[0], s[1])
856 print "Failed %s: %s" % (s[0], s[1])
854
857
855 _checkhglib("Tested")
858 _checkhglib("Tested")
856 print "# Ran %d tests, %d skipped, %d failed." % (
859 print "# Ran %d tests, %d skipped, %d failed." % (
857 tested, skipped, failed)
860 tested, skipped, failed)
858
861
859 if options.anycoverage:
862 if options.anycoverage:
860 outputcoverage(options)
863 outputcoverage(options)
861 sys.exit(failures != 0)
864 sys.exit(failures != 0)
862
865
863 def runtests(options, tests):
866 def runtests(options, tests):
864 global DAEMON_PIDS, HGRCPATH
867 global DAEMON_PIDS, HGRCPATH
865 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
868 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
866 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
869 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
867
870
868 try:
871 try:
869 if INST:
872 if INST:
870 installhg(options)
873 installhg(options)
871 _checkhglib("Testing")
874 _checkhglib("Testing")
872
875
873 if options.timeout > 0:
876 if options.timeout > 0:
874 try:
877 try:
875 signal.signal(signal.SIGALRM, alarmed)
878 signal.signal(signal.SIGALRM, alarmed)
876 vlog('# Running each test with %d second timeout' %
879 vlog('# Running each test with %d second timeout' %
877 options.timeout)
880 options.timeout)
878 except AttributeError:
881 except AttributeError:
879 print 'WARNING: cannot run tests with timeouts'
882 print 'WARNING: cannot run tests with timeouts'
880 options.timeout = 0
883 options.timeout = 0
881
884
882 tested = 0
885 tested = 0
883 failed = 0
886 failed = 0
884 skipped = 0
887 skipped = 0
885
888
886 if options.restart:
889 if options.restart:
887 orig = list(tests)
890 orig = list(tests)
888 while tests:
891 while tests:
889 if os.path.exists(tests[0] + ".err"):
892 if os.path.exists(tests[0] + ".err"):
890 break
893 break
891 tests.pop(0)
894 tests.pop(0)
892 if not tests:
895 if not tests:
893 print "running all tests"
896 print "running all tests"
894 tests = orig
897 tests = orig
895
898
896 skips = []
899 skips = []
897 fails = []
900 fails = []
898
901
899 for test in tests:
902 for test in tests:
900 if options.blacklist:
903 if options.blacklist:
901 filename = options.blacklist.get(test)
904 filename = options.blacklist.get(test)
902 if filename is not None:
905 if filename is not None:
903 skips.append((test, "blacklisted (%s)" % filename))
906 skips.append((test, "blacklisted (%s)" % filename))
904 skipped += 1
907 skipped += 1
905 continue
908 continue
906
909
907 if options.retest and not os.path.exists(test + ".err"):
910 if options.retest and not os.path.exists(test + ".err"):
908 skipped += 1
911 skipped += 1
909 continue
912 continue
910
913
911 if options.keywords:
914 if options.keywords:
912 t = open(test).read().lower() + test.lower()
915 t = open(test).read().lower() + test.lower()
913 for k in options.keywords.lower().split():
916 for k in options.keywords.lower().split():
914 if k in t:
917 if k in t:
915 break
918 break
916 else:
919 else:
917 skipped += 1
920 skipped += 1
918 continue
921 continue
919
922
920 ret = runone(options, test, skips, fails)
923 ret = runone(options, test, skips, fails)
921 if ret is None:
924 if ret is None:
922 skipped += 1
925 skipped += 1
923 elif not ret:
926 elif not ret:
924 if options.interactive:
927 if options.interactive:
925 print "Accept this change? [n] ",
928 print "Accept this change? [n] ",
926 answer = sys.stdin.readline().strip()
929 answer = sys.stdin.readline().strip()
927 if answer.lower() in "y yes".split():
930 if answer.lower() in "y yes".split():
928 if test.endswith(".t"):
931 if test.endswith(".t"):
929 rename(test + ".err", test)
932 rename(test + ".err", test)
930 else:
933 else:
931 rename(test + ".err", test + ".out")
934 rename(test + ".err", test + ".out")
932 tested += 1
935 tested += 1
933 fails.pop()
936 fails.pop()
934 continue
937 continue
935 failed += 1
938 failed += 1
936 if options.first:
939 if options.first:
937 break
940 break
938 tested += 1
941 tested += 1
939
942
940 if options.child:
943 if options.child:
941 fp = os.fdopen(options.child, 'w')
944 fp = os.fdopen(options.child, 'w')
942 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
945 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
943 for s in skips:
946 for s in skips:
944 fp.write("%s %s\n" % s)
947 fp.write("%s %s\n" % s)
945 for s in fails:
948 for s in fails:
946 fp.write("%s %s\n" % s)
949 fp.write("%s %s\n" % s)
947 fp.close()
950 fp.close()
948 else:
951 else:
949 print
952 print
950 for s in skips:
953 for s in skips:
951 print "Skipped %s: %s" % s
954 print "Skipped %s: %s" % s
952 for s in fails:
955 for s in fails:
953 print "Failed %s: %s" % s
956 print "Failed %s: %s" % s
954 _checkhglib("Tested")
957 _checkhglib("Tested")
955 print "# Ran %d tests, %d skipped, %d failed." % (
958 print "# Ran %d tests, %d skipped, %d failed." % (
956 tested, skipped, failed)
959 tested, skipped, failed)
957
960
958 if options.anycoverage:
961 if options.anycoverage:
959 outputcoverage(options)
962 outputcoverage(options)
960 except KeyboardInterrupt:
963 except KeyboardInterrupt:
961 failed = True
964 failed = True
962 print "\ninterrupted!"
965 print "\ninterrupted!"
963
966
964 if failed:
967 if failed:
965 sys.exit(1)
968 sys.exit(1)
966
969
967 def main():
970 def main():
968 (options, args) = parseargs()
971 (options, args) = parseargs()
969 if not options.child:
972 if not options.child:
970 os.umask(022)
973 os.umask(022)
971
974
972 checktools()
975 checktools()
973
976
974 # Reset some environment variables to well-known values so that
977 # Reset some environment variables to well-known values so that
975 # the tests produce repeatable output.
978 # the tests produce repeatable output.
976 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
979 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
977 os.environ['TZ'] = 'GMT'
980 os.environ['TZ'] = 'GMT'
978 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
981 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
979 os.environ['CDPATH'] = ''
982 os.environ['CDPATH'] = ''
980 os.environ['COLUMNS'] = '80'
983 os.environ['COLUMNS'] = '80'
981 os.environ['GREP_OPTIONS'] = ''
984 os.environ['GREP_OPTIONS'] = ''
982 os.environ['http_proxy'] = ''
985 os.environ['http_proxy'] = ''
983
986
984 # unset env related to hooks
987 # unset env related to hooks
985 for k in os.environ.keys():
988 for k in os.environ.keys():
986 if k.startswith('HG_'):
989 if k.startswith('HG_'):
987 # can't remove on solaris
990 # can't remove on solaris
988 os.environ[k] = ''
991 os.environ[k] = ''
989 del os.environ[k]
992 del os.environ[k]
990
993
991 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
994 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
992 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
995 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
993 if options.tmpdir:
996 if options.tmpdir:
994 options.keep_tmpdir = True
997 options.keep_tmpdir = True
995 tmpdir = options.tmpdir
998 tmpdir = options.tmpdir
996 if os.path.exists(tmpdir):
999 if os.path.exists(tmpdir):
997 # Meaning of tmpdir has changed since 1.3: we used to create
1000 # Meaning of tmpdir has changed since 1.3: we used to create
998 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1001 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
999 # tmpdir already exists.
1002 # tmpdir already exists.
1000 sys.exit("error: temp dir %r already exists" % tmpdir)
1003 sys.exit("error: temp dir %r already exists" % tmpdir)
1001
1004
1002 # Automatically removing tmpdir sounds convenient, but could
1005 # Automatically removing tmpdir sounds convenient, but could
1003 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1006 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1004 # or "--tmpdir=$HOME".
1007 # or "--tmpdir=$HOME".
1005 #vlog("# Removing temp dir", tmpdir)
1008 #vlog("# Removing temp dir", tmpdir)
1006 #shutil.rmtree(tmpdir)
1009 #shutil.rmtree(tmpdir)
1007 os.makedirs(tmpdir)
1010 os.makedirs(tmpdir)
1008 else:
1011 else:
1009 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1012 tmpdir = tempfile.mkdtemp('', 'hgtests.')
1010 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1013 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1011 DAEMON_PIDS = None
1014 DAEMON_PIDS = None
1012 HGRCPATH = None
1015 HGRCPATH = None
1013
1016
1014 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1017 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1015 os.environ["HGMERGE"] = "internal:merge"
1018 os.environ["HGMERGE"] = "internal:merge"
1016 os.environ["HGUSER"] = "test"
1019 os.environ["HGUSER"] = "test"
1017 os.environ["HGENCODING"] = "ascii"
1020 os.environ["HGENCODING"] = "ascii"
1018 os.environ["HGENCODINGMODE"] = "strict"
1021 os.environ["HGENCODINGMODE"] = "strict"
1019 os.environ["HGPORT"] = str(options.port)
1022 os.environ["HGPORT"] = str(options.port)
1020 os.environ["HGPORT1"] = str(options.port + 1)
1023 os.environ["HGPORT1"] = str(options.port + 1)
1021 os.environ["HGPORT2"] = str(options.port + 2)
1024 os.environ["HGPORT2"] = str(options.port + 2)
1022
1025
1023 if options.with_hg:
1026 if options.with_hg:
1024 INST = None
1027 INST = None
1025 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1028 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1026
1029
1027 # This looks redundant with how Python initializes sys.path from
1030 # This looks redundant with how Python initializes sys.path from
1028 # the location of the script being executed. Needed because the
1031 # the location of the script being executed. Needed because the
1029 # "hg" specified by --with-hg is not the only Python script
1032 # "hg" specified by --with-hg is not the only Python script
1030 # executed in the test suite that needs to import 'mercurial'
1033 # executed in the test suite that needs to import 'mercurial'
1031 # ... which means it's not really redundant at all.
1034 # ... which means it's not really redundant at all.
1032 PYTHONDIR = BINDIR
1035 PYTHONDIR = BINDIR
1033 else:
1036 else:
1034 INST = os.path.join(HGTMP, "install")
1037 INST = os.path.join(HGTMP, "install")
1035 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1038 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1036 PYTHONDIR = os.path.join(INST, "lib", "python")
1039 PYTHONDIR = os.path.join(INST, "lib", "python")
1037
1040
1038 os.environ["BINDIR"] = BINDIR
1041 os.environ["BINDIR"] = BINDIR
1039 os.environ["PYTHON"] = PYTHON
1042 os.environ["PYTHON"] = PYTHON
1040
1043
1041 if not options.child:
1044 if not options.child:
1042 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1045 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1043 os.environ["PATH"] = os.pathsep.join(path)
1046 os.environ["PATH"] = os.pathsep.join(path)
1044
1047
1045 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1048 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1046 # can run .../tests/run-tests.py test-foo where test-foo
1049 # can run .../tests/run-tests.py test-foo where test-foo
1047 # adds an extension to HGRC
1050 # adds an extension to HGRC
1048 pypath = [PYTHONDIR, TESTDIR]
1051 pypath = [PYTHONDIR, TESTDIR]
1049 # We have to augment PYTHONPATH, rather than simply replacing
1052 # We have to augment PYTHONPATH, rather than simply replacing
1050 # it, in case external libraries are only available via current
1053 # it, in case external libraries are only available via current
1051 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1054 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1052 # are in /opt/subversion.)
1055 # are in /opt/subversion.)
1053 oldpypath = os.environ.get(IMPL_PATH)
1056 oldpypath = os.environ.get(IMPL_PATH)
1054 if oldpypath:
1057 if oldpypath:
1055 pypath.append(oldpypath)
1058 pypath.append(oldpypath)
1056 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1059 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1057
1060
1058 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1061 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1059
1062
1060 if len(args) == 0:
1063 if len(args) == 0:
1061 args = os.listdir(".")
1064 args = os.listdir(".")
1062 args.sort()
1065 args.sort()
1063
1066
1064 tests = []
1067 tests = []
1065 for test in args:
1068 for test in args:
1066 if (test.startswith("test-") and '~' not in test and
1069 if (test.startswith("test-") and '~' not in test and
1067 ('.' not in test or test.endswith('.py') or
1070 ('.' not in test or test.endswith('.py') or
1068 test.endswith('.bat') or test.endswith('.t'))):
1071 test.endswith('.bat') or test.endswith('.t'))):
1069 tests.append(test)
1072 tests.append(test)
1070 if not tests:
1073 if not tests:
1071 print "# Ran 0 tests, 0 skipped, 0 failed."
1074 print "# Ran 0 tests, 0 skipped, 0 failed."
1072 return
1075 return
1073
1076
1074 vlog("# Using TESTDIR", TESTDIR)
1077 vlog("# Using TESTDIR", TESTDIR)
1075 vlog("# Using HGTMP", HGTMP)
1078 vlog("# Using HGTMP", HGTMP)
1076 vlog("# Using PATH", os.environ["PATH"])
1079 vlog("# Using PATH", os.environ["PATH"])
1077 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1080 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1078
1081
1079 try:
1082 try:
1080 if len(tests) > 1 and options.jobs > 1:
1083 if len(tests) > 1 and options.jobs > 1:
1081 runchildren(options, tests)
1084 runchildren(options, tests)
1082 else:
1085 else:
1083 runtests(options, tests)
1086 runtests(options, tests)
1084 finally:
1087 finally:
1085 time.sleep(1)
1088 time.sleep(1)
1086 cleanup(options)
1089 cleanup(options)
1087
1090
1088 main()
1091 main()
General Comments 0
You need to be logged in to leave comments. Login now