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