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