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