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