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