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