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