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