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