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