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