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