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