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