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