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