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