##// END OF EJS Templates
run-tests: handle interrupted in Test.run()
Gregory Szorc -
r21320:e5eed8de default
parent child Browse files
Show More
@@ -1,1417 +1,1415 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, refpath):
554 def __init__(self, test, path, options, count, refpath):
555 self._test = test
555 self._path = path
556 self._path = path
556 self._options = options
557 self._options = options
557 self._count = count
558 self._count = count
558 self._daemonpids = []
559 self._daemonpids = []
559
560
560 # If we're not in --debug mode and reference output file exists,
561 # If we're not in --debug mode and reference output file exists,
561 # check test output against it.
562 # check test output against it.
562 if options.debug:
563 if options.debug:
563 self._refout = None # to match "out is None"
564 self._refout = None # to match "out is None"
564 elif os.path.exists(refpath):
565 elif os.path.exists(refpath):
565 f = open(refpath, 'r')
566 f = open(refpath, 'r')
566 self._refout = f.read().splitlines(True)
567 self._refout = f.read().splitlines(True)
567 f.close()
568 f.close()
568 else:
569 else:
569 self._refout = []
570 self._refout = []
570
571
571 self._threadtmp = os.path.join(HGTMP, 'child%d' % count)
572 self._threadtmp = os.path.join(HGTMP, 'child%d' % count)
572 os.mkdir(self._threadtmp)
573 os.mkdir(self._threadtmp)
573
574
574 def cleanup(self):
575 def cleanup(self):
575 for entry in self._daemonpids:
576 for entry in self._daemonpids:
576 killdaemons(entry)
577 killdaemons(entry)
577
578
578 if self._threadtmp and not self._options.keep_tmpdir:
579 if self._threadtmp and not self._options.keep_tmpdir:
579 shutil.rmtree(self._threadtmp, True)
580 shutil.rmtree(self._threadtmp, True)
580
581
581 def run(self, result):
582 def run(self, result):
582 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
583 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
583 os.mkdir(testtmp)
584 os.mkdir(testtmp)
584 replacements, port = self._getreplacements(testtmp)
585 replacements, port = self._getreplacements(testtmp)
585 env = self._getenv(testtmp, port)
586 env = self._getenv(testtmp, port)
586 self._daemonpids.append(env['DAEMON_PIDS'])
587 self._daemonpids.append(env['DAEMON_PIDS'])
587 createhgrc(env['HGRCPATH'], self._options)
588 createhgrc(env['HGRCPATH'], self._options)
588
589
589 starttime = time.time()
590 starttime = time.time()
590
591
591 def updateduration():
592 def updateduration():
592 result.duration = time.time() - starttime
593 result.duration = time.time() - starttime
593
594
594 try:
595 try:
595 ret, out = self._run(testtmp, replacements, env)
596 ret, out = self._run(testtmp, replacements, env)
596 updateduration()
597 updateduration()
597 result.ret = ret
598 result.ret = ret
598 result.out = out
599 result.out = out
599 except KeyboardInterrupt:
600 except KeyboardInterrupt:
600 updateduration()
601 updateduration()
601 result.interrupted = True
602 log('INTERRUPTED: %s (after %d seconds)' % (self._test,
603 result.duration))
604 raise
602 except Exception, e:
605 except Exception, e:
603 updateduration()
606 updateduration()
604 result.exception = e
607 result.exception = e
605
608
606 killdaemons(env['DAEMON_PIDS'])
609 killdaemons(env['DAEMON_PIDS'])
607
610
608 result.refout = self._refout
611 result.refout = self._refout
609
612
610 if not self._options.keep_tmpdir:
613 if not self._options.keep_tmpdir:
611 shutil.rmtree(testtmp)
614 shutil.rmtree(testtmp)
612
615
613 def _run(self, testtmp, replacements, env):
616 def _run(self, testtmp, replacements, env):
614 raise NotImplemented('Subclasses must implement Test.run()')
617 raise NotImplemented('Subclasses must implement Test.run()')
615
618
616 def _getreplacements(self, testtmp):
619 def _getreplacements(self, testtmp):
617 port = self._options.port + self._count * 3
620 port = self._options.port + self._count * 3
618 r = [
621 r = [
619 (r':%s\b' % port, ':$HGPORT'),
622 (r':%s\b' % port, ':$HGPORT'),
620 (r':%s\b' % (port + 1), ':$HGPORT1'),
623 (r':%s\b' % (port + 1), ':$HGPORT1'),
621 (r':%s\b' % (port + 2), ':$HGPORT2'),
624 (r':%s\b' % (port + 2), ':$HGPORT2'),
622 ]
625 ]
623
626
624 if os.name == 'nt':
627 if os.name == 'nt':
625 r.append(
628 r.append(
626 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
629 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
627 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
630 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
628 for c in testtmp), '$TESTTMP'))
631 for c in testtmp), '$TESTTMP'))
629 else:
632 else:
630 r.append((re.escape(testtmp), '$TESTTMP'))
633 r.append((re.escape(testtmp), '$TESTTMP'))
631
634
632 return r, port
635 return r, port
633
636
634 def _getenv(self, testtmp, port):
637 def _getenv(self, testtmp, port):
635 env = os.environ.copy()
638 env = os.environ.copy()
636 env['TESTTMP'] = testtmp
639 env['TESTTMP'] = testtmp
637 env['HOME'] = testtmp
640 env['HOME'] = testtmp
638 env["HGPORT"] = str(port)
641 env["HGPORT"] = str(port)
639 env["HGPORT1"] = str(port + 1)
642 env["HGPORT1"] = str(port + 1)
640 env["HGPORT2"] = str(port + 2)
643 env["HGPORT2"] = str(port + 2)
641 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
644 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
642 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
645 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
643 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
646 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
644 env["HGMERGE"] = "internal:merge"
647 env["HGMERGE"] = "internal:merge"
645 env["HGUSER"] = "test"
648 env["HGUSER"] = "test"
646 env["HGENCODING"] = "ascii"
649 env["HGENCODING"] = "ascii"
647 env["HGENCODINGMODE"] = "strict"
650 env["HGENCODINGMODE"] = "strict"
648
651
649 # Reset some environment variables to well-known values so that
652 # Reset some environment variables to well-known values so that
650 # the tests produce repeatable output.
653 # the tests produce repeatable output.
651 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
654 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
652 env['TZ'] = 'GMT'
655 env['TZ'] = 'GMT'
653 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
656 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
654 env['COLUMNS'] = '80'
657 env['COLUMNS'] = '80'
655 env['TERM'] = 'xterm'
658 env['TERM'] = 'xterm'
656
659
657 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
660 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
658 'NO_PROXY').split():
661 'NO_PROXY').split():
659 if k in env:
662 if k in env:
660 del env[k]
663 del env[k]
661
664
662 # unset env related to hooks
665 # unset env related to hooks
663 for k in env.keys():
666 for k in env.keys():
664 if k.startswith('HG_'):
667 if k.startswith('HG_'):
665 del env[k]
668 del env[k]
666
669
667 return env
670 return env
668
671
669 class TestResult(object):
672 class TestResult(object):
670 """Holds the result of a test execution."""
673 """Holds the result of a test execution."""
671
674
672 def __init__(self):
675 def __init__(self):
673 self.ret = None
676 self.ret = None
674 self.out = None
677 self.out = None
675 self.duration = None
678 self.duration = None
676 self.interrupted = False
677 self.exception = None
679 self.exception = None
678 self.refout = None
680 self.refout = None
679
681
680 @property
682 @property
681 def skipped(self):
683 def skipped(self):
682 """Whether the test was skipped."""
684 """Whether the test was skipped."""
683 return self.ret == SKIPPED_STATUS
685 return self.ret == SKIPPED_STATUS
684
686
685 class PythonTest(Test):
687 class PythonTest(Test):
686 """A Python-based test."""
688 """A Python-based test."""
687 def _run(self, testtmp, replacements, env):
689 def _run(self, testtmp, replacements, env):
688 py3kswitch = self._options.py3k_warnings and ' -3' or ''
690 py3kswitch = self._options.py3k_warnings and ' -3' or ''
689 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
691 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
690 vlog("# Running", cmd)
692 vlog("# Running", cmd)
691 if os.name == 'nt':
693 if os.name == 'nt':
692 replacements.append((r'\r\n', '\n'))
694 replacements.append((r'\r\n', '\n'))
693 return run(cmd, testtmp, self._options, replacements, env)
695 return run(cmd, testtmp, self._options, replacements, env)
694
696
695
697
696 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
698 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
697 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
699 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
698 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
700 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
699 escapemap.update({'\\': '\\\\', '\r': r'\r'})
701 escapemap.update({'\\': '\\\\', '\r': r'\r'})
700 def escapef(m):
702 def escapef(m):
701 return escapemap[m.group(0)]
703 return escapemap[m.group(0)]
702 def stringescape(s):
704 def stringescape(s):
703 return escapesub(escapef, s)
705 return escapesub(escapef, s)
704
706
705 class TTest(Test):
707 class TTest(Test):
706 """A "t test" is a test backed by a .t file."""
708 """A "t test" is a test backed by a .t file."""
707
709
708 def _run(self, testtmp, replacements, env):
710 def _run(self, testtmp, replacements, env):
709 f = open(self._path)
711 f = open(self._path)
710 lines = f.readlines()
712 lines = f.readlines()
711 f.close()
713 f.close()
712
714
713 salt, script, after, expected = self._parsetest(lines, testtmp)
715 salt, script, after, expected = self._parsetest(lines, testtmp)
714
716
715 # Write out the generated script.
717 # Write out the generated script.
716 fname = '%s.sh' % testtmp
718 fname = '%s.sh' % testtmp
717 f = open(fname, 'w')
719 f = open(fname, 'w')
718 for l in script:
720 for l in script:
719 f.write(l)
721 f.write(l)
720 f.close()
722 f.close()
721
723
722 cmd = '%s "%s"' % (self._options.shell, fname)
724 cmd = '%s "%s"' % (self._options.shell, fname)
723 vlog("# Running", cmd)
725 vlog("# Running", cmd)
724
726
725 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
727 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
726 # Do not merge output if skipped. Return hghave message instead.
728 # Do not merge output if skipped. Return hghave message instead.
727 # Similarly, with --debug, output is None.
729 # Similarly, with --debug, output is None.
728 if exitcode == SKIPPED_STATUS or output is None:
730 if exitcode == SKIPPED_STATUS or output is None:
729 return exitcode, output
731 return exitcode, output
730
732
731 return self._processoutput(exitcode, output, salt, after, expected)
733 return self._processoutput(exitcode, output, salt, after, expected)
732
734
733 def _hghave(self, reqs, testtmp):
735 def _hghave(self, reqs, testtmp):
734 # TODO do something smarter when all other uses of hghave are gone.
736 # TODO do something smarter when all other uses of hghave are gone.
735 tdir = TESTDIR.replace('\\', '/')
737 tdir = TESTDIR.replace('\\', '/')
736 proc = Popen4('%s -c "%s/hghave %s"' %
738 proc = Popen4('%s -c "%s/hghave %s"' %
737 (self._options.shell, tdir, ' '.join(reqs)),
739 (self._options.shell, tdir, ' '.join(reqs)),
738 testtmp, 0)
740 testtmp, 0)
739 stdout, stderr = proc.communicate()
741 stdout, stderr = proc.communicate()
740 ret = proc.wait()
742 ret = proc.wait()
741 if wifexited(ret):
743 if wifexited(ret):
742 ret = os.WEXITSTATUS(ret)
744 ret = os.WEXITSTATUS(ret)
743 if ret == 2:
745 if ret == 2:
744 print stdout
746 print stdout
745 sys.exit(1)
747 sys.exit(1)
746
748
747 return ret == 0
749 return ret == 0
748
750
749 def _parsetest(self, lines, testtmp):
751 def _parsetest(self, lines, testtmp):
750 # We generate a shell script which outputs unique markers to line
752 # We generate a shell script which outputs unique markers to line
751 # up script results with our source. These markers include input
753 # up script results with our source. These markers include input
752 # line number and the last return code.
754 # line number and the last return code.
753 salt = "SALT" + str(time.time())
755 salt = "SALT" + str(time.time())
754 def addsalt(line, inpython):
756 def addsalt(line, inpython):
755 if inpython:
757 if inpython:
756 script.append('%s %d 0\n' % (salt, line))
758 script.append('%s %d 0\n' % (salt, line))
757 else:
759 else:
758 script.append('echo %s %s $?\n' % (salt, line))
760 script.append('echo %s %s $?\n' % (salt, line))
759
761
760 script = []
762 script = []
761
763
762 # After we run the shell script, we re-unify the script output
764 # After we run the shell script, we re-unify the script output
763 # with non-active parts of the source, with synchronization by our
765 # with non-active parts of the source, with synchronization by our
764 # SALT line number markers. The after table contains the non-active
766 # SALT line number markers. The after table contains the non-active
765 # components, ordered by line number.
767 # components, ordered by line number.
766 after = {}
768 after = {}
767
769
768 # Expected shell script output.
770 # Expected shell script output.
769 expected = {}
771 expected = {}
770
772
771 pos = prepos = -1
773 pos = prepos = -1
772
774
773 # True or False when in a true or false conditional section
775 # True or False when in a true or false conditional section
774 skipping = None
776 skipping = None
775
777
776 # We keep track of whether or not we're in a Python block so we
778 # We keep track of whether or not we're in a Python block so we
777 # can generate the surrounding doctest magic.
779 # can generate the surrounding doctest magic.
778 inpython = False
780 inpython = False
779
781
780 if self._options.debug:
782 if self._options.debug:
781 script.append('set -x\n')
783 script.append('set -x\n')
782 if os.getenv('MSYSTEM'):
784 if os.getenv('MSYSTEM'):
783 script.append('alias pwd="pwd -W"\n')
785 script.append('alias pwd="pwd -W"\n')
784
786
785 for n, l in enumerate(lines):
787 for n, l in enumerate(lines):
786 if not l.endswith('\n'):
788 if not l.endswith('\n'):
787 l += '\n'
789 l += '\n'
788 if l.startswith('#if'):
790 if l.startswith('#if'):
789 lsplit = l.split()
791 lsplit = l.split()
790 if len(lsplit) < 2 or lsplit[0] != '#if':
792 if len(lsplit) < 2 or lsplit[0] != '#if':
791 after.setdefault(pos, []).append(' !!! invalid #if\n')
793 after.setdefault(pos, []).append(' !!! invalid #if\n')
792 if skipping is not None:
794 if skipping is not None:
793 after.setdefault(pos, []).append(' !!! nested #if\n')
795 after.setdefault(pos, []).append(' !!! nested #if\n')
794 skipping = not self._hghave(lsplit[1:], testtmp)
796 skipping = not self._hghave(lsplit[1:], testtmp)
795 after.setdefault(pos, []).append(l)
797 after.setdefault(pos, []).append(l)
796 elif l.startswith('#else'):
798 elif l.startswith('#else'):
797 if skipping is None:
799 if skipping is None:
798 after.setdefault(pos, []).append(' !!! missing #if\n')
800 after.setdefault(pos, []).append(' !!! missing #if\n')
799 skipping = not skipping
801 skipping = not skipping
800 after.setdefault(pos, []).append(l)
802 after.setdefault(pos, []).append(l)
801 elif l.startswith('#endif'):
803 elif l.startswith('#endif'):
802 if skipping is None:
804 if skipping is None:
803 after.setdefault(pos, []).append(' !!! missing #if\n')
805 after.setdefault(pos, []).append(' !!! missing #if\n')
804 skipping = None
806 skipping = None
805 after.setdefault(pos, []).append(l)
807 after.setdefault(pos, []).append(l)
806 elif skipping:
808 elif skipping:
807 after.setdefault(pos, []).append(l)
809 after.setdefault(pos, []).append(l)
808 elif l.startswith(' >>> '): # python inlines
810 elif l.startswith(' >>> '): # python inlines
809 after.setdefault(pos, []).append(l)
811 after.setdefault(pos, []).append(l)
810 prepos = pos
812 prepos = pos
811 pos = n
813 pos = n
812 if not inpython:
814 if not inpython:
813 # We've just entered a Python block. Add the header.
815 # We've just entered a Python block. Add the header.
814 inpython = True
816 inpython = True
815 addsalt(prepos, False) # Make sure we report the exit code.
817 addsalt(prepos, False) # Make sure we report the exit code.
816 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
818 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
817 addsalt(n, True)
819 addsalt(n, True)
818 script.append(l[2:])
820 script.append(l[2:])
819 elif l.startswith(' ... '): # python inlines
821 elif l.startswith(' ... '): # python inlines
820 after.setdefault(prepos, []).append(l)
822 after.setdefault(prepos, []).append(l)
821 script.append(l[2:])
823 script.append(l[2:])
822 elif l.startswith(' $ '): # commands
824 elif l.startswith(' $ '): # commands
823 if inpython:
825 if inpython:
824 script.append('EOF\n')
826 script.append('EOF\n')
825 inpython = False
827 inpython = False
826 after.setdefault(pos, []).append(l)
828 after.setdefault(pos, []).append(l)
827 prepos = pos
829 prepos = pos
828 pos = n
830 pos = n
829 addsalt(n, False)
831 addsalt(n, False)
830 cmd = l[4:].split()
832 cmd = l[4:].split()
831 if len(cmd) == 2 and cmd[0] == 'cd':
833 if len(cmd) == 2 and cmd[0] == 'cd':
832 l = ' $ cd %s || exit 1\n' % cmd[1]
834 l = ' $ cd %s || exit 1\n' % cmd[1]
833 script.append(l[4:])
835 script.append(l[4:])
834 elif l.startswith(' > '): # continuations
836 elif l.startswith(' > '): # continuations
835 after.setdefault(prepos, []).append(l)
837 after.setdefault(prepos, []).append(l)
836 script.append(l[4:])
838 script.append(l[4:])
837 elif l.startswith(' '): # results
839 elif l.startswith(' '): # results
838 # Queue up a list of expected results.
840 # Queue up a list of expected results.
839 expected.setdefault(pos, []).append(l[2:])
841 expected.setdefault(pos, []).append(l[2:])
840 else:
842 else:
841 if inpython:
843 if inpython:
842 script.append('EOF\n')
844 script.append('EOF\n')
843 inpython = False
845 inpython = False
844 # Non-command/result. Queue up for merged output.
846 # Non-command/result. Queue up for merged output.
845 after.setdefault(pos, []).append(l)
847 after.setdefault(pos, []).append(l)
846
848
847 if inpython:
849 if inpython:
848 script.append('EOF\n')
850 script.append('EOF\n')
849 if skipping is not None:
851 if skipping is not None:
850 after.setdefault(pos, []).append(' !!! missing #endif\n')
852 after.setdefault(pos, []).append(' !!! missing #endif\n')
851 addsalt(n + 1, False)
853 addsalt(n + 1, False)
852
854
853 return salt, script, after, expected
855 return salt, script, after, expected
854
856
855 def _processoutput(self, exitcode, output, salt, after, expected):
857 def _processoutput(self, exitcode, output, salt, after, expected):
856 # Merge the script output back into a unified test.
858 # Merge the script output back into a unified test.
857 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
859 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
858 if exitcode != 0:
860 if exitcode != 0:
859 warnonly = 3
861 warnonly = 3
860
862
861 pos = -1
863 pos = -1
862 postout = []
864 postout = []
863 for l in output:
865 for l in output:
864 lout, lcmd = l, None
866 lout, lcmd = l, None
865 if salt in l:
867 if salt in l:
866 lout, lcmd = l.split(salt, 1)
868 lout, lcmd = l.split(salt, 1)
867
869
868 if lout:
870 if lout:
869 if not lout.endswith('\n'):
871 if not lout.endswith('\n'):
870 lout += ' (no-eol)\n'
872 lout += ' (no-eol)\n'
871
873
872 # Find the expected output at the current position.
874 # Find the expected output at the current position.
873 el = None
875 el = None
874 if expected.get(pos, None):
876 if expected.get(pos, None):
875 el = expected[pos].pop(0)
877 el = expected[pos].pop(0)
876
878
877 r = TTest.linematch(el, lout)
879 r = TTest.linematch(el, lout)
878 if isinstance(r, str):
880 if isinstance(r, str):
879 if r == '+glob':
881 if r == '+glob':
880 lout = el[:-1] + ' (glob)\n'
882 lout = el[:-1] + ' (glob)\n'
881 r = '' # Warn only this line.
883 r = '' # Warn only this line.
882 elif r == '-glob':
884 elif r == '-glob':
883 lout = ''.join(el.rsplit(' (glob)', 1))
885 lout = ''.join(el.rsplit(' (glob)', 1))
884 r = '' # Warn only this line.
886 r = '' # Warn only this line.
885 else:
887 else:
886 log('\ninfo, unknown linematch result: %r\n' % r)
888 log('\ninfo, unknown linematch result: %r\n' % r)
887 r = False
889 r = False
888 if r:
890 if r:
889 postout.append(' ' + el)
891 postout.append(' ' + el)
890 else:
892 else:
891 if needescape(lout):
893 if needescape(lout):
892 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
894 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
893 postout.append(' ' + lout) # Let diff deal with it.
895 postout.append(' ' + lout) # Let diff deal with it.
894 if r != '': # If line failed.
896 if r != '': # If line failed.
895 warnonly = 3 # for sure not
897 warnonly = 3 # for sure not
896 elif warnonly == 1: # Is "not yet" and line is warn only.
898 elif warnonly == 1: # Is "not yet" and line is warn only.
897 warnonly = 2 # Yes do warn.
899 warnonly = 2 # Yes do warn.
898
900
899 if lcmd:
901 if lcmd:
900 # Add on last return code.
902 # Add on last return code.
901 ret = int(lcmd.split()[1])
903 ret = int(lcmd.split()[1])
902 if ret != 0:
904 if ret != 0:
903 postout.append(' [%s]\n' % ret)
905 postout.append(' [%s]\n' % ret)
904 if pos in after:
906 if pos in after:
905 # Merge in non-active test bits.
907 # Merge in non-active test bits.
906 postout += after.pop(pos)
908 postout += after.pop(pos)
907 pos = int(lcmd.split()[0])
909 pos = int(lcmd.split()[0])
908
910
909 if pos in after:
911 if pos in after:
910 postout += after.pop(pos)
912 postout += after.pop(pos)
911
913
912 if warnonly == 2:
914 if warnonly == 2:
913 exitcode = False # Set exitcode to warned.
915 exitcode = False # Set exitcode to warned.
914
916
915 return exitcode, postout
917 return exitcode, postout
916
918
917 @staticmethod
919 @staticmethod
918 def rematch(el, l):
920 def rematch(el, l):
919 try:
921 try:
920 # use \Z to ensure that the regex matches to the end of the string
922 # use \Z to ensure that the regex matches to the end of the string
921 if os.name == 'nt':
923 if os.name == 'nt':
922 return re.match(el + r'\r?\n\Z', l)
924 return re.match(el + r'\r?\n\Z', l)
923 return re.match(el + r'\n\Z', l)
925 return re.match(el + r'\n\Z', l)
924 except re.error:
926 except re.error:
925 # el is an invalid regex
927 # el is an invalid regex
926 return False
928 return False
927
929
928 @staticmethod
930 @staticmethod
929 def globmatch(el, l):
931 def globmatch(el, l):
930 # The only supported special characters are * and ? plus / which also
932 # The only supported special characters are * and ? plus / which also
931 # matches \ on windows. Escaping of these characters is supported.
933 # matches \ on windows. Escaping of these characters is supported.
932 if el + '\n' == l:
934 if el + '\n' == l:
933 if os.altsep:
935 if os.altsep:
934 # matching on "/" is not needed for this line
936 # matching on "/" is not needed for this line
935 return '-glob'
937 return '-glob'
936 return True
938 return True
937 i, n = 0, len(el)
939 i, n = 0, len(el)
938 res = ''
940 res = ''
939 while i < n:
941 while i < n:
940 c = el[i]
942 c = el[i]
941 i += 1
943 i += 1
942 if c == '\\' and el[i] in '*?\\/':
944 if c == '\\' and el[i] in '*?\\/':
943 res += el[i - 1:i + 1]
945 res += el[i - 1:i + 1]
944 i += 1
946 i += 1
945 elif c == '*':
947 elif c == '*':
946 res += '.*'
948 res += '.*'
947 elif c == '?':
949 elif c == '?':
948 res += '.'
950 res += '.'
949 elif c == '/' and os.altsep:
951 elif c == '/' and os.altsep:
950 res += '[/\\\\]'
952 res += '[/\\\\]'
951 else:
953 else:
952 res += re.escape(c)
954 res += re.escape(c)
953 return TTest.rematch(res, l)
955 return TTest.rematch(res, l)
954
956
955 @staticmethod
957 @staticmethod
956 def linematch(el, l):
958 def linematch(el, l):
957 if el == l: # perfect match (fast)
959 if el == l: # perfect match (fast)
958 return True
960 return True
959 if el:
961 if el:
960 if el.endswith(" (esc)\n"):
962 if el.endswith(" (esc)\n"):
961 el = el[:-7].decode('string-escape') + '\n'
963 el = el[:-7].decode('string-escape') + '\n'
962 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
964 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
963 return True
965 return True
964 if el.endswith(" (re)\n"):
966 if el.endswith(" (re)\n"):
965 return TTest.rematch(el[:-6], l)
967 return TTest.rematch(el[:-6], l)
966 if el.endswith(" (glob)\n"):
968 if el.endswith(" (glob)\n"):
967 return TTest.globmatch(el[:-8], l)
969 return TTest.globmatch(el[:-8], l)
968 if os.altsep and l.replace('\\', '/') == el:
970 if os.altsep and l.replace('\\', '/') == el:
969 return '+glob'
971 return '+glob'
970 return False
972 return False
971
973
972 wifexited = getattr(os, "WIFEXITED", lambda x: False)
974 wifexited = getattr(os, "WIFEXITED", lambda x: False)
973 def run(cmd, wd, options, replacements, env):
975 def run(cmd, wd, options, replacements, env):
974 """Run command in a sub-process, capturing the output (stdout and stderr).
976 """Run command in a sub-process, capturing the output (stdout and stderr).
975 Return a tuple (exitcode, output). output is None in debug mode."""
977 Return a tuple (exitcode, output). output is None in debug mode."""
976 # TODO: Use subprocess.Popen if we're running on Python 2.4
978 # TODO: Use subprocess.Popen if we're running on Python 2.4
977 if options.debug:
979 if options.debug:
978 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
980 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
979 ret = proc.wait()
981 ret = proc.wait()
980 return (ret, None)
982 return (ret, None)
981
983
982 proc = Popen4(cmd, wd, options.timeout, env)
984 proc = Popen4(cmd, wd, options.timeout, env)
983 def cleanup():
985 def cleanup():
984 terminate(proc)
986 terminate(proc)
985 ret = proc.wait()
987 ret = proc.wait()
986 if ret == 0:
988 if ret == 0:
987 ret = signal.SIGTERM << 8
989 ret = signal.SIGTERM << 8
988 killdaemons(env['DAEMON_PIDS'])
990 killdaemons(env['DAEMON_PIDS'])
989 return ret
991 return ret
990
992
991 output = ''
993 output = ''
992 proc.tochild.close()
994 proc.tochild.close()
993
995
994 try:
996 try:
995 output = proc.fromchild.read()
997 output = proc.fromchild.read()
996 except KeyboardInterrupt:
998 except KeyboardInterrupt:
997 vlog('# Handling keyboard interrupt')
999 vlog('# Handling keyboard interrupt')
998 cleanup()
1000 cleanup()
999 raise
1001 raise
1000
1002
1001 ret = proc.wait()
1003 ret = proc.wait()
1002 if wifexited(ret):
1004 if wifexited(ret):
1003 ret = os.WEXITSTATUS(ret)
1005 ret = os.WEXITSTATUS(ret)
1004
1006
1005 if proc.timeout:
1007 if proc.timeout:
1006 ret = 'timeout'
1008 ret = 'timeout'
1007
1009
1008 if ret:
1010 if ret:
1009 killdaemons(env['DAEMON_PIDS'])
1011 killdaemons(env['DAEMON_PIDS'])
1010
1012
1011 if abort:
1013 if abort:
1012 raise KeyboardInterrupt()
1014 raise KeyboardInterrupt()
1013
1015
1014 for s, r in replacements:
1016 for s, r in replacements:
1015 output = re.sub(s, r, output)
1017 output = re.sub(s, r, output)
1016 return ret, output.splitlines(True)
1018 return ret, output.splitlines(True)
1017
1019
1018 def runone(options, test, count):
1020 def runone(options, test, count):
1019 '''returns a result element: (code, test, msg)'''
1021 '''returns a result element: (code, test, msg)'''
1020
1022
1021 def skip(msg):
1023 def skip(msg):
1022 if options.verbose:
1024 if options.verbose:
1023 log("\nSkipping %s: %s" % (testpath, msg))
1025 log("\nSkipping %s: %s" % (testpath, msg))
1024 return 's', test, msg
1026 return 's', test, msg
1025
1027
1026 def fail(msg, ret):
1028 def fail(msg, ret):
1027 warned = ret is False
1029 warned = ret is False
1028 if not options.nodiff:
1030 if not options.nodiff:
1029 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
1031 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
1030 if (not ret and options.interactive
1032 if (not ret and options.interactive
1031 and os.path.exists(testpath + ".err")):
1033 and os.path.exists(testpath + ".err")):
1032 iolock.acquire()
1034 iolock.acquire()
1033 print "Accept this change? [n] ",
1035 print "Accept this change? [n] ",
1034 answer = sys.stdin.readline().strip()
1036 answer = sys.stdin.readline().strip()
1035 iolock.release()
1037 iolock.release()
1036 if answer.lower() in "y yes".split():
1038 if answer.lower() in "y yes".split():
1037 if test.endswith(".t"):
1039 if test.endswith(".t"):
1038 rename(testpath + ".err", testpath)
1040 rename(testpath + ".err", testpath)
1039 else:
1041 else:
1040 rename(testpath + ".err", testpath + ".out")
1042 rename(testpath + ".err", testpath + ".out")
1041 return '.', test, ''
1043 return '.', test, ''
1042 return warned and '~' or '!', test, msg
1044 return warned and '~' or '!', test, msg
1043
1045
1044 def success():
1046 def success():
1045 return '.', test, ''
1047 return '.', test, ''
1046
1048
1047 def ignore(msg):
1049 def ignore(msg):
1048 return 'i', test, msg
1050 return 'i', test, msg
1049
1051
1050 def describe(ret):
1052 def describe(ret):
1051 if ret < 0:
1053 if ret < 0:
1052 return 'killed by signal %d' % -ret
1054 return 'killed by signal %d' % -ret
1053 return 'returned error code %d' % ret
1055 return 'returned error code %d' % ret
1054
1056
1055 testpath = os.path.join(TESTDIR, test)
1057 testpath = os.path.join(TESTDIR, test)
1056 err = os.path.join(TESTDIR, test + ".err")
1058 err = os.path.join(TESTDIR, test + ".err")
1057 lctest = test.lower()
1059 lctest = test.lower()
1058
1060
1059 if not os.path.exists(testpath):
1061 if not os.path.exists(testpath):
1060 return skip("doesn't exist")
1062 return skip("doesn't exist")
1061
1063
1062 if not (options.whitelisted and test in options.whitelisted):
1064 if not (options.whitelisted and test in options.whitelisted):
1063 if options.blacklist and test in options.blacklist:
1065 if options.blacklist and test in options.blacklist:
1064 return skip("blacklisted")
1066 return skip("blacklisted")
1065
1067
1066 if options.retest and not os.path.exists(test + ".err"):
1068 if options.retest and not os.path.exists(test + ".err"):
1067 return ignore("not retesting")
1069 return ignore("not retesting")
1068
1070
1069 if options.keywords:
1071 if options.keywords:
1070 fp = open(test)
1072 fp = open(test)
1071 t = fp.read().lower() + test.lower()
1073 t = fp.read().lower() + test.lower()
1072 fp.close()
1074 fp.close()
1073 for k in options.keywords.lower().split():
1075 for k in options.keywords.lower().split():
1074 if k in t:
1076 if k in t:
1075 break
1077 break
1076 else:
1078 else:
1077 return ignore("doesn't match keyword")
1079 return ignore("doesn't match keyword")
1078
1080
1079 if not os.path.basename(lctest).startswith("test-"):
1081 if not os.path.basename(lctest).startswith("test-"):
1080 return skip("not a test file")
1082 return skip("not a test file")
1081 for ext, cls, out in testtypes:
1083 for ext, cls, out in testtypes:
1082 if lctest.endswith(ext):
1084 if lctest.endswith(ext):
1083 runner = cls
1085 runner = cls
1084 ref = os.path.join(TESTDIR, test + out)
1086 ref = os.path.join(TESTDIR, test + out)
1085 break
1087 break
1086 else:
1088 else:
1087 return skip("unknown test type")
1089 return skip("unknown test type")
1088
1090
1089 vlog("# Test", test)
1091 vlog("# Test", test)
1090
1092
1091 if os.path.exists(err):
1093 if os.path.exists(err):
1092 os.remove(err) # Remove any previous output files
1094 os.remove(err) # Remove any previous output files
1093
1095
1094 t = runner(testpath, options, count, ref)
1096 t = runner(test, testpath, options, count, ref)
1095 res = TestResult()
1097 res = TestResult()
1096 t.run(res)
1098 t.run(res)
1097 t.cleanup()
1099 t.cleanup()
1098
1100
1099 if res.interrupted:
1100 log('INTERRUPTED: %s (after %d seconds)' % (test, res.duration))
1101 raise KeyboardInterrupt()
1102
1103 if res.exception:
1101 if res.exception:
1104 return fail('Exception during execution: %s' % res.exception, 255)
1102 return fail('Exception during execution: %s' % res.exception, 255)
1105
1103
1106 ret = res.ret
1104 ret = res.ret
1107 out = res.out
1105 out = res.out
1108
1106
1109 times.append((test, res.duration))
1107 times.append((test, res.duration))
1110 vlog("# Ret was:", ret)
1108 vlog("# Ret was:", ret)
1111
1109
1112 skipped = res.skipped
1110 skipped = res.skipped
1113 refout = res.refout
1111 refout = res.refout
1114
1112
1115 if (ret != 0 or out != refout) and not skipped and not options.debug:
1113 if (ret != 0 or out != refout) and not skipped and not options.debug:
1116 # Save errors to a file for diagnosis
1114 # Save errors to a file for diagnosis
1117 f = open(err, "wb")
1115 f = open(err, "wb")
1118 for line in out:
1116 for line in out:
1119 f.write(line)
1117 f.write(line)
1120 f.close()
1118 f.close()
1121
1119
1122 if skipped:
1120 if skipped:
1123 if out is None: # debug mode: nothing to parse
1121 if out is None: # debug mode: nothing to parse
1124 missing = ['unknown']
1122 missing = ['unknown']
1125 failed = None
1123 failed = None
1126 else:
1124 else:
1127 missing, failed = parsehghaveoutput(out)
1125 missing, failed = parsehghaveoutput(out)
1128 if not missing:
1126 if not missing:
1129 missing = ['irrelevant']
1127 missing = ['irrelevant']
1130 if failed:
1128 if failed:
1131 result = fail("hghave failed checking for %s" % failed[-1], ret)
1129 result = fail("hghave failed checking for %s" % failed[-1], ret)
1132 skipped = False
1130 skipped = False
1133 else:
1131 else:
1134 result = skip(missing[-1])
1132 result = skip(missing[-1])
1135 elif ret == 'timeout':
1133 elif ret == 'timeout':
1136 result = fail("timed out", ret)
1134 result = fail("timed out", ret)
1137 elif out != refout:
1135 elif out != refout:
1138 info = {}
1136 info = {}
1139 if not options.nodiff:
1137 if not options.nodiff:
1140 iolock.acquire()
1138 iolock.acquire()
1141 if options.view:
1139 if options.view:
1142 os.system("%s %s %s" % (options.view, ref, err))
1140 os.system("%s %s %s" % (options.view, ref, err))
1143 else:
1141 else:
1144 info = showdiff(refout, out, ref, err)
1142 info = showdiff(refout, out, ref, err)
1145 iolock.release()
1143 iolock.release()
1146 msg = ""
1144 msg = ""
1147 if info.get('servefail'): msg += "serve failed and "
1145 if info.get('servefail'): msg += "serve failed and "
1148 if ret:
1146 if ret:
1149 msg += "output changed and " + describe(ret)
1147 msg += "output changed and " + describe(ret)
1150 else:
1148 else:
1151 msg += "output changed"
1149 msg += "output changed"
1152 result = fail(msg, ret)
1150 result = fail(msg, ret)
1153 elif ret:
1151 elif ret:
1154 result = fail(describe(ret), ret)
1152 result = fail(describe(ret), ret)
1155 else:
1153 else:
1156 result = success()
1154 result = success()
1157
1155
1158 if not options.verbose:
1156 if not options.verbose:
1159 iolock.acquire()
1157 iolock.acquire()
1160 sys.stdout.write(result[0])
1158 sys.stdout.write(result[0])
1161 sys.stdout.flush()
1159 sys.stdout.flush()
1162 iolock.release()
1160 iolock.release()
1163
1161
1164 return result
1162 return result
1165
1163
1166 _hgpath = None
1164 _hgpath = None
1167
1165
1168 def _gethgpath():
1166 def _gethgpath():
1169 """Return the path to the mercurial package that is actually found by
1167 """Return the path to the mercurial package that is actually found by
1170 the current Python interpreter."""
1168 the current Python interpreter."""
1171 global _hgpath
1169 global _hgpath
1172 if _hgpath is not None:
1170 if _hgpath is not None:
1173 return _hgpath
1171 return _hgpath
1174
1172
1175 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1173 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1176 pipe = os.popen(cmd % PYTHON)
1174 pipe = os.popen(cmd % PYTHON)
1177 try:
1175 try:
1178 _hgpath = pipe.read().strip()
1176 _hgpath = pipe.read().strip()
1179 finally:
1177 finally:
1180 pipe.close()
1178 pipe.close()
1181 return _hgpath
1179 return _hgpath
1182
1180
1183 def _checkhglib(verb):
1181 def _checkhglib(verb):
1184 """Ensure that the 'mercurial' package imported by python is
1182 """Ensure that the 'mercurial' package imported by python is
1185 the one we expect it to be. If not, print a warning to stderr."""
1183 the one we expect it to be. If not, print a warning to stderr."""
1186 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1184 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1187 actualhg = _gethgpath()
1185 actualhg = _gethgpath()
1188 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1186 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1189 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1187 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1190 ' (expected %s)\n'
1188 ' (expected %s)\n'
1191 % (verb, actualhg, expecthg))
1189 % (verb, actualhg, expecthg))
1192
1190
1193 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1191 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1194 times = []
1192 times = []
1195 iolock = threading.Lock()
1193 iolock = threading.Lock()
1196 abort = False
1194 abort = False
1197
1195
1198 def scheduletests(options, tests):
1196 def scheduletests(options, tests):
1199 jobs = options.jobs
1197 jobs = options.jobs
1200 done = queue.Queue()
1198 done = queue.Queue()
1201 running = 0
1199 running = 0
1202 count = 0
1200 count = 0
1203 global abort
1201 global abort
1204
1202
1205 def job(test, count):
1203 def job(test, count):
1206 try:
1204 try:
1207 done.put(runone(options, test, count))
1205 done.put(runone(options, test, count))
1208 except KeyboardInterrupt:
1206 except KeyboardInterrupt:
1209 pass
1207 pass
1210 except: # re-raises
1208 except: # re-raises
1211 done.put(('!', test, 'run-test raised an error, see traceback'))
1209 done.put(('!', test, 'run-test raised an error, see traceback'))
1212 raise
1210 raise
1213
1211
1214 try:
1212 try:
1215 while tests or running:
1213 while tests or running:
1216 if not done.empty() or running == jobs or not tests:
1214 if not done.empty() or running == jobs or not tests:
1217 try:
1215 try:
1218 code, test, msg = done.get(True, 1)
1216 code, test, msg = done.get(True, 1)
1219 results[code].append((test, msg))
1217 results[code].append((test, msg))
1220 if options.first and code not in '.si':
1218 if options.first and code not in '.si':
1221 break
1219 break
1222 except queue.Empty:
1220 except queue.Empty:
1223 continue
1221 continue
1224 running -= 1
1222 running -= 1
1225 if tests and not running == jobs:
1223 if tests and not running == jobs:
1226 test = tests.pop(0)
1224 test = tests.pop(0)
1227 if options.loop:
1225 if options.loop:
1228 tests.append(test)
1226 tests.append(test)
1229 t = threading.Thread(target=job, name=test, args=(test, count))
1227 t = threading.Thread(target=job, name=test, args=(test, count))
1230 t.start()
1228 t.start()
1231 running += 1
1229 running += 1
1232 count += 1
1230 count += 1
1233 except KeyboardInterrupt:
1231 except KeyboardInterrupt:
1234 abort = True
1232 abort = True
1235
1233
1236 def runtests(options, tests):
1234 def runtests(options, tests):
1237 try:
1235 try:
1238 if INST:
1236 if INST:
1239 installhg(options)
1237 installhg(options)
1240 _checkhglib("Testing")
1238 _checkhglib("Testing")
1241 else:
1239 else:
1242 usecorrectpython()
1240 usecorrectpython()
1243
1241
1244 if options.restart:
1242 if options.restart:
1245 orig = list(tests)
1243 orig = list(tests)
1246 while tests:
1244 while tests:
1247 if os.path.exists(tests[0] + ".err"):
1245 if os.path.exists(tests[0] + ".err"):
1248 break
1246 break
1249 tests.pop(0)
1247 tests.pop(0)
1250 if not tests:
1248 if not tests:
1251 print "running all tests"
1249 print "running all tests"
1252 tests = orig
1250 tests = orig
1253
1251
1254 scheduletests(options, tests)
1252 scheduletests(options, tests)
1255
1253
1256 failed = len(results['!'])
1254 failed = len(results['!'])
1257 warned = len(results['~'])
1255 warned = len(results['~'])
1258 tested = len(results['.']) + failed + warned
1256 tested = len(results['.']) + failed + warned
1259 skipped = len(results['s'])
1257 skipped = len(results['s'])
1260 ignored = len(results['i'])
1258 ignored = len(results['i'])
1261
1259
1262 print
1260 print
1263 if not options.noskips:
1261 if not options.noskips:
1264 for s in results['s']:
1262 for s in results['s']:
1265 print "Skipped %s: %s" % s
1263 print "Skipped %s: %s" % s
1266 for s in results['~']:
1264 for s in results['~']:
1267 print "Warned %s: %s" % s
1265 print "Warned %s: %s" % s
1268 for s in results['!']:
1266 for s in results['!']:
1269 print "Failed %s: %s" % s
1267 print "Failed %s: %s" % s
1270 _checkhglib("Tested")
1268 _checkhglib("Tested")
1271 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1269 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1272 tested, skipped + ignored, warned, failed)
1270 tested, skipped + ignored, warned, failed)
1273 if results['!']:
1271 if results['!']:
1274 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1272 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1275 if options.time:
1273 if options.time:
1276 outputtimes(options)
1274 outputtimes(options)
1277
1275
1278 if options.anycoverage:
1276 if options.anycoverage:
1279 outputcoverage(options)
1277 outputcoverage(options)
1280 except KeyboardInterrupt:
1278 except KeyboardInterrupt:
1281 failed = True
1279 failed = True
1282 print "\ninterrupted!"
1280 print "\ninterrupted!"
1283
1281
1284 if failed:
1282 if failed:
1285 return 1
1283 return 1
1286 if warned:
1284 if warned:
1287 return 80
1285 return 80
1288
1286
1289 testtypes = [('.py', PythonTest, '.out'),
1287 testtypes = [('.py', PythonTest, '.out'),
1290 ('.t', TTest, '')]
1288 ('.t', TTest, '')]
1291
1289
1292 def main(args, parser=None):
1290 def main(args, parser=None):
1293 parser = parser or getparser()
1291 parser = parser or getparser()
1294 (options, args) = parseargs(args, parser)
1292 (options, args) = parseargs(args, parser)
1295 os.umask(022)
1293 os.umask(022)
1296
1294
1297 checktools()
1295 checktools()
1298
1296
1299 if not args:
1297 if not args:
1300 if options.changed:
1298 if options.changed:
1301 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1299 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1302 None, 0)
1300 None, 0)
1303 stdout, stderr = proc.communicate()
1301 stdout, stderr = proc.communicate()
1304 args = stdout.strip('\0').split('\0')
1302 args = stdout.strip('\0').split('\0')
1305 else:
1303 else:
1306 args = os.listdir(".")
1304 args = os.listdir(".")
1307
1305
1308 tests = [t for t in args
1306 tests = [t for t in args
1309 if os.path.basename(t).startswith("test-")
1307 if os.path.basename(t).startswith("test-")
1310 and (t.endswith(".py") or t.endswith(".t"))]
1308 and (t.endswith(".py") or t.endswith(".t"))]
1311
1309
1312 if options.random:
1310 if options.random:
1313 random.shuffle(tests)
1311 random.shuffle(tests)
1314 else:
1312 else:
1315 # keywords for slow tests
1313 # keywords for slow tests
1316 slow = 'svn gendoc check-code-hg'.split()
1314 slow = 'svn gendoc check-code-hg'.split()
1317 def sortkey(f):
1315 def sortkey(f):
1318 # run largest tests first, as they tend to take the longest
1316 # run largest tests first, as they tend to take the longest
1319 try:
1317 try:
1320 val = -os.stat(f).st_size
1318 val = -os.stat(f).st_size
1321 except OSError, e:
1319 except OSError, e:
1322 if e.errno != errno.ENOENT:
1320 if e.errno != errno.ENOENT:
1323 raise
1321 raise
1324 return -1e9 # file does not exist, tell early
1322 return -1e9 # file does not exist, tell early
1325 for kw in slow:
1323 for kw in slow:
1326 if kw in f:
1324 if kw in f:
1327 val *= 10
1325 val *= 10
1328 return val
1326 return val
1329 tests.sort(key=sortkey)
1327 tests.sort(key=sortkey)
1330
1328
1331 if 'PYTHONHASHSEED' not in os.environ:
1329 if 'PYTHONHASHSEED' not in os.environ:
1332 # use a random python hash seed all the time
1330 # use a random python hash seed all the time
1333 # we do the randomness ourself to know what seed is used
1331 # we do the randomness ourself to know what seed is used
1334 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1332 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1335
1333
1336 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1334 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1337 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1335 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1338 if options.tmpdir:
1336 if options.tmpdir:
1339 options.keep_tmpdir = True
1337 options.keep_tmpdir = True
1340 tmpdir = options.tmpdir
1338 tmpdir = options.tmpdir
1341 if os.path.exists(tmpdir):
1339 if os.path.exists(tmpdir):
1342 # Meaning of tmpdir has changed since 1.3: we used to create
1340 # Meaning of tmpdir has changed since 1.3: we used to create
1343 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1341 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1344 # tmpdir already exists.
1342 # tmpdir already exists.
1345 print "error: temp dir %r already exists" % tmpdir
1343 print "error: temp dir %r already exists" % tmpdir
1346 return 1
1344 return 1
1347
1345
1348 # Automatically removing tmpdir sounds convenient, but could
1346 # Automatically removing tmpdir sounds convenient, but could
1349 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1347 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1350 # or "--tmpdir=$HOME".
1348 # or "--tmpdir=$HOME".
1351 #vlog("# Removing temp dir", tmpdir)
1349 #vlog("# Removing temp dir", tmpdir)
1352 #shutil.rmtree(tmpdir)
1350 #shutil.rmtree(tmpdir)
1353 os.makedirs(tmpdir)
1351 os.makedirs(tmpdir)
1354 else:
1352 else:
1355 d = None
1353 d = None
1356 if os.name == 'nt':
1354 if os.name == 'nt':
1357 # without this, we get the default temp dir location, but
1355 # without this, we get the default temp dir location, but
1358 # in all lowercase, which causes troubles with paths (issue3490)
1356 # in all lowercase, which causes troubles with paths (issue3490)
1359 d = os.getenv('TMP')
1357 d = os.getenv('TMP')
1360 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1358 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1361 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1359 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1362
1360
1363 if options.with_hg:
1361 if options.with_hg:
1364 INST = None
1362 INST = None
1365 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1363 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1366 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1364 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1367 os.makedirs(TMPBINDIR)
1365 os.makedirs(TMPBINDIR)
1368
1366
1369 # This looks redundant with how Python initializes sys.path from
1367 # This looks redundant with how Python initializes sys.path from
1370 # the location of the script being executed. Needed because the
1368 # the location of the script being executed. Needed because the
1371 # "hg" specified by --with-hg is not the only Python script
1369 # "hg" specified by --with-hg is not the only Python script
1372 # executed in the test suite that needs to import 'mercurial'
1370 # executed in the test suite that needs to import 'mercurial'
1373 # ... which means it's not really redundant at all.
1371 # ... which means it's not really redundant at all.
1374 PYTHONDIR = BINDIR
1372 PYTHONDIR = BINDIR
1375 else:
1373 else:
1376 INST = os.path.join(HGTMP, "install")
1374 INST = os.path.join(HGTMP, "install")
1377 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1375 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1378 TMPBINDIR = BINDIR
1376 TMPBINDIR = BINDIR
1379 PYTHONDIR = os.path.join(INST, "lib", "python")
1377 PYTHONDIR = os.path.join(INST, "lib", "python")
1380
1378
1381 os.environ["BINDIR"] = BINDIR
1379 os.environ["BINDIR"] = BINDIR
1382 os.environ["PYTHON"] = PYTHON
1380 os.environ["PYTHON"] = PYTHON
1383
1381
1384 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1382 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1385 if TMPBINDIR != BINDIR:
1383 if TMPBINDIR != BINDIR:
1386 path = [TMPBINDIR] + path
1384 path = [TMPBINDIR] + path
1387 os.environ["PATH"] = os.pathsep.join(path)
1385 os.environ["PATH"] = os.pathsep.join(path)
1388
1386
1389 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1387 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1390 # can run .../tests/run-tests.py test-foo where test-foo
1388 # can run .../tests/run-tests.py test-foo where test-foo
1391 # adds an extension to HGRC. Also include run-test.py directory to import
1389 # adds an extension to HGRC. Also include run-test.py directory to import
1392 # modules like heredoctest.
1390 # modules like heredoctest.
1393 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1391 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1394 # We have to augment PYTHONPATH, rather than simply replacing
1392 # We have to augment PYTHONPATH, rather than simply replacing
1395 # it, in case external libraries are only available via current
1393 # it, in case external libraries are only available via current
1396 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1394 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1397 # are in /opt/subversion.)
1395 # are in /opt/subversion.)
1398 oldpypath = os.environ.get(IMPL_PATH)
1396 oldpypath = os.environ.get(IMPL_PATH)
1399 if oldpypath:
1397 if oldpypath:
1400 pypath.append(oldpypath)
1398 pypath.append(oldpypath)
1401 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1399 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1402
1400
1403 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1401 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1404
1402
1405 vlog("# Using TESTDIR", TESTDIR)
1403 vlog("# Using TESTDIR", TESTDIR)
1406 vlog("# Using HGTMP", HGTMP)
1404 vlog("# Using HGTMP", HGTMP)
1407 vlog("# Using PATH", os.environ["PATH"])
1405 vlog("# Using PATH", os.environ["PATH"])
1408 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1406 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1409
1407
1410 try:
1408 try:
1411 return runtests(options, tests) or 0
1409 return runtests(options, tests) or 0
1412 finally:
1410 finally:
1413 time.sleep(.1)
1411 time.sleep(.1)
1414 cleanup(options)
1412 cleanup(options)
1415
1413
1416 if __name__ == '__main__':
1414 if __name__ == '__main__':
1417 sys.exit(main(sys.argv[1:]))
1415 sys.exit(main(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now