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