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