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