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