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