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