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