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