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