##// END OF EJS Templates
run-tests: move runtests() into TestRunner
Gregory Szorc -
r21360:becce297 default
parent child Browse files
Show More
@@ -1,1442 +1,1442 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 defaults = {
108 defaults = {
109 'jobs': ('HGTEST_JOBS', 1),
109 'jobs': ('HGTEST_JOBS', 1),
110 'timeout': ('HGTEST_TIMEOUT', 180),
110 'timeout': ('HGTEST_TIMEOUT', 180),
111 'port': ('HGTEST_PORT', 20059),
111 'port': ('HGTEST_PORT', 20059),
112 'shell': ('HGTEST_SHELL', 'sh'),
112 'shell': ('HGTEST_SHELL', 'sh'),
113 }
113 }
114
114
115 def parselistfiles(files, listtype, warn=True):
115 def parselistfiles(files, listtype, warn=True):
116 entries = dict()
116 entries = dict()
117 for filename in files:
117 for filename in files:
118 try:
118 try:
119 path = os.path.expanduser(os.path.expandvars(filename))
119 path = os.path.expanduser(os.path.expandvars(filename))
120 f = open(path, "r")
120 f = open(path, "r")
121 except IOError, err:
121 except IOError, err:
122 if err.errno != errno.ENOENT:
122 if err.errno != errno.ENOENT:
123 raise
123 raise
124 if warn:
124 if warn:
125 print "warning: no such %s file: %s" % (listtype, filename)
125 print "warning: no such %s file: %s" % (listtype, filename)
126 continue
126 continue
127
127
128 for line in f.readlines():
128 for line in f.readlines():
129 line = line.split('#', 1)[0].strip()
129 line = line.split('#', 1)[0].strip()
130 if line:
130 if line:
131 entries[line] = filename
131 entries[line] = filename
132
132
133 f.close()
133 f.close()
134 return entries
134 return entries
135
135
136 def getparser():
136 def getparser():
137 parser = optparse.OptionParser("%prog [options] [tests]")
137 parser = optparse.OptionParser("%prog [options] [tests]")
138
138
139 # keep these sorted
139 # keep these sorted
140 parser.add_option("--blacklist", action="append",
140 parser.add_option("--blacklist", action="append",
141 help="skip tests listed in the specified blacklist file")
141 help="skip tests listed in the specified blacklist file")
142 parser.add_option("--whitelist", action="append",
142 parser.add_option("--whitelist", action="append",
143 help="always run tests listed in the specified whitelist file")
143 help="always run tests listed in the specified whitelist file")
144 parser.add_option("--changed", type="string",
144 parser.add_option("--changed", type="string",
145 help="run tests that are changed in parent rev or working directory")
145 help="run tests that are changed in parent rev or working directory")
146 parser.add_option("-C", "--annotate", action="store_true",
146 parser.add_option("-C", "--annotate", action="store_true",
147 help="output files annotated with coverage")
147 help="output files annotated with coverage")
148 parser.add_option("-c", "--cover", action="store_true",
148 parser.add_option("-c", "--cover", action="store_true",
149 help="print a test coverage report")
149 help="print a test coverage report")
150 parser.add_option("-d", "--debug", action="store_true",
150 parser.add_option("-d", "--debug", action="store_true",
151 help="debug mode: write output of test scripts to console"
151 help="debug mode: write output of test scripts to console"
152 " rather than capturing and diffing it (disables timeout)")
152 " rather than capturing and diffing it (disables timeout)")
153 parser.add_option("-f", "--first", action="store_true",
153 parser.add_option("-f", "--first", action="store_true",
154 help="exit on the first test failure")
154 help="exit on the first test failure")
155 parser.add_option("-H", "--htmlcov", action="store_true",
155 parser.add_option("-H", "--htmlcov", action="store_true",
156 help="create an HTML report of the coverage of the files")
156 help="create an HTML report of the coverage of the files")
157 parser.add_option("-i", "--interactive", action="store_true",
157 parser.add_option("-i", "--interactive", action="store_true",
158 help="prompt to accept changed output")
158 help="prompt to accept changed output")
159 parser.add_option("-j", "--jobs", type="int",
159 parser.add_option("-j", "--jobs", type="int",
160 help="number of jobs to run in parallel"
160 help="number of jobs to run in parallel"
161 " (default: $%s or %d)" % defaults['jobs'])
161 " (default: $%s or %d)" % defaults['jobs'])
162 parser.add_option("--keep-tmpdir", action="store_true",
162 parser.add_option("--keep-tmpdir", action="store_true",
163 help="keep temporary directory after running tests")
163 help="keep temporary directory after running tests")
164 parser.add_option("-k", "--keywords",
164 parser.add_option("-k", "--keywords",
165 help="run tests matching keywords")
165 help="run tests matching keywords")
166 parser.add_option("-l", "--local", action="store_true",
166 parser.add_option("-l", "--local", action="store_true",
167 help="shortcut for --with-hg=<testdir>/../hg")
167 help="shortcut for --with-hg=<testdir>/../hg")
168 parser.add_option("--loop", action="store_true",
168 parser.add_option("--loop", action="store_true",
169 help="loop tests repeatedly")
169 help="loop tests repeatedly")
170 parser.add_option("-n", "--nodiff", action="store_true",
170 parser.add_option("-n", "--nodiff", action="store_true",
171 help="skip showing test changes")
171 help="skip showing test changes")
172 parser.add_option("-p", "--port", type="int",
172 parser.add_option("-p", "--port", type="int",
173 help="port on which servers should listen"
173 help="port on which servers should listen"
174 " (default: $%s or %d)" % defaults['port'])
174 " (default: $%s or %d)" % defaults['port'])
175 parser.add_option("--compiler", type="string",
175 parser.add_option("--compiler", type="string",
176 help="compiler to build with")
176 help="compiler to build with")
177 parser.add_option("--pure", action="store_true",
177 parser.add_option("--pure", action="store_true",
178 help="use pure Python code instead of C extensions")
178 help="use pure Python code instead of C extensions")
179 parser.add_option("-R", "--restart", action="store_true",
179 parser.add_option("-R", "--restart", action="store_true",
180 help="restart at last error")
180 help="restart at last error")
181 parser.add_option("-r", "--retest", action="store_true",
181 parser.add_option("-r", "--retest", action="store_true",
182 help="retest failed tests")
182 help="retest failed tests")
183 parser.add_option("-S", "--noskips", action="store_true",
183 parser.add_option("-S", "--noskips", action="store_true",
184 help="don't report skip tests verbosely")
184 help="don't report skip tests verbosely")
185 parser.add_option("--shell", type="string",
185 parser.add_option("--shell", type="string",
186 help="shell to use (default: $%s or %s)" % defaults['shell'])
186 help="shell to use (default: $%s or %s)" % defaults['shell'])
187 parser.add_option("-t", "--timeout", type="int",
187 parser.add_option("-t", "--timeout", type="int",
188 help="kill errant tests after TIMEOUT seconds"
188 help="kill errant tests after TIMEOUT seconds"
189 " (default: $%s or %d)" % defaults['timeout'])
189 " (default: $%s or %d)" % defaults['timeout'])
190 parser.add_option("--time", action="store_true",
190 parser.add_option("--time", action="store_true",
191 help="time how long each test takes")
191 help="time how long each test takes")
192 parser.add_option("--tmpdir", type="string",
192 parser.add_option("--tmpdir", type="string",
193 help="run tests in the given temporary directory"
193 help="run tests in the given temporary directory"
194 " (implies --keep-tmpdir)")
194 " (implies --keep-tmpdir)")
195 parser.add_option("-v", "--verbose", action="store_true",
195 parser.add_option("-v", "--verbose", action="store_true",
196 help="output verbose messages")
196 help="output verbose messages")
197 parser.add_option("--view", type="string",
197 parser.add_option("--view", type="string",
198 help="external diff viewer")
198 help="external diff viewer")
199 parser.add_option("--with-hg", type="string",
199 parser.add_option("--with-hg", type="string",
200 metavar="HG",
200 metavar="HG",
201 help="test using specified hg script rather than a "
201 help="test using specified hg script rather than a "
202 "temporary installation")
202 "temporary installation")
203 parser.add_option("-3", "--py3k-warnings", action="store_true",
203 parser.add_option("-3", "--py3k-warnings", action="store_true",
204 help="enable Py3k warnings on Python 2.6+")
204 help="enable Py3k warnings on Python 2.6+")
205 parser.add_option('--extra-config-opt', action="append",
205 parser.add_option('--extra-config-opt', action="append",
206 help='set the given config opt in the test hgrc')
206 help='set the given config opt in the test hgrc')
207 parser.add_option('--random', action="store_true",
207 parser.add_option('--random', action="store_true",
208 help='run tests in random order')
208 help='run tests in random order')
209
209
210 for option, (envvar, default) in defaults.items():
210 for option, (envvar, default) in defaults.items():
211 defaults[option] = type(default)(os.environ.get(envvar, default))
211 defaults[option] = type(default)(os.environ.get(envvar, default))
212 parser.set_defaults(**defaults)
212 parser.set_defaults(**defaults)
213
213
214 return parser
214 return parser
215
215
216 def parseargs(args, parser):
216 def parseargs(args, parser):
217 (options, args) = parser.parse_args(args)
217 (options, args) = parser.parse_args(args)
218
218
219 # jython is always pure
219 # jython is always pure
220 if 'java' in sys.platform or '__pypy__' in sys.modules:
220 if 'java' in sys.platform or '__pypy__' in sys.modules:
221 options.pure = True
221 options.pure = True
222
222
223 if options.with_hg:
223 if options.with_hg:
224 options.with_hg = os.path.expanduser(options.with_hg)
224 options.with_hg = os.path.expanduser(options.with_hg)
225 if not (os.path.isfile(options.with_hg) and
225 if not (os.path.isfile(options.with_hg) and
226 os.access(options.with_hg, os.X_OK)):
226 os.access(options.with_hg, os.X_OK)):
227 parser.error('--with-hg must specify an executable hg script')
227 parser.error('--with-hg must specify an executable hg script')
228 if not os.path.basename(options.with_hg) == 'hg':
228 if not os.path.basename(options.with_hg) == 'hg':
229 sys.stderr.write('warning: --with-hg should specify an hg script\n')
229 sys.stderr.write('warning: --with-hg should specify an hg script\n')
230 if options.local:
230 if options.local:
231 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
231 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
232 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
232 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
233 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
233 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
234 parser.error('--local specified, but %r not found or not executable'
234 parser.error('--local specified, but %r not found or not executable'
235 % hgbin)
235 % hgbin)
236 options.with_hg = hgbin
236 options.with_hg = hgbin
237
237
238 options.anycoverage = options.cover or options.annotate or options.htmlcov
238 options.anycoverage = options.cover or options.annotate or options.htmlcov
239 if options.anycoverage:
239 if options.anycoverage:
240 try:
240 try:
241 import coverage
241 import coverage
242 covver = version.StrictVersion(coverage.__version__).version
242 covver = version.StrictVersion(coverage.__version__).version
243 if covver < (3, 3):
243 if covver < (3, 3):
244 parser.error('coverage options require coverage 3.3 or later')
244 parser.error('coverage options require coverage 3.3 or later')
245 except ImportError:
245 except ImportError:
246 parser.error('coverage options now require the coverage package')
246 parser.error('coverage options now require the coverage package')
247
247
248 if options.anycoverage and options.local:
248 if options.anycoverage and options.local:
249 # this needs some path mangling somewhere, I guess
249 # this needs some path mangling somewhere, I guess
250 parser.error("sorry, coverage options do not work when --local "
250 parser.error("sorry, coverage options do not work when --local "
251 "is specified")
251 "is specified")
252
252
253 global verbose
253 global verbose
254 if options.verbose:
254 if options.verbose:
255 verbose = ''
255 verbose = ''
256
256
257 if options.tmpdir:
257 if options.tmpdir:
258 options.tmpdir = os.path.expanduser(options.tmpdir)
258 options.tmpdir = os.path.expanduser(options.tmpdir)
259
259
260 if options.jobs < 1:
260 if options.jobs < 1:
261 parser.error('--jobs must be positive')
261 parser.error('--jobs must be positive')
262 if options.interactive and options.debug:
262 if options.interactive and options.debug:
263 parser.error("-i/--interactive and -d/--debug are incompatible")
263 parser.error("-i/--interactive and -d/--debug are incompatible")
264 if options.debug:
264 if options.debug:
265 if options.timeout != defaults['timeout']:
265 if options.timeout != defaults['timeout']:
266 sys.stderr.write(
266 sys.stderr.write(
267 'warning: --timeout option ignored with --debug\n')
267 'warning: --timeout option ignored with --debug\n')
268 options.timeout = 0
268 options.timeout = 0
269 if options.py3k_warnings:
269 if options.py3k_warnings:
270 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
270 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
271 parser.error('--py3k-warnings can only be used on Python 2.6+')
271 parser.error('--py3k-warnings can only be used on Python 2.6+')
272 if options.blacklist:
272 if options.blacklist:
273 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
273 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
274 if options.whitelist:
274 if options.whitelist:
275 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
275 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
276 else:
276 else:
277 options.whitelisted = {}
277 options.whitelisted = {}
278
278
279 return (options, args)
279 return (options, args)
280
280
281 def rename(src, dst):
281 def rename(src, dst):
282 """Like os.rename(), trade atomicity and opened files friendliness
282 """Like os.rename(), trade atomicity and opened files friendliness
283 for existing destination support.
283 for existing destination support.
284 """
284 """
285 shutil.copy(src, dst)
285 shutil.copy(src, dst)
286 os.remove(src)
286 os.remove(src)
287
287
288 def parsehghaveoutput(lines):
288 def parsehghaveoutput(lines):
289 '''Parse hghave log lines.
289 '''Parse hghave log lines.
290 Return tuple of lists (missing, failed):
290 Return tuple of lists (missing, failed):
291 * the missing/unknown features
291 * the missing/unknown features
292 * the features for which existence check failed'''
292 * the features for which existence check failed'''
293 missing = []
293 missing = []
294 failed = []
294 failed = []
295 for line in lines:
295 for line in lines:
296 if line.startswith(SKIPPED_PREFIX):
296 if line.startswith(SKIPPED_PREFIX):
297 line = line.splitlines()[0]
297 line = line.splitlines()[0]
298 missing.append(line[len(SKIPPED_PREFIX):])
298 missing.append(line[len(SKIPPED_PREFIX):])
299 elif line.startswith(FAILED_PREFIX):
299 elif line.startswith(FAILED_PREFIX):
300 line = line.splitlines()[0]
300 line = line.splitlines()[0]
301 failed.append(line[len(FAILED_PREFIX):])
301 failed.append(line[len(FAILED_PREFIX):])
302
302
303 return missing, failed
303 return missing, failed
304
304
305 def showdiff(expected, output, ref, err):
305 def showdiff(expected, output, ref, err):
306 print
306 print
307 servefail = False
307 servefail = False
308 for line in difflib.unified_diff(expected, output, ref, err):
308 for line in difflib.unified_diff(expected, output, ref, err):
309 sys.stdout.write(line)
309 sys.stdout.write(line)
310 if not servefail and line.startswith(
310 if not servefail and line.startswith(
311 '+ abort: child process failed to start'):
311 '+ abort: child process failed to start'):
312 servefail = True
312 servefail = True
313 return {'servefail': servefail}
313 return {'servefail': servefail}
314
314
315
315
316 verbose = False
316 verbose = False
317 def vlog(*msg):
317 def vlog(*msg):
318 if verbose is not False:
318 if verbose is not False:
319 iolock.acquire()
319 iolock.acquire()
320 if verbose:
320 if verbose:
321 print verbose,
321 print verbose,
322 for m in msg:
322 for m in msg:
323 print m,
323 print m,
324 print
324 print
325 sys.stdout.flush()
325 sys.stdout.flush()
326 iolock.release()
326 iolock.release()
327
327
328 def log(*msg):
328 def log(*msg):
329 iolock.acquire()
329 iolock.acquire()
330 if verbose:
330 if verbose:
331 print verbose,
331 print verbose,
332 for m in msg:
332 for m in msg:
333 print m,
333 print m,
334 print
334 print
335 sys.stdout.flush()
335 sys.stdout.flush()
336 iolock.release()
336 iolock.release()
337
337
338 def findprogram(program):
338 def findprogram(program):
339 """Search PATH for a executable program"""
339 """Search PATH for a executable program"""
340 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
340 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
341 name = os.path.join(p, program)
341 name = os.path.join(p, program)
342 if os.name == 'nt' or os.access(name, os.X_OK):
342 if os.name == 'nt' or os.access(name, os.X_OK):
343 return name
343 return name
344 return None
344 return None
345
345
346 def createhgrc(path, options):
346 def createhgrc(path, options):
347 # create a fresh hgrc
347 # create a fresh hgrc
348 hgrc = open(path, 'w')
348 hgrc = open(path, 'w')
349 hgrc.write('[ui]\n')
349 hgrc.write('[ui]\n')
350 hgrc.write('slash = True\n')
350 hgrc.write('slash = True\n')
351 hgrc.write('interactive = False\n')
351 hgrc.write('interactive = False\n')
352 hgrc.write('[defaults]\n')
352 hgrc.write('[defaults]\n')
353 hgrc.write('backout = -d "0 0"\n')
353 hgrc.write('backout = -d "0 0"\n')
354 hgrc.write('commit = -d "0 0"\n')
354 hgrc.write('commit = -d "0 0"\n')
355 hgrc.write('shelve = --date "0 0"\n')
355 hgrc.write('shelve = --date "0 0"\n')
356 hgrc.write('tag = -d "0 0"\n')
356 hgrc.write('tag = -d "0 0"\n')
357 if options.extra_config_opt:
357 if options.extra_config_opt:
358 for opt in options.extra_config_opt:
358 for opt in options.extra_config_opt:
359 section, key = opt.split('.', 1)
359 section, key = opt.split('.', 1)
360 assert '=' in key, ('extra config opt %s must '
360 assert '=' in key, ('extra config opt %s must '
361 'have an = for assignment' % opt)
361 'have an = for assignment' % opt)
362 hgrc.write('[%s]\n%s\n' % (section, key))
362 hgrc.write('[%s]\n%s\n' % (section, key))
363 hgrc.close()
363 hgrc.close()
364
364
365 def checktools():
365 def checktools():
366 # Before we go any further, check for pre-requisite tools
366 # Before we go any further, check for pre-requisite tools
367 # stuff from coreutils (cat, rm, etc) are not tested
367 # stuff from coreutils (cat, rm, etc) are not tested
368 for p in requiredtools:
368 for p in requiredtools:
369 if os.name == 'nt' and not p.endswith('.exe'):
369 if os.name == 'nt' and not p.endswith('.exe'):
370 p += '.exe'
370 p += '.exe'
371 found = findprogram(p)
371 found = findprogram(p)
372 if found:
372 if found:
373 vlog("# Found prerequisite", p, "at", found)
373 vlog("# Found prerequisite", p, "at", found)
374 else:
374 else:
375 print "WARNING: Did not find prerequisite tool: "+p
375 print "WARNING: Did not find prerequisite tool: "+p
376
376
377 def terminate(proc):
377 def terminate(proc):
378 """Terminate subprocess (with fallback for Python versions < 2.6)"""
378 """Terminate subprocess (with fallback for Python versions < 2.6)"""
379 vlog('# Terminating process %d' % proc.pid)
379 vlog('# Terminating process %d' % proc.pid)
380 try:
380 try:
381 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
381 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
382 except OSError:
382 except OSError:
383 pass
383 pass
384
384
385 def killdaemons(pidfile):
385 def killdaemons(pidfile):
386 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
386 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
387 logfn=vlog)
387 logfn=vlog)
388
388
389 class Test(object):
389 class Test(object):
390 """Encapsulates a single, runnable test.
390 """Encapsulates a single, runnable test.
391
391
392 Test instances can be run multiple times via run(). However, multiple
392 Test instances can be run multiple times via run(). However, multiple
393 runs cannot be run concurrently.
393 runs cannot be run concurrently.
394 """
394 """
395
395
396 def __init__(self, runner, test, count, refpath):
396 def __init__(self, runner, test, count, refpath):
397 path = os.path.join(runner.testdir, test)
397 path = os.path.join(runner.testdir, test)
398 errpath = os.path.join(runner.testdir, '%s.err' % test)
398 errpath = os.path.join(runner.testdir, '%s.err' % test)
399
399
400 self._runner = runner
400 self._runner = runner
401 self._testdir = runner.testdir
401 self._testdir = runner.testdir
402 self._test = test
402 self._test = test
403 self._path = path
403 self._path = path
404 self._options = runner.options
404 self._options = runner.options
405 self._count = count
405 self._count = count
406 self._daemonpids = []
406 self._daemonpids = []
407 self._refpath = refpath
407 self._refpath = refpath
408 self._errpath = errpath
408 self._errpath = errpath
409
409
410 # If we're not in --debug mode and reference output file exists,
410 # If we're not in --debug mode and reference output file exists,
411 # check test output against it.
411 # check test output against it.
412 if runner.options.debug:
412 if runner.options.debug:
413 self._refout = None # to match "out is None"
413 self._refout = None # to match "out is None"
414 elif os.path.exists(refpath):
414 elif os.path.exists(refpath):
415 f = open(refpath, 'r')
415 f = open(refpath, 'r')
416 self._refout = f.read().splitlines(True)
416 self._refout = f.read().splitlines(True)
417 f.close()
417 f.close()
418 else:
418 else:
419 self._refout = []
419 self._refout = []
420
420
421 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
421 self._threadtmp = os.path.join(runner.hgtmp, 'child%d' % count)
422 os.mkdir(self._threadtmp)
422 os.mkdir(self._threadtmp)
423
423
424 def cleanup(self):
424 def cleanup(self):
425 for entry in self._daemonpids:
425 for entry in self._daemonpids:
426 killdaemons(entry)
426 killdaemons(entry)
427
427
428 if self._threadtmp and not self._options.keep_tmpdir:
428 if self._threadtmp and not self._options.keep_tmpdir:
429 shutil.rmtree(self._threadtmp, True)
429 shutil.rmtree(self._threadtmp, True)
430
430
431 def run(self):
431 def run(self):
432 if not os.path.exists(self._path):
432 if not os.path.exists(self._path):
433 return self.skip("Doesn't exist")
433 return self.skip("Doesn't exist")
434
434
435 options = self._options
435 options = self._options
436 if not (options.whitelisted and self._test in options.whitelisted):
436 if not (options.whitelisted and self._test in options.whitelisted):
437 if options.blacklist and self._test in options.blacklist:
437 if options.blacklist and self._test in options.blacklist:
438 return self.skip('blacklisted')
438 return self.skip('blacklisted')
439
439
440 if options.retest and not os.path.exists('%s.err' % self._test):
440 if options.retest and not os.path.exists('%s.err' % self._test):
441 return self.ignore('not retesting')
441 return self.ignore('not retesting')
442
442
443 if options.keywords:
443 if options.keywords:
444 f = open(self._test)
444 f = open(self._test)
445 t = f.read().lower() + self._test.lower()
445 t = f.read().lower() + self._test.lower()
446 f.close()
446 f.close()
447 for k in options.keywords.lower().split():
447 for k in options.keywords.lower().split():
448 if k in t:
448 if k in t:
449 break
449 break
450 else:
450 else:
451 return self.ignore("doesn't match keyword")
451 return self.ignore("doesn't match keyword")
452
452
453 if not os.path.basename(self._test.lower()).startswith('test-'):
453 if not os.path.basename(self._test.lower()).startswith('test-'):
454 return self.skip('not a test file')
454 return self.skip('not a test file')
455
455
456 # Remove any previous output files.
456 # Remove any previous output files.
457 if os.path.exists(self._errpath):
457 if os.path.exists(self._errpath):
458 os.remove(self._errpath)
458 os.remove(self._errpath)
459
459
460 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
460 testtmp = os.path.join(self._threadtmp, os.path.basename(self._path))
461 os.mkdir(testtmp)
461 os.mkdir(testtmp)
462 replacements, port = self._getreplacements(testtmp)
462 replacements, port = self._getreplacements(testtmp)
463 env = self._getenv(testtmp, port)
463 env = self._getenv(testtmp, port)
464 self._daemonpids.append(env['DAEMON_PIDS'])
464 self._daemonpids.append(env['DAEMON_PIDS'])
465 createhgrc(env['HGRCPATH'], options)
465 createhgrc(env['HGRCPATH'], options)
466
466
467 vlog('# Test', self._test)
467 vlog('# Test', self._test)
468
468
469 starttime = time.time()
469 starttime = time.time()
470 try:
470 try:
471 ret, out = self._run(testtmp, replacements, env)
471 ret, out = self._run(testtmp, replacements, env)
472 duration = time.time() - starttime
472 duration = time.time() - starttime
473 except KeyboardInterrupt:
473 except KeyboardInterrupt:
474 duration = time.time() - starttime
474 duration = time.time() - starttime
475 log('INTERRUPTED: %s (after %d seconds)' % (self._test, duration))
475 log('INTERRUPTED: %s (after %d seconds)' % (self._test, duration))
476 raise
476 raise
477 except Exception, e:
477 except Exception, e:
478 return self.fail('Exception during execution: %s' % e, 255)
478 return self.fail('Exception during execution: %s' % e, 255)
479
479
480 killdaemons(env['DAEMON_PIDS'])
480 killdaemons(env['DAEMON_PIDS'])
481
481
482 if not options.keep_tmpdir:
482 if not options.keep_tmpdir:
483 shutil.rmtree(testtmp)
483 shutil.rmtree(testtmp)
484
484
485 def describe(ret):
485 def describe(ret):
486 if ret < 0:
486 if ret < 0:
487 return 'killed by signal: %d' % -ret
487 return 'killed by signal: %d' % -ret
488 return 'returned error code %d' % ret
488 return 'returned error code %d' % ret
489
489
490 skipped = False
490 skipped = False
491
491
492 if ret == SKIPPED_STATUS:
492 if ret == SKIPPED_STATUS:
493 if out is None: # Debug mode, nothing to parse.
493 if out is None: # Debug mode, nothing to parse.
494 missing = ['unknown']
494 missing = ['unknown']
495 failed = None
495 failed = None
496 else:
496 else:
497 missing, failed = parsehghaveoutput(out)
497 missing, failed = parsehghaveoutput(out)
498
498
499 if not missing:
499 if not missing:
500 missing = ['irrelevant']
500 missing = ['irrelevant']
501
501
502 if failed:
502 if failed:
503 res = self.fail('hg have failed checking for %s' % failed[-1],
503 res = self.fail('hg have failed checking for %s' % failed[-1],
504 ret)
504 ret)
505 else:
505 else:
506 skipped = True
506 skipped = True
507 res = self.skip(missing[-1])
507 res = self.skip(missing[-1])
508 elif ret == 'timeout':
508 elif ret == 'timeout':
509 res = self.fail('timed out', ret)
509 res = self.fail('timed out', ret)
510 elif out != self._refout:
510 elif out != self._refout:
511 info = {}
511 info = {}
512 if not options.nodiff:
512 if not options.nodiff:
513 iolock.acquire()
513 iolock.acquire()
514 if options.view:
514 if options.view:
515 os.system("%s %s %s" % (options.view, self._refpath,
515 os.system("%s %s %s" % (options.view, self._refpath,
516 self._errpath))
516 self._errpath))
517 else:
517 else:
518 info = showdiff(self._refout, out, self._refpath,
518 info = showdiff(self._refout, out, self._refpath,
519 self._errpath)
519 self._errpath)
520 iolock.release()
520 iolock.release()
521 msg = ''
521 msg = ''
522 if info.get('servefail'):
522 if info.get('servefail'):
523 msg += 'serve failed and '
523 msg += 'serve failed and '
524 if ret:
524 if ret:
525 msg += 'output changed and ' + describe(ret)
525 msg += 'output changed and ' + describe(ret)
526 else:
526 else:
527 msg += 'output changed'
527 msg += 'output changed'
528
528
529 res = self.fail(msg, ret)
529 res = self.fail(msg, ret)
530 elif ret:
530 elif ret:
531 res = self.fail(describe(ret), ret)
531 res = self.fail(describe(ret), ret)
532 else:
532 else:
533 res = self.success()
533 res = self.success()
534
534
535 if (ret != 0 or out != self._refout) and not skipped \
535 if (ret != 0 or out != self._refout) and not skipped \
536 and not options.debug:
536 and not options.debug:
537 f = open(self._errpath, 'wb')
537 f = open(self._errpath, 'wb')
538 for line in out:
538 for line in out:
539 f.write(line)
539 f.write(line)
540 f.close()
540 f.close()
541
541
542 vlog("# Ret was:", ret)
542 vlog("# Ret was:", ret)
543
543
544 if not options.verbose:
544 if not options.verbose:
545 iolock.acquire()
545 iolock.acquire()
546 sys.stdout.write(res[0])
546 sys.stdout.write(res[0])
547 sys.stdout.flush()
547 sys.stdout.flush()
548 iolock.release()
548 iolock.release()
549
549
550 self._runner.times.append((self._test, duration))
550 self._runner.times.append((self._test, duration))
551
551
552 return res
552 return res
553
553
554 def _run(self, testtmp, replacements, env):
554 def _run(self, testtmp, replacements, env):
555 # This should be implemented in child classes to run tests.
555 # This should be implemented in child classes to run tests.
556 return self._skip('unknown test type')
556 return self._skip('unknown test type')
557
557
558 def _getreplacements(self, testtmp):
558 def _getreplacements(self, testtmp):
559 port = self._options.port + self._count * 3
559 port = self._options.port + self._count * 3
560 r = [
560 r = [
561 (r':%s\b' % port, ':$HGPORT'),
561 (r':%s\b' % port, ':$HGPORT'),
562 (r':%s\b' % (port + 1), ':$HGPORT1'),
562 (r':%s\b' % (port + 1), ':$HGPORT1'),
563 (r':%s\b' % (port + 2), ':$HGPORT2'),
563 (r':%s\b' % (port + 2), ':$HGPORT2'),
564 ]
564 ]
565
565
566 if os.name == 'nt':
566 if os.name == 'nt':
567 r.append(
567 r.append(
568 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
568 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
569 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
569 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
570 for c in testtmp), '$TESTTMP'))
570 for c in testtmp), '$TESTTMP'))
571 else:
571 else:
572 r.append((re.escape(testtmp), '$TESTTMP'))
572 r.append((re.escape(testtmp), '$TESTTMP'))
573
573
574 return r, port
574 return r, port
575
575
576 def _getenv(self, testtmp, port):
576 def _getenv(self, testtmp, port):
577 env = os.environ.copy()
577 env = os.environ.copy()
578 env['TESTTMP'] = testtmp
578 env['TESTTMP'] = testtmp
579 env['HOME'] = testtmp
579 env['HOME'] = testtmp
580 env["HGPORT"] = str(port)
580 env["HGPORT"] = str(port)
581 env["HGPORT1"] = str(port + 1)
581 env["HGPORT1"] = str(port + 1)
582 env["HGPORT2"] = str(port + 2)
582 env["HGPORT2"] = str(port + 2)
583 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
583 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
584 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
584 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
585 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
585 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
586 env["HGMERGE"] = "internal:merge"
586 env["HGMERGE"] = "internal:merge"
587 env["HGUSER"] = "test"
587 env["HGUSER"] = "test"
588 env["HGENCODING"] = "ascii"
588 env["HGENCODING"] = "ascii"
589 env["HGENCODINGMODE"] = "strict"
589 env["HGENCODINGMODE"] = "strict"
590
590
591 # Reset some environment variables to well-known values so that
591 # Reset some environment variables to well-known values so that
592 # the tests produce repeatable output.
592 # the tests produce repeatable output.
593 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
593 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
594 env['TZ'] = 'GMT'
594 env['TZ'] = 'GMT'
595 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
595 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
596 env['COLUMNS'] = '80'
596 env['COLUMNS'] = '80'
597 env['TERM'] = 'xterm'
597 env['TERM'] = 'xterm'
598
598
599 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
599 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
600 'NO_PROXY').split():
600 'NO_PROXY').split():
601 if k in env:
601 if k in env:
602 del env[k]
602 del env[k]
603
603
604 # unset env related to hooks
604 # unset env related to hooks
605 for k in env.keys():
605 for k in env.keys():
606 if k.startswith('HG_'):
606 if k.startswith('HG_'):
607 del env[k]
607 del env[k]
608
608
609 return env
609 return env
610
610
611 def success(self):
611 def success(self):
612 return '.', self._test, ''
612 return '.', self._test, ''
613
613
614 def fail(self, msg, ret):
614 def fail(self, msg, ret):
615 warned = ret is False
615 warned = ret is False
616 if not self._options.nodiff:
616 if not self._options.nodiff:
617 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
617 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self._test,
618 msg))
618 msg))
619 if (not ret and self._options.interactive and
619 if (not ret and self._options.interactive and
620 os.path.exists(self._errpath)):
620 os.path.exists(self._errpath)):
621 iolock.acquire()
621 iolock.acquire()
622 print 'Accept this change? [n] ',
622 print 'Accept this change? [n] ',
623 answer = sys.stdin.readline().strip()
623 answer = sys.stdin.readline().strip()
624 iolock.release()
624 iolock.release()
625 if answer.lower() in ('y', 'yes').split():
625 if answer.lower() in ('y', 'yes').split():
626 if self._test.endswith('.t'):
626 if self._test.endswith('.t'):
627 rename(self._errpath, self._testpath)
627 rename(self._errpath, self._testpath)
628 else:
628 else:
629 rename(self._errpath, '%s.out' % self._testpath)
629 rename(self._errpath, '%s.out' % self._testpath)
630
630
631 return '.', self._test, ''
631 return '.', self._test, ''
632
632
633 return warned and '~' or '!', self._test, msg
633 return warned and '~' or '!', self._test, msg
634
634
635 def skip(self, msg):
635 def skip(self, msg):
636 if self._options.verbose:
636 if self._options.verbose:
637 log("\nSkipping %s: %s" % (self._path, msg))
637 log("\nSkipping %s: %s" % (self._path, msg))
638
638
639 return 's', self._test, msg
639 return 's', self._test, msg
640
640
641 def ignore(self, msg):
641 def ignore(self, msg):
642 return 'i', self._test, msg
642 return 'i', self._test, msg
643
643
644 class PythonTest(Test):
644 class PythonTest(Test):
645 """A Python-based test."""
645 """A Python-based test."""
646 def _run(self, testtmp, replacements, env):
646 def _run(self, testtmp, replacements, env):
647 py3kswitch = self._options.py3k_warnings and ' -3' or ''
647 py3kswitch = self._options.py3k_warnings and ' -3' or ''
648 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
648 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self._path)
649 vlog("# Running", cmd)
649 vlog("# Running", cmd)
650 if os.name == 'nt':
650 if os.name == 'nt':
651 replacements.append((r'\r\n', '\n'))
651 replacements.append((r'\r\n', '\n'))
652 return run(cmd, testtmp, self._options, replacements, env)
652 return run(cmd, testtmp, self._options, replacements, env)
653
653
654
654
655 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
655 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
656 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
656 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
657 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
657 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
658 escapemap.update({'\\': '\\\\', '\r': r'\r'})
658 escapemap.update({'\\': '\\\\', '\r': r'\r'})
659 def escapef(m):
659 def escapef(m):
660 return escapemap[m.group(0)]
660 return escapemap[m.group(0)]
661 def stringescape(s):
661 def stringescape(s):
662 return escapesub(escapef, s)
662 return escapesub(escapef, s)
663
663
664 class TTest(Test):
664 class TTest(Test):
665 """A "t test" is a test backed by a .t file."""
665 """A "t test" is a test backed by a .t file."""
666
666
667 def _run(self, testtmp, replacements, env):
667 def _run(self, testtmp, replacements, env):
668 f = open(self._path)
668 f = open(self._path)
669 lines = f.readlines()
669 lines = f.readlines()
670 f.close()
670 f.close()
671
671
672 salt, script, after, expected = self._parsetest(lines, testtmp)
672 salt, script, after, expected = self._parsetest(lines, testtmp)
673
673
674 # Write out the generated script.
674 # Write out the generated script.
675 fname = '%s.sh' % testtmp
675 fname = '%s.sh' % testtmp
676 f = open(fname, 'w')
676 f = open(fname, 'w')
677 for l in script:
677 for l in script:
678 f.write(l)
678 f.write(l)
679 f.close()
679 f.close()
680
680
681 cmd = '%s "%s"' % (self._options.shell, fname)
681 cmd = '%s "%s"' % (self._options.shell, fname)
682 vlog("# Running", cmd)
682 vlog("# Running", cmd)
683
683
684 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
684 exitcode, output = run(cmd, testtmp, self._options, replacements, env)
685 # Do not merge output if skipped. Return hghave message instead.
685 # Do not merge output if skipped. Return hghave message instead.
686 # Similarly, with --debug, output is None.
686 # Similarly, with --debug, output is None.
687 if exitcode == SKIPPED_STATUS or output is None:
687 if exitcode == SKIPPED_STATUS or output is None:
688 return exitcode, output
688 return exitcode, output
689
689
690 return self._processoutput(exitcode, output, salt, after, expected)
690 return self._processoutput(exitcode, output, salt, after, expected)
691
691
692 def _hghave(self, reqs, testtmp):
692 def _hghave(self, reqs, testtmp):
693 # TODO do something smarter when all other uses of hghave are gone.
693 # TODO do something smarter when all other uses of hghave are gone.
694 tdir = self._testdir.replace('\\', '/')
694 tdir = self._testdir.replace('\\', '/')
695 proc = Popen4('%s -c "%s/hghave %s"' %
695 proc = Popen4('%s -c "%s/hghave %s"' %
696 (self._options.shell, tdir, ' '.join(reqs)),
696 (self._options.shell, tdir, ' '.join(reqs)),
697 testtmp, 0)
697 testtmp, 0)
698 stdout, stderr = proc.communicate()
698 stdout, stderr = proc.communicate()
699 ret = proc.wait()
699 ret = proc.wait()
700 if wifexited(ret):
700 if wifexited(ret):
701 ret = os.WEXITSTATUS(ret)
701 ret = os.WEXITSTATUS(ret)
702 if ret == 2:
702 if ret == 2:
703 print stdout
703 print stdout
704 sys.exit(1)
704 sys.exit(1)
705
705
706 return ret == 0
706 return ret == 0
707
707
708 def _parsetest(self, lines, testtmp):
708 def _parsetest(self, lines, testtmp):
709 # We generate a shell script which outputs unique markers to line
709 # We generate a shell script which outputs unique markers to line
710 # up script results with our source. These markers include input
710 # up script results with our source. These markers include input
711 # line number and the last return code.
711 # line number and the last return code.
712 salt = "SALT" + str(time.time())
712 salt = "SALT" + str(time.time())
713 def addsalt(line, inpython):
713 def addsalt(line, inpython):
714 if inpython:
714 if inpython:
715 script.append('%s %d 0\n' % (salt, line))
715 script.append('%s %d 0\n' % (salt, line))
716 else:
716 else:
717 script.append('echo %s %s $?\n' % (salt, line))
717 script.append('echo %s %s $?\n' % (salt, line))
718
718
719 script = []
719 script = []
720
720
721 # After we run the shell script, we re-unify the script output
721 # After we run the shell script, we re-unify the script output
722 # with non-active parts of the source, with synchronization by our
722 # with non-active parts of the source, with synchronization by our
723 # SALT line number markers. The after table contains the non-active
723 # SALT line number markers. The after table contains the non-active
724 # components, ordered by line number.
724 # components, ordered by line number.
725 after = {}
725 after = {}
726
726
727 # Expected shell script output.
727 # Expected shell script output.
728 expected = {}
728 expected = {}
729
729
730 pos = prepos = -1
730 pos = prepos = -1
731
731
732 # True or False when in a true or false conditional section
732 # True or False when in a true or false conditional section
733 skipping = None
733 skipping = None
734
734
735 # We keep track of whether or not we're in a Python block so we
735 # We keep track of whether or not we're in a Python block so we
736 # can generate the surrounding doctest magic.
736 # can generate the surrounding doctest magic.
737 inpython = False
737 inpython = False
738
738
739 if self._options.debug:
739 if self._options.debug:
740 script.append('set -x\n')
740 script.append('set -x\n')
741 if os.getenv('MSYSTEM'):
741 if os.getenv('MSYSTEM'):
742 script.append('alias pwd="pwd -W"\n')
742 script.append('alias pwd="pwd -W"\n')
743
743
744 for n, l in enumerate(lines):
744 for n, l in enumerate(lines):
745 if not l.endswith('\n'):
745 if not l.endswith('\n'):
746 l += '\n'
746 l += '\n'
747 if l.startswith('#if'):
747 if l.startswith('#if'):
748 lsplit = l.split()
748 lsplit = l.split()
749 if len(lsplit) < 2 or lsplit[0] != '#if':
749 if len(lsplit) < 2 or lsplit[0] != '#if':
750 after.setdefault(pos, []).append(' !!! invalid #if\n')
750 after.setdefault(pos, []).append(' !!! invalid #if\n')
751 if skipping is not None:
751 if skipping is not None:
752 after.setdefault(pos, []).append(' !!! nested #if\n')
752 after.setdefault(pos, []).append(' !!! nested #if\n')
753 skipping = not self._hghave(lsplit[1:], testtmp)
753 skipping = not self._hghave(lsplit[1:], testtmp)
754 after.setdefault(pos, []).append(l)
754 after.setdefault(pos, []).append(l)
755 elif l.startswith('#else'):
755 elif l.startswith('#else'):
756 if skipping is None:
756 if skipping is None:
757 after.setdefault(pos, []).append(' !!! missing #if\n')
757 after.setdefault(pos, []).append(' !!! missing #if\n')
758 skipping = not skipping
758 skipping = not skipping
759 after.setdefault(pos, []).append(l)
759 after.setdefault(pos, []).append(l)
760 elif l.startswith('#endif'):
760 elif l.startswith('#endif'):
761 if skipping is None:
761 if skipping is None:
762 after.setdefault(pos, []).append(' !!! missing #if\n')
762 after.setdefault(pos, []).append(' !!! missing #if\n')
763 skipping = None
763 skipping = None
764 after.setdefault(pos, []).append(l)
764 after.setdefault(pos, []).append(l)
765 elif skipping:
765 elif skipping:
766 after.setdefault(pos, []).append(l)
766 after.setdefault(pos, []).append(l)
767 elif l.startswith(' >>> '): # python inlines
767 elif l.startswith(' >>> '): # python inlines
768 after.setdefault(pos, []).append(l)
768 after.setdefault(pos, []).append(l)
769 prepos = pos
769 prepos = pos
770 pos = n
770 pos = n
771 if not inpython:
771 if not inpython:
772 # We've just entered a Python block. Add the header.
772 # We've just entered a Python block. Add the header.
773 inpython = True
773 inpython = True
774 addsalt(prepos, False) # Make sure we report the exit code.
774 addsalt(prepos, False) # Make sure we report the exit code.
775 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
775 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
776 addsalt(n, True)
776 addsalt(n, True)
777 script.append(l[2:])
777 script.append(l[2:])
778 elif l.startswith(' ... '): # python inlines
778 elif l.startswith(' ... '): # python inlines
779 after.setdefault(prepos, []).append(l)
779 after.setdefault(prepos, []).append(l)
780 script.append(l[2:])
780 script.append(l[2:])
781 elif l.startswith(' $ '): # commands
781 elif l.startswith(' $ '): # commands
782 if inpython:
782 if inpython:
783 script.append('EOF\n')
783 script.append('EOF\n')
784 inpython = False
784 inpython = False
785 after.setdefault(pos, []).append(l)
785 after.setdefault(pos, []).append(l)
786 prepos = pos
786 prepos = pos
787 pos = n
787 pos = n
788 addsalt(n, False)
788 addsalt(n, False)
789 cmd = l[4:].split()
789 cmd = l[4:].split()
790 if len(cmd) == 2 and cmd[0] == 'cd':
790 if len(cmd) == 2 and cmd[0] == 'cd':
791 l = ' $ cd %s || exit 1\n' % cmd[1]
791 l = ' $ cd %s || exit 1\n' % cmd[1]
792 script.append(l[4:])
792 script.append(l[4:])
793 elif l.startswith(' > '): # continuations
793 elif l.startswith(' > '): # continuations
794 after.setdefault(prepos, []).append(l)
794 after.setdefault(prepos, []).append(l)
795 script.append(l[4:])
795 script.append(l[4:])
796 elif l.startswith(' '): # results
796 elif l.startswith(' '): # results
797 # Queue up a list of expected results.
797 # Queue up a list of expected results.
798 expected.setdefault(pos, []).append(l[2:])
798 expected.setdefault(pos, []).append(l[2:])
799 else:
799 else:
800 if inpython:
800 if inpython:
801 script.append('EOF\n')
801 script.append('EOF\n')
802 inpython = False
802 inpython = False
803 # Non-command/result. Queue up for merged output.
803 # Non-command/result. Queue up for merged output.
804 after.setdefault(pos, []).append(l)
804 after.setdefault(pos, []).append(l)
805
805
806 if inpython:
806 if inpython:
807 script.append('EOF\n')
807 script.append('EOF\n')
808 if skipping is not None:
808 if skipping is not None:
809 after.setdefault(pos, []).append(' !!! missing #endif\n')
809 after.setdefault(pos, []).append(' !!! missing #endif\n')
810 addsalt(n + 1, False)
810 addsalt(n + 1, False)
811
811
812 return salt, script, after, expected
812 return salt, script, after, expected
813
813
814 def _processoutput(self, exitcode, output, salt, after, expected):
814 def _processoutput(self, exitcode, output, salt, after, expected):
815 # Merge the script output back into a unified test.
815 # Merge the script output back into a unified test.
816 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
816 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
817 if exitcode != 0:
817 if exitcode != 0:
818 warnonly = 3
818 warnonly = 3
819
819
820 pos = -1
820 pos = -1
821 postout = []
821 postout = []
822 for l in output:
822 for l in output:
823 lout, lcmd = l, None
823 lout, lcmd = l, None
824 if salt in l:
824 if salt in l:
825 lout, lcmd = l.split(salt, 1)
825 lout, lcmd = l.split(salt, 1)
826
826
827 if lout:
827 if lout:
828 if not lout.endswith('\n'):
828 if not lout.endswith('\n'):
829 lout += ' (no-eol)\n'
829 lout += ' (no-eol)\n'
830
830
831 # Find the expected output at the current position.
831 # Find the expected output at the current position.
832 el = None
832 el = None
833 if expected.get(pos, None):
833 if expected.get(pos, None):
834 el = expected[pos].pop(0)
834 el = expected[pos].pop(0)
835
835
836 r = TTest.linematch(el, lout)
836 r = TTest.linematch(el, lout)
837 if isinstance(r, str):
837 if isinstance(r, str):
838 if r == '+glob':
838 if r == '+glob':
839 lout = el[:-1] + ' (glob)\n'
839 lout = el[:-1] + ' (glob)\n'
840 r = '' # Warn only this line.
840 r = '' # Warn only this line.
841 elif r == '-glob':
841 elif r == '-glob':
842 lout = ''.join(el.rsplit(' (glob)', 1))
842 lout = ''.join(el.rsplit(' (glob)', 1))
843 r = '' # Warn only this line.
843 r = '' # Warn only this line.
844 else:
844 else:
845 log('\ninfo, unknown linematch result: %r\n' % r)
845 log('\ninfo, unknown linematch result: %r\n' % r)
846 r = False
846 r = False
847 if r:
847 if r:
848 postout.append(' ' + el)
848 postout.append(' ' + el)
849 else:
849 else:
850 if needescape(lout):
850 if needescape(lout):
851 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
851 lout = stringescape(lout.rstrip('\n')) + ' (esc)\n'
852 postout.append(' ' + lout) # Let diff deal with it.
852 postout.append(' ' + lout) # Let diff deal with it.
853 if r != '': # If line failed.
853 if r != '': # If line failed.
854 warnonly = 3 # for sure not
854 warnonly = 3 # for sure not
855 elif warnonly == 1: # Is "not yet" and line is warn only.
855 elif warnonly == 1: # Is "not yet" and line is warn only.
856 warnonly = 2 # Yes do warn.
856 warnonly = 2 # Yes do warn.
857
857
858 if lcmd:
858 if lcmd:
859 # Add on last return code.
859 # Add on last return code.
860 ret = int(lcmd.split()[1])
860 ret = int(lcmd.split()[1])
861 if ret != 0:
861 if ret != 0:
862 postout.append(' [%s]\n' % ret)
862 postout.append(' [%s]\n' % ret)
863 if pos in after:
863 if pos in after:
864 # Merge in non-active test bits.
864 # Merge in non-active test bits.
865 postout += after.pop(pos)
865 postout += after.pop(pos)
866 pos = int(lcmd.split()[0])
866 pos = int(lcmd.split()[0])
867
867
868 if pos in after:
868 if pos in after:
869 postout += after.pop(pos)
869 postout += after.pop(pos)
870
870
871 if warnonly == 2:
871 if warnonly == 2:
872 exitcode = False # Set exitcode to warned.
872 exitcode = False # Set exitcode to warned.
873
873
874 return exitcode, postout
874 return exitcode, postout
875
875
876 @staticmethod
876 @staticmethod
877 def rematch(el, l):
877 def rematch(el, l):
878 try:
878 try:
879 # use \Z to ensure that the regex matches to the end of the string
879 # use \Z to ensure that the regex matches to the end of the string
880 if os.name == 'nt':
880 if os.name == 'nt':
881 return re.match(el + r'\r?\n\Z', l)
881 return re.match(el + r'\r?\n\Z', l)
882 return re.match(el + r'\n\Z', l)
882 return re.match(el + r'\n\Z', l)
883 except re.error:
883 except re.error:
884 # el is an invalid regex
884 # el is an invalid regex
885 return False
885 return False
886
886
887 @staticmethod
887 @staticmethod
888 def globmatch(el, l):
888 def globmatch(el, l):
889 # The only supported special characters are * and ? plus / which also
889 # The only supported special characters are * and ? plus / which also
890 # matches \ on windows. Escaping of these characters is supported.
890 # matches \ on windows. Escaping of these characters is supported.
891 if el + '\n' == l:
891 if el + '\n' == l:
892 if os.altsep:
892 if os.altsep:
893 # matching on "/" is not needed for this line
893 # matching on "/" is not needed for this line
894 return '-glob'
894 return '-glob'
895 return True
895 return True
896 i, n = 0, len(el)
896 i, n = 0, len(el)
897 res = ''
897 res = ''
898 while i < n:
898 while i < n:
899 c = el[i]
899 c = el[i]
900 i += 1
900 i += 1
901 if c == '\\' and el[i] in '*?\\/':
901 if c == '\\' and el[i] in '*?\\/':
902 res += el[i - 1:i + 1]
902 res += el[i - 1:i + 1]
903 i += 1
903 i += 1
904 elif c == '*':
904 elif c == '*':
905 res += '.*'
905 res += '.*'
906 elif c == '?':
906 elif c == '?':
907 res += '.'
907 res += '.'
908 elif c == '/' and os.altsep:
908 elif c == '/' and os.altsep:
909 res += '[/\\\\]'
909 res += '[/\\\\]'
910 else:
910 else:
911 res += re.escape(c)
911 res += re.escape(c)
912 return TTest.rematch(res, l)
912 return TTest.rematch(res, l)
913
913
914 @staticmethod
914 @staticmethod
915 def linematch(el, l):
915 def linematch(el, l):
916 if el == l: # perfect match (fast)
916 if el == l: # perfect match (fast)
917 return True
917 return True
918 if el:
918 if el:
919 if el.endswith(" (esc)\n"):
919 if el.endswith(" (esc)\n"):
920 el = el[:-7].decode('string-escape') + '\n'
920 el = el[:-7].decode('string-escape') + '\n'
921 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
921 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
922 return True
922 return True
923 if el.endswith(" (re)\n"):
923 if el.endswith(" (re)\n"):
924 return TTest.rematch(el[:-6], l)
924 return TTest.rematch(el[:-6], l)
925 if el.endswith(" (glob)\n"):
925 if el.endswith(" (glob)\n"):
926 return TTest.globmatch(el[:-8], l)
926 return TTest.globmatch(el[:-8], l)
927 if os.altsep and l.replace('\\', '/') == el:
927 if os.altsep and l.replace('\\', '/') == el:
928 return '+glob'
928 return '+glob'
929 return False
929 return False
930
930
931 wifexited = getattr(os, "WIFEXITED", lambda x: False)
931 wifexited = getattr(os, "WIFEXITED", lambda x: False)
932 def run(cmd, wd, options, replacements, env):
932 def run(cmd, wd, options, replacements, env):
933 """Run command in a sub-process, capturing the output (stdout and stderr).
933 """Run command in a sub-process, capturing the output (stdout and stderr).
934 Return a tuple (exitcode, output). output is None in debug mode."""
934 Return a tuple (exitcode, output). output is None in debug mode."""
935 # TODO: Use subprocess.Popen if we're running on Python 2.4
935 # TODO: Use subprocess.Popen if we're running on Python 2.4
936 if options.debug:
936 if options.debug:
937 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
937 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
938 ret = proc.wait()
938 ret = proc.wait()
939 return (ret, None)
939 return (ret, None)
940
940
941 proc = Popen4(cmd, wd, options.timeout, env)
941 proc = Popen4(cmd, wd, options.timeout, env)
942 def cleanup():
942 def cleanup():
943 terminate(proc)
943 terminate(proc)
944 ret = proc.wait()
944 ret = proc.wait()
945 if ret == 0:
945 if ret == 0:
946 ret = signal.SIGTERM << 8
946 ret = signal.SIGTERM << 8
947 killdaemons(env['DAEMON_PIDS'])
947 killdaemons(env['DAEMON_PIDS'])
948 return ret
948 return ret
949
949
950 output = ''
950 output = ''
951 proc.tochild.close()
951 proc.tochild.close()
952
952
953 try:
953 try:
954 output = proc.fromchild.read()
954 output = proc.fromchild.read()
955 except KeyboardInterrupt:
955 except KeyboardInterrupt:
956 vlog('# Handling keyboard interrupt')
956 vlog('# Handling keyboard interrupt')
957 cleanup()
957 cleanup()
958 raise
958 raise
959
959
960 ret = proc.wait()
960 ret = proc.wait()
961 if wifexited(ret):
961 if wifexited(ret):
962 ret = os.WEXITSTATUS(ret)
962 ret = os.WEXITSTATUS(ret)
963
963
964 if proc.timeout:
964 if proc.timeout:
965 ret = 'timeout'
965 ret = 'timeout'
966
966
967 if ret:
967 if ret:
968 killdaemons(env['DAEMON_PIDS'])
968 killdaemons(env['DAEMON_PIDS'])
969
969
970 if abort:
970 if abort:
971 raise KeyboardInterrupt()
971 raise KeyboardInterrupt()
972
972
973 for s, r in replacements:
973 for s, r in replacements:
974 output = re.sub(s, r, output)
974 output = re.sub(s, r, output)
975 return ret, output.splitlines(True)
975 return ret, output.splitlines(True)
976
976
977 _hgpath = None
977 _hgpath = None
978
978
979 def _gethgpath():
979 def _gethgpath():
980 """Return the path to the mercurial package that is actually found by
980 """Return the path to the mercurial package that is actually found by
981 the current Python interpreter."""
981 the current Python interpreter."""
982 global _hgpath
982 global _hgpath
983 if _hgpath is not None:
983 if _hgpath is not None:
984 return _hgpath
984 return _hgpath
985
985
986 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
986 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
987 pipe = os.popen(cmd % PYTHON)
987 pipe = os.popen(cmd % PYTHON)
988 try:
988 try:
989 _hgpath = pipe.read().strip()
989 _hgpath = pipe.read().strip()
990 finally:
990 finally:
991 pipe.close()
991 pipe.close()
992 return _hgpath
992 return _hgpath
993
993
994 iolock = threading.Lock()
994 iolock = threading.Lock()
995 abort = False
995 abort = False
996
996
997 def scheduletests(runner, tests):
997 def scheduletests(runner, tests):
998 jobs = runner.options.jobs
998 jobs = runner.options.jobs
999 done = queue.Queue()
999 done = queue.Queue()
1000 running = 0
1000 running = 0
1001 count = 0
1001 count = 0
1002 global abort
1002 global abort
1003
1003
1004 def job(test, count):
1004 def job(test, count):
1005 try:
1005 try:
1006 t = runner.gettest(test, count)
1006 t = runner.gettest(test, count)
1007 done.put(t.run())
1007 done.put(t.run())
1008 t.cleanup()
1008 t.cleanup()
1009 except KeyboardInterrupt:
1009 except KeyboardInterrupt:
1010 pass
1010 pass
1011 except: # re-raises
1011 except: # re-raises
1012 done.put(('!', test, 'run-test raised an error, see traceback'))
1012 done.put(('!', test, 'run-test raised an error, see traceback'))
1013 raise
1013 raise
1014
1014
1015 try:
1015 try:
1016 while tests or running:
1016 while tests or running:
1017 if not done.empty() or running == jobs or not tests:
1017 if not done.empty() or running == jobs or not tests:
1018 try:
1018 try:
1019 code, test, msg = done.get(True, 1)
1019 code, test, msg = done.get(True, 1)
1020 runner.results[code].append((test, msg))
1020 runner.results[code].append((test, msg))
1021 if runner.options.first and code not in '.si':
1021 if runner.options.first and code not in '.si':
1022 break
1022 break
1023 except queue.Empty:
1023 except queue.Empty:
1024 continue
1024 continue
1025 running -= 1
1025 running -= 1
1026 if tests and not running == jobs:
1026 if tests and not running == jobs:
1027 test = tests.pop(0)
1027 test = tests.pop(0)
1028 if runner.options.loop:
1028 if runner.options.loop:
1029 tests.append(test)
1029 tests.append(test)
1030 t = threading.Thread(target=job, name=test, args=(test, count))
1030 t = threading.Thread(target=job, name=test, args=(test, count))
1031 t.start()
1031 t.start()
1032 running += 1
1032 running += 1
1033 count += 1
1033 count += 1
1034 except KeyboardInterrupt:
1034 except KeyboardInterrupt:
1035 abort = True
1035 abort = True
1036
1036
1037 def runtests(runner, tests):
1038 try:
1039 if runner.inst:
1040 runner.installhg()
1041 runner.checkhglib("Testing")
1042 else:
1043 runner.usecorrectpython()
1044
1045 if runner.options.restart:
1046 orig = list(tests)
1047 while tests:
1048 if os.path.exists(tests[0] + ".err"):
1049 break
1050 tests.pop(0)
1051 if not tests:
1052 print "running all tests"
1053 tests = orig
1054
1055 scheduletests(runner, tests)
1056
1057 failed = len(runner.results['!'])
1058 warned = len(runner.results['~'])
1059 tested = len(runner.results['.']) + failed + warned
1060 skipped = len(runner.results['s'])
1061 ignored = len(runner.results['i'])
1062
1063 print
1064 if not runner.options.noskips:
1065 for s in runner.results['s']:
1066 print "Skipped %s: %s" % s
1067 for s in runner.results['~']:
1068 print "Warned %s: %s" % s
1069 for s in runner.results['!']:
1070 print "Failed %s: %s" % s
1071 runner.checkhglib("Tested")
1072 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1073 tested, skipped + ignored, warned, failed)
1074 if runner.results['!']:
1075 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1076 if runner.options.time:
1077 runner.outputtimes()
1078
1079 if runner.options.anycoverage:
1080 runner.outputcoverage()
1081 except KeyboardInterrupt:
1082 failed = True
1083 print "\ninterrupted!"
1084
1085 if failed:
1086 return 1
1087 if warned:
1088 return 80
1089
1090 class TestRunner(object):
1037 class TestRunner(object):
1091 """Holds context for executing tests.
1038 """Holds context for executing tests.
1092
1039
1093 Tests rely on a lot of state. This object holds it for them.
1040 Tests rely on a lot of state. This object holds it for them.
1094 """
1041 """
1095
1042
1096 TESTTYPES = [
1043 TESTTYPES = [
1097 ('.py', PythonTest, '.out'),
1044 ('.py', PythonTest, '.out'),
1098 ('.t', TTest, ''),
1045 ('.t', TTest, ''),
1099 ]
1046 ]
1100
1047
1101 def __init__(self):
1048 def __init__(self):
1102 self.options = None
1049 self.options = None
1103 self.testdir = None
1050 self.testdir = None
1104 self.hgtmp = None
1051 self.hgtmp = None
1105 self.inst = None
1052 self.inst = None
1106 self.bindir = None
1053 self.bindir = None
1107 self.tmpbinddir = None
1054 self.tmpbinddir = None
1108 self.pythondir = None
1055 self.pythondir = None
1109 self.coveragefile = None
1056 self.coveragefile = None
1110 self.times = [] # Holds execution times of tests.
1057 self.times = [] # Holds execution times of tests.
1111 self.results = {
1058 self.results = {
1112 '.': [],
1059 '.': [],
1113 '!': [],
1060 '!': [],
1114 '~': [],
1061 '~': [],
1115 's': [],
1062 's': [],
1116 'i': [],
1063 'i': [],
1117 }
1064 }
1118 self._createdfiles = []
1065 self._createdfiles = []
1119
1066
1067 def runtests(self, tests):
1068 try:
1069 if self.inst:
1070 self.installhg()
1071 self.checkhglib("Testing")
1072 else:
1073 self.usecorrectpython()
1074
1075 if self.options.restart:
1076 orig = list(tests)
1077 while tests:
1078 if os.path.exists(tests[0] + ".err"):
1079 break
1080 tests.pop(0)
1081 if not tests:
1082 print "running all tests"
1083 tests = orig
1084
1085 scheduletests(self, tests)
1086
1087 failed = len(self.results['!'])
1088 warned = len(self.results['~'])
1089 tested = len(self.results['.']) + failed + warned
1090 skipped = len(self.results['s'])
1091 ignored = len(self.results['i'])
1092
1093 print
1094 if not self.options.noskips:
1095 for s in self.results['s']:
1096 print "Skipped %s: %s" % s
1097 for s in self.results['~']:
1098 print "Warned %s: %s" % s
1099 for s in self.results['!']:
1100 print "Failed %s: %s" % s
1101 self.checkhglib("Tested")
1102 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1103 tested, skipped + ignored, warned, failed)
1104 if self.results['!']:
1105 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1106 if self.options.time:
1107 self.outputtimes()
1108
1109 if self.options.anycoverage:
1110 self.outputcoverage()
1111 except KeyboardInterrupt:
1112 failed = True
1113 print "\ninterrupted!"
1114
1115 if failed:
1116 return 1
1117 if warned:
1118 return 80
1119
1120 def gettest(self, test, count):
1120 def gettest(self, test, count):
1121 """Obtain a Test by looking at its filename.
1121 """Obtain a Test by looking at its filename.
1122
1122
1123 Returns a Test instance. The Test may not be runnable if it doesn't
1123 Returns a Test instance. The Test may not be runnable if it doesn't
1124 map to a known type.
1124 map to a known type.
1125 """
1125 """
1126 lctest = test.lower()
1126 lctest = test.lower()
1127 refpath = os.path.join(self.testdir, test)
1127 refpath = os.path.join(self.testdir, test)
1128
1128
1129 testcls = Test
1129 testcls = Test
1130
1130
1131 for ext, cls, out in self.TESTTYPES:
1131 for ext, cls, out in self.TESTTYPES:
1132 if lctest.endswith(ext):
1132 if lctest.endswith(ext):
1133 testcls = cls
1133 testcls = cls
1134 refpath = os.path.join(self.testdir, test + out)
1134 refpath = os.path.join(self.testdir, test + out)
1135 break
1135 break
1136
1136
1137 return testcls(self, test, count, refpath)
1137 return testcls(self, test, count, refpath)
1138
1138
1139 def cleanup(self):
1139 def cleanup(self):
1140 """Clean up state from this test invocation."""
1140 """Clean up state from this test invocation."""
1141
1141
1142 if self.options.keep_tmpdir:
1142 if self.options.keep_tmpdir:
1143 return
1143 return
1144
1144
1145 vlog("# Cleaning up HGTMP", self.hgtmp)
1145 vlog("# Cleaning up HGTMP", self.hgtmp)
1146 shutil.rmtree(self.hgtmp, True)
1146 shutil.rmtree(self.hgtmp, True)
1147 for f in self._createdfiles:
1147 for f in self._createdfiles:
1148 try:
1148 try:
1149 os.remove(f)
1149 os.remove(f)
1150 except OSError:
1150 except OSError:
1151 pass
1151 pass
1152
1152
1153 def usecorrectpython(self):
1153 def usecorrectpython(self):
1154 # Some tests run the Python interpreter. They must use the
1154 # Some tests run the Python interpreter. They must use the
1155 # same interpreter or bad things will happen.
1155 # same interpreter or bad things will happen.
1156 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1156 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1157 if getattr(os, 'symlink', None):
1157 if getattr(os, 'symlink', None):
1158 vlog("# Making python executable in test path a symlink to '%s'" %
1158 vlog("# Making python executable in test path a symlink to '%s'" %
1159 sys.executable)
1159 sys.executable)
1160 mypython = os.path.join(self.tmpbindir, pyexename)
1160 mypython = os.path.join(self.tmpbindir, pyexename)
1161 try:
1161 try:
1162 if os.readlink(mypython) == sys.executable:
1162 if os.readlink(mypython) == sys.executable:
1163 return
1163 return
1164 os.unlink(mypython)
1164 os.unlink(mypython)
1165 except OSError, err:
1165 except OSError, err:
1166 if err.errno != errno.ENOENT:
1166 if err.errno != errno.ENOENT:
1167 raise
1167 raise
1168 if findprogram(pyexename) != sys.executable:
1168 if findprogram(pyexename) != sys.executable:
1169 try:
1169 try:
1170 os.symlink(sys.executable, mypython)
1170 os.symlink(sys.executable, mypython)
1171 self._createdfiles.append(mypython)
1171 self._createdfiles.append(mypython)
1172 except OSError, err:
1172 except OSError, err:
1173 # child processes may race, which is harmless
1173 # child processes may race, which is harmless
1174 if err.errno != errno.EEXIST:
1174 if err.errno != errno.EEXIST:
1175 raise
1175 raise
1176 else:
1176 else:
1177 exedir, exename = os.path.split(sys.executable)
1177 exedir, exename = os.path.split(sys.executable)
1178 vlog("# Modifying search path to find %s as %s in '%s'" %
1178 vlog("# Modifying search path to find %s as %s in '%s'" %
1179 (exename, pyexename, exedir))
1179 (exename, pyexename, exedir))
1180 path = os.environ['PATH'].split(os.pathsep)
1180 path = os.environ['PATH'].split(os.pathsep)
1181 while exedir in path:
1181 while exedir in path:
1182 path.remove(exedir)
1182 path.remove(exedir)
1183 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1183 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1184 if not findprogram(pyexename):
1184 if not findprogram(pyexename):
1185 print "WARNING: Cannot find %s in search path" % pyexename
1185 print "WARNING: Cannot find %s in search path" % pyexename
1186
1186
1187 def installhg(self):
1187 def installhg(self):
1188 vlog("# Performing temporary installation of HG")
1188 vlog("# Performing temporary installation of HG")
1189 installerrs = os.path.join("tests", "install.err")
1189 installerrs = os.path.join("tests", "install.err")
1190 compiler = ''
1190 compiler = ''
1191 if self.options.compiler:
1191 if self.options.compiler:
1192 compiler = '--compiler ' + self.options.compiler
1192 compiler = '--compiler ' + self.options.compiler
1193 pure = self.options.pure and "--pure" or ""
1193 pure = self.options.pure and "--pure" or ""
1194 py3 = ''
1194 py3 = ''
1195 if sys.version_info[0] == 3:
1195 if sys.version_info[0] == 3:
1196 py3 = '--c2to3'
1196 py3 = '--c2to3'
1197
1197
1198 # Run installer in hg root
1198 # Run installer in hg root
1199 script = os.path.realpath(sys.argv[0])
1199 script = os.path.realpath(sys.argv[0])
1200 hgroot = os.path.dirname(os.path.dirname(script))
1200 hgroot = os.path.dirname(os.path.dirname(script))
1201 os.chdir(hgroot)
1201 os.chdir(hgroot)
1202 nohome = '--home=""'
1202 nohome = '--home=""'
1203 if os.name == 'nt':
1203 if os.name == 'nt':
1204 # The --home="" trick works only on OS where os.sep == '/'
1204 # The --home="" trick works only on OS where os.sep == '/'
1205 # because of a distutils convert_path() fast-path. Avoid it at
1205 # because of a distutils convert_path() fast-path. Avoid it at
1206 # least on Windows for now, deal with .pydistutils.cfg bugs
1206 # least on Windows for now, deal with .pydistutils.cfg bugs
1207 # when they happen.
1207 # when they happen.
1208 nohome = ''
1208 nohome = ''
1209 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1209 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1210 ' build %(compiler)s --build-base="%(base)s"'
1210 ' build %(compiler)s --build-base="%(base)s"'
1211 ' install --force --prefix="%(prefix)s"'
1211 ' install --force --prefix="%(prefix)s"'
1212 ' --install-lib="%(libdir)s"'
1212 ' --install-lib="%(libdir)s"'
1213 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1213 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1214 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1214 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1215 'compiler': compiler,
1215 'compiler': compiler,
1216 'base': os.path.join(self.hgtmp, "build"),
1216 'base': os.path.join(self.hgtmp, "build"),
1217 'prefix': self.inst, 'libdir': self.pythondir,
1217 'prefix': self.inst, 'libdir': self.pythondir,
1218 'bindir': self.bindir,
1218 'bindir': self.bindir,
1219 'nohome': nohome, 'logfile': installerrs})
1219 'nohome': nohome, 'logfile': installerrs})
1220 vlog("# Running", cmd)
1220 vlog("# Running", cmd)
1221 if os.system(cmd) == 0:
1221 if os.system(cmd) == 0:
1222 if not self.options.verbose:
1222 if not self.options.verbose:
1223 os.remove(installerrs)
1223 os.remove(installerrs)
1224 else:
1224 else:
1225 f = open(installerrs)
1225 f = open(installerrs)
1226 for line in f:
1226 for line in f:
1227 print line,
1227 print line,
1228 f.close()
1228 f.close()
1229 sys.exit(1)
1229 sys.exit(1)
1230 os.chdir(self.testdir)
1230 os.chdir(self.testdir)
1231
1231
1232 self.usecorrectpython()
1232 self.usecorrectpython()
1233
1233
1234 if self.options.py3k_warnings and not self.options.anycoverage:
1234 if self.options.py3k_warnings and not self.options.anycoverage:
1235 vlog("# Updating hg command to enable Py3k Warnings switch")
1235 vlog("# Updating hg command to enable Py3k Warnings switch")
1236 f = open(os.path.join(self.bindir, 'hg'), 'r')
1236 f = open(os.path.join(self.bindir, 'hg'), 'r')
1237 lines = [line.rstrip() for line in f]
1237 lines = [line.rstrip() for line in f]
1238 lines[0] += ' -3'
1238 lines[0] += ' -3'
1239 f.close()
1239 f.close()
1240 f = open(os.path.join(self.bindir, 'hg'), 'w')
1240 f = open(os.path.join(self.bindir, 'hg'), 'w')
1241 for line in lines:
1241 for line in lines:
1242 f.write(line + '\n')
1242 f.write(line + '\n')
1243 f.close()
1243 f.close()
1244
1244
1245 hgbat = os.path.join(self.bindir, 'hg.bat')
1245 hgbat = os.path.join(self.bindir, 'hg.bat')
1246 if os.path.isfile(hgbat):
1246 if os.path.isfile(hgbat):
1247 # hg.bat expects to be put in bin/scripts while run-tests.py
1247 # hg.bat expects to be put in bin/scripts while run-tests.py
1248 # installation layout put it in bin/ directly. Fix it
1248 # installation layout put it in bin/ directly. Fix it
1249 f = open(hgbat, 'rb')
1249 f = open(hgbat, 'rb')
1250 data = f.read()
1250 data = f.read()
1251 f.close()
1251 f.close()
1252 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1252 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1253 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1253 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1254 '"%~dp0python" "%~dp0hg" %*')
1254 '"%~dp0python" "%~dp0hg" %*')
1255 f = open(hgbat, 'wb')
1255 f = open(hgbat, 'wb')
1256 f.write(data)
1256 f.write(data)
1257 f.close()
1257 f.close()
1258 else:
1258 else:
1259 print 'WARNING: cannot fix hg.bat reference to python.exe'
1259 print 'WARNING: cannot fix hg.bat reference to python.exe'
1260
1260
1261 if self.options.anycoverage:
1261 if self.options.anycoverage:
1262 custom = os.path.join(self.testdir, 'sitecustomize.py')
1262 custom = os.path.join(self.testdir, 'sitecustomize.py')
1263 target = os.path.join(self.pythondir, 'sitecustomize.py')
1263 target = os.path.join(self.pythondir, 'sitecustomize.py')
1264 vlog('# Installing coverage trigger to %s' % target)
1264 vlog('# Installing coverage trigger to %s' % target)
1265 shutil.copyfile(custom, target)
1265 shutil.copyfile(custom, target)
1266 rc = os.path.join(self.testdir, '.coveragerc')
1266 rc = os.path.join(self.testdir, '.coveragerc')
1267 vlog('# Installing coverage rc to %s' % rc)
1267 vlog('# Installing coverage rc to %s' % rc)
1268 os.environ['COVERAGE_PROCESS_START'] = rc
1268 os.environ['COVERAGE_PROCESS_START'] = rc
1269 fn = os.path.join(self.inst, '..', '.coverage')
1269 fn = os.path.join(self.inst, '..', '.coverage')
1270 os.environ['COVERAGE_FILE'] = fn
1270 os.environ['COVERAGE_FILE'] = fn
1271
1271
1272 def checkhglib(self, verb):
1272 def checkhglib(self, verb):
1273 """Ensure that the 'mercurial' package imported by python is
1273 """Ensure that the 'mercurial' package imported by python is
1274 the one we expect it to be. If not, print a warning to stderr."""
1274 the one we expect it to be. If not, print a warning to stderr."""
1275 expecthg = os.path.join(self.pythondir, 'mercurial')
1275 expecthg = os.path.join(self.pythondir, 'mercurial')
1276 actualhg = _gethgpath()
1276 actualhg = _gethgpath()
1277 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1277 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1278 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1278 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1279 ' (expected %s)\n'
1279 ' (expected %s)\n'
1280 % (verb, actualhg, expecthg))
1280 % (verb, actualhg, expecthg))
1281
1281
1282 def outputtimes(self):
1282 def outputtimes(self):
1283 vlog('# Producing time report')
1283 vlog('# Producing time report')
1284 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1284 self.times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1285 cols = '%7.3f %s'
1285 cols = '%7.3f %s'
1286 print '\n%-7s %s' % ('Time', 'Test')
1286 print '\n%-7s %s' % ('Time', 'Test')
1287 for test, timetaken in self.times:
1287 for test, timetaken in self.times:
1288 print cols % (timetaken, test)
1288 print cols % (timetaken, test)
1289
1289
1290 def outputcoverage(self):
1290 def outputcoverage(self):
1291 vlog('# Producing coverage report')
1291 vlog('# Producing coverage report')
1292 os.chdir(self.pythondir)
1292 os.chdir(self.pythondir)
1293
1293
1294 def covrun(*args):
1294 def covrun(*args):
1295 cmd = 'coverage %s' % ' '.join(args)
1295 cmd = 'coverage %s' % ' '.join(args)
1296 vlog('# Running: %s' % cmd)
1296 vlog('# Running: %s' % cmd)
1297 os.system(cmd)
1297 os.system(cmd)
1298
1298
1299 covrun('-c')
1299 covrun('-c')
1300 omit = ','.join(os.path.join(x, '*') for x in
1300 omit = ','.join(os.path.join(x, '*') for x in
1301 [self.bindir, self.testdir])
1301 [self.bindir, self.testdir])
1302 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1302 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1303 if self.options.htmlcov:
1303 if self.options.htmlcov:
1304 htmldir = os.path.join(self.testdir, 'htmlcov')
1304 htmldir = os.path.join(self.testdir, 'htmlcov')
1305 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1305 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1306 '"--omit=%s"' % omit)
1306 '"--omit=%s"' % omit)
1307 if self.options.annotate:
1307 if self.options.annotate:
1308 adir = os.path.join(self.testdir, 'annotated')
1308 adir = os.path.join(self.testdir, 'annotated')
1309 if not os.path.isdir(adir):
1309 if not os.path.isdir(adir):
1310 os.mkdir(adir)
1310 os.mkdir(adir)
1311 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1311 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1312
1312
1313 def main(args, parser=None):
1313 def main(args, parser=None):
1314 runner = TestRunner()
1314 runner = TestRunner()
1315
1315
1316 parser = parser or getparser()
1316 parser = parser or getparser()
1317 (options, args) = parseargs(args, parser)
1317 (options, args) = parseargs(args, parser)
1318 runner.options = options
1318 runner.options = options
1319 os.umask(022)
1319 os.umask(022)
1320
1320
1321 checktools()
1321 checktools()
1322
1322
1323 if not args:
1323 if not args:
1324 if options.changed:
1324 if options.changed:
1325 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1325 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1326 None, 0)
1326 None, 0)
1327 stdout, stderr = proc.communicate()
1327 stdout, stderr = proc.communicate()
1328 args = stdout.strip('\0').split('\0')
1328 args = stdout.strip('\0').split('\0')
1329 else:
1329 else:
1330 args = os.listdir(".")
1330 args = os.listdir(".")
1331
1331
1332 tests = [t for t in args
1332 tests = [t for t in args
1333 if os.path.basename(t).startswith("test-")
1333 if os.path.basename(t).startswith("test-")
1334 and (t.endswith(".py") or t.endswith(".t"))]
1334 and (t.endswith(".py") or t.endswith(".t"))]
1335
1335
1336 if options.random:
1336 if options.random:
1337 random.shuffle(tests)
1337 random.shuffle(tests)
1338 else:
1338 else:
1339 # keywords for slow tests
1339 # keywords for slow tests
1340 slow = 'svn gendoc check-code-hg'.split()
1340 slow = 'svn gendoc check-code-hg'.split()
1341 def sortkey(f):
1341 def sortkey(f):
1342 # run largest tests first, as they tend to take the longest
1342 # run largest tests first, as they tend to take the longest
1343 try:
1343 try:
1344 val = -os.stat(f).st_size
1344 val = -os.stat(f).st_size
1345 except OSError, e:
1345 except OSError, e:
1346 if e.errno != errno.ENOENT:
1346 if e.errno != errno.ENOENT:
1347 raise
1347 raise
1348 return -1e9 # file does not exist, tell early
1348 return -1e9 # file does not exist, tell early
1349 for kw in slow:
1349 for kw in slow:
1350 if kw in f:
1350 if kw in f:
1351 val *= 10
1351 val *= 10
1352 return val
1352 return val
1353 tests.sort(key=sortkey)
1353 tests.sort(key=sortkey)
1354
1354
1355 if 'PYTHONHASHSEED' not in os.environ:
1355 if 'PYTHONHASHSEED' not in os.environ:
1356 # use a random python hash seed all the time
1356 # use a random python hash seed all the time
1357 # we do the randomness ourself to know what seed is used
1357 # we do the randomness ourself to know what seed is used
1358 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1358 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1359
1359
1360 runner.testdir = os.environ['TESTDIR'] = os.getcwd()
1360 runner.testdir = os.environ['TESTDIR'] = os.getcwd()
1361 if options.tmpdir:
1361 if options.tmpdir:
1362 options.keep_tmpdir = True
1362 options.keep_tmpdir = True
1363 tmpdir = options.tmpdir
1363 tmpdir = options.tmpdir
1364 if os.path.exists(tmpdir):
1364 if os.path.exists(tmpdir):
1365 # Meaning of tmpdir has changed since 1.3: we used to create
1365 # Meaning of tmpdir has changed since 1.3: we used to create
1366 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1366 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1367 # tmpdir already exists.
1367 # tmpdir already exists.
1368 print "error: temp dir %r already exists" % tmpdir
1368 print "error: temp dir %r already exists" % tmpdir
1369 return 1
1369 return 1
1370
1370
1371 # Automatically removing tmpdir sounds convenient, but could
1371 # Automatically removing tmpdir sounds convenient, but could
1372 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1372 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1373 # or "--tmpdir=$HOME".
1373 # or "--tmpdir=$HOME".
1374 #vlog("# Removing temp dir", tmpdir)
1374 #vlog("# Removing temp dir", tmpdir)
1375 #shutil.rmtree(tmpdir)
1375 #shutil.rmtree(tmpdir)
1376 os.makedirs(tmpdir)
1376 os.makedirs(tmpdir)
1377 else:
1377 else:
1378 d = None
1378 d = None
1379 if os.name == 'nt':
1379 if os.name == 'nt':
1380 # without this, we get the default temp dir location, but
1380 # without this, we get the default temp dir location, but
1381 # in all lowercase, which causes troubles with paths (issue3490)
1381 # in all lowercase, which causes troubles with paths (issue3490)
1382 d = os.getenv('TMP')
1382 d = os.getenv('TMP')
1383 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1383 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1384 runner.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1384 runner.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1385
1385
1386 if options.with_hg:
1386 if options.with_hg:
1387 runner.inst = None
1387 runner.inst = None
1388 runner.bindir = os.path.dirname(os.path.realpath(options.with_hg))
1388 runner.bindir = os.path.dirname(os.path.realpath(options.with_hg))
1389 runner.tmpbindir = os.path.join(runner.hgtmp, 'install', 'bin')
1389 runner.tmpbindir = os.path.join(runner.hgtmp, 'install', 'bin')
1390 os.makedirs(runner.tmpbindir)
1390 os.makedirs(runner.tmpbindir)
1391
1391
1392 # This looks redundant with how Python initializes sys.path from
1392 # This looks redundant with how Python initializes sys.path from
1393 # the location of the script being executed. Needed because the
1393 # the location of the script being executed. Needed because the
1394 # "hg" specified by --with-hg is not the only Python script
1394 # "hg" specified by --with-hg is not the only Python script
1395 # executed in the test suite that needs to import 'mercurial'
1395 # executed in the test suite that needs to import 'mercurial'
1396 # ... which means it's not really redundant at all.
1396 # ... which means it's not really redundant at all.
1397 runner.pythondir = runner.bindir
1397 runner.pythondir = runner.bindir
1398 else:
1398 else:
1399 runner.inst = os.path.join(runner.hgtmp, "install")
1399 runner.inst = os.path.join(runner.hgtmp, "install")
1400 runner.bindir = os.environ["BINDIR"] = os.path.join(runner.inst,
1400 runner.bindir = os.environ["BINDIR"] = os.path.join(runner.inst,
1401 "bin")
1401 "bin")
1402 runner.tmpbindir = runner.bindir
1402 runner.tmpbindir = runner.bindir
1403 runner.pythondir = os.path.join(runner.inst, "lib", "python")
1403 runner.pythondir = os.path.join(runner.inst, "lib", "python")
1404
1404
1405 os.environ["BINDIR"] = runner.bindir
1405 os.environ["BINDIR"] = runner.bindir
1406 os.environ["PYTHON"] = PYTHON
1406 os.environ["PYTHON"] = PYTHON
1407
1407
1408 path = [runner.bindir] + os.environ["PATH"].split(os.pathsep)
1408 path = [runner.bindir] + os.environ["PATH"].split(os.pathsep)
1409 if runner.tmpbindir != runner.bindir:
1409 if runner.tmpbindir != runner.bindir:
1410 path = [runner.tmpbindir] + path
1410 path = [runner.tmpbindir] + path
1411 os.environ["PATH"] = os.pathsep.join(path)
1411 os.environ["PATH"] = os.pathsep.join(path)
1412
1412
1413 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1413 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1414 # can run .../tests/run-tests.py test-foo where test-foo
1414 # can run .../tests/run-tests.py test-foo where test-foo
1415 # adds an extension to HGRC. Also include run-test.py directory to import
1415 # adds an extension to HGRC. Also include run-test.py directory to import
1416 # modules like heredoctest.
1416 # modules like heredoctest.
1417 pypath = [runner.pythondir, runner.testdir,
1417 pypath = [runner.pythondir, runner.testdir,
1418 os.path.abspath(os.path.dirname(__file__))]
1418 os.path.abspath(os.path.dirname(__file__))]
1419 # We have to augment PYTHONPATH, rather than simply replacing
1419 # We have to augment PYTHONPATH, rather than simply replacing
1420 # it, in case external libraries are only available via current
1420 # it, in case external libraries are only available via current
1421 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1421 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1422 # are in /opt/subversion.)
1422 # are in /opt/subversion.)
1423 oldpypath = os.environ.get(IMPL_PATH)
1423 oldpypath = os.environ.get(IMPL_PATH)
1424 if oldpypath:
1424 if oldpypath:
1425 pypath.append(oldpypath)
1425 pypath.append(oldpypath)
1426 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1426 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1427
1427
1428 runner.coveragefile = os.path.join(runner.testdir, ".coverage")
1428 runner.coveragefile = os.path.join(runner.testdir, ".coverage")
1429
1429
1430 vlog("# Using TESTDIR", runner.testdir)
1430 vlog("# Using TESTDIR", runner.testdir)
1431 vlog("# Using HGTMP", runner.hgtmp)
1431 vlog("# Using HGTMP", runner.hgtmp)
1432 vlog("# Using PATH", os.environ["PATH"])
1432 vlog("# Using PATH", os.environ["PATH"])
1433 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1433 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1434
1434
1435 try:
1435 try:
1436 return runtests(runner, tests) or 0
1436 return runner.runtests(tests) or 0
1437 finally:
1437 finally:
1438 time.sleep(.1)
1438 time.sleep(.1)
1439 runner.cleanup()
1439 runner.cleanup()
1440
1440
1441 if __name__ == '__main__':
1441 if __name__ == '__main__':
1442 sys.exit(main(sys.argv[1:]))
1442 sys.exit(main(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now