##// END OF EJS Templates
run-tests: refactor port number declaration...
Gregory Szorc -
r21514:59fe123d default
parent child Browse files
Show More
@@ -1,1720 +1,1724 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 import unittest
60 import unittest
61
61
62 processlock = threading.Lock()
62 processlock = threading.Lock()
63
63
64 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
64 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
65 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
65 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
66 # zombies but it's pretty harmless even if we do.
66 # zombies but it's pretty harmless even if we do.
67 if sys.version_info < (2, 5):
67 if sys.version_info < (2, 5):
68 subprocess._cleanup = lambda: None
68 subprocess._cleanup = lambda: None
69
69
70 closefds = os.name == 'posix'
70 closefds = os.name == 'posix'
71 def Popen4(cmd, wd, timeout, env=None):
71 def Popen4(cmd, wd, timeout, env=None):
72 processlock.acquire()
72 processlock.acquire()
73 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
73 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
74 close_fds=closefds,
74 close_fds=closefds,
75 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
75 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
76 stderr=subprocess.STDOUT)
76 stderr=subprocess.STDOUT)
77 processlock.release()
77 processlock.release()
78
78
79 p.fromchild = p.stdout
79 p.fromchild = p.stdout
80 p.tochild = p.stdin
80 p.tochild = p.stdin
81 p.childerr = p.stderr
81 p.childerr = p.stderr
82
82
83 p.timeout = False
83 p.timeout = False
84 if timeout:
84 if timeout:
85 def t():
85 def t():
86 start = time.time()
86 start = time.time()
87 while time.time() - start < timeout and p.returncode is None:
87 while time.time() - start < timeout and p.returncode is None:
88 time.sleep(.1)
88 time.sleep(.1)
89 p.timeout = True
89 p.timeout = True
90 if p.returncode is None:
90 if p.returncode is None:
91 terminate(p)
91 terminate(p)
92 threading.Thread(target=t).start()
92 threading.Thread(target=t).start()
93
93
94 return p
94 return p
95
95
96 PYTHON = sys.executable.replace('\\', '/')
96 PYTHON = sys.executable.replace('\\', '/')
97 IMPL_PATH = 'PYTHONPATH'
97 IMPL_PATH = 'PYTHONPATH'
98 if 'java' in sys.platform:
98 if 'java' in sys.platform:
99 IMPL_PATH = 'JYTHONPATH'
99 IMPL_PATH = 'JYTHONPATH'
100
100
101 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
101 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
102
102
103 defaults = {
103 defaults = {
104 'jobs': ('HGTEST_JOBS', 1),
104 'jobs': ('HGTEST_JOBS', 1),
105 'timeout': ('HGTEST_TIMEOUT', 180),
105 'timeout': ('HGTEST_TIMEOUT', 180),
106 'port': ('HGTEST_PORT', 20059),
106 'port': ('HGTEST_PORT', 20059),
107 'shell': ('HGTEST_SHELL', 'sh'),
107 'shell': ('HGTEST_SHELL', 'sh'),
108 }
108 }
109
109
110 def parselistfiles(files, listtype, warn=True):
110 def parselistfiles(files, listtype, warn=True):
111 entries = dict()
111 entries = dict()
112 for filename in files:
112 for filename in files:
113 try:
113 try:
114 path = os.path.expanduser(os.path.expandvars(filename))
114 path = os.path.expanduser(os.path.expandvars(filename))
115 f = open(path, "r")
115 f = open(path, "r")
116 except IOError, err:
116 except IOError, err:
117 if err.errno != errno.ENOENT:
117 if err.errno != errno.ENOENT:
118 raise
118 raise
119 if warn:
119 if warn:
120 print "warning: no such %s file: %s" % (listtype, filename)
120 print "warning: no such %s file: %s" % (listtype, filename)
121 continue
121 continue
122
122
123 for line in f.readlines():
123 for line in f.readlines():
124 line = line.split('#', 1)[0].strip()
124 line = line.split('#', 1)[0].strip()
125 if line:
125 if line:
126 entries[line] = filename
126 entries[line] = filename
127
127
128 f.close()
128 f.close()
129 return entries
129 return entries
130
130
131 def getparser():
131 def getparser():
132 """Obtain the OptionParser used by the CLI."""
132 """Obtain the OptionParser used by the CLI."""
133 parser = optparse.OptionParser("%prog [options] [tests]")
133 parser = optparse.OptionParser("%prog [options] [tests]")
134
134
135 # keep these sorted
135 # keep these sorted
136 parser.add_option("--blacklist", action="append",
136 parser.add_option("--blacklist", action="append",
137 help="skip tests listed in the specified blacklist file")
137 help="skip tests listed in the specified blacklist file")
138 parser.add_option("--whitelist", action="append",
138 parser.add_option("--whitelist", action="append",
139 help="always run tests listed in the specified whitelist file")
139 help="always run tests listed in the specified whitelist file")
140 parser.add_option("--changed", type="string",
140 parser.add_option("--changed", type="string",
141 help="run tests that are changed in parent rev or working directory")
141 help="run tests that are changed in parent rev or working directory")
142 parser.add_option("-C", "--annotate", action="store_true",
142 parser.add_option("-C", "--annotate", action="store_true",
143 help="output files annotated with coverage")
143 help="output files annotated with coverage")
144 parser.add_option("-c", "--cover", action="store_true",
144 parser.add_option("-c", "--cover", action="store_true",
145 help="print a test coverage report")
145 help="print a test coverage report")
146 parser.add_option("-d", "--debug", action="store_true",
146 parser.add_option("-d", "--debug", action="store_true",
147 help="debug mode: write output of test scripts to console"
147 help="debug mode: write output of test scripts to console"
148 " rather than capturing and diffing it (disables timeout)")
148 " rather than capturing and diffing it (disables timeout)")
149 parser.add_option("-f", "--first", action="store_true",
149 parser.add_option("-f", "--first", action="store_true",
150 help="exit on the first test failure")
150 help="exit on the first test failure")
151 parser.add_option("-H", "--htmlcov", action="store_true",
151 parser.add_option("-H", "--htmlcov", action="store_true",
152 help="create an HTML report of the coverage of the files")
152 help="create an HTML report of the coverage of the files")
153 parser.add_option("-i", "--interactive", action="store_true",
153 parser.add_option("-i", "--interactive", action="store_true",
154 help="prompt to accept changed output")
154 help="prompt to accept changed output")
155 parser.add_option("-j", "--jobs", type="int",
155 parser.add_option("-j", "--jobs", type="int",
156 help="number of jobs to run in parallel"
156 help="number of jobs to run in parallel"
157 " (default: $%s or %d)" % defaults['jobs'])
157 " (default: $%s or %d)" % defaults['jobs'])
158 parser.add_option("--keep-tmpdir", action="store_true",
158 parser.add_option("--keep-tmpdir", action="store_true",
159 help="keep temporary directory after running tests")
159 help="keep temporary directory after running tests")
160 parser.add_option("-k", "--keywords",
160 parser.add_option("-k", "--keywords",
161 help="run tests matching keywords")
161 help="run tests matching keywords")
162 parser.add_option("-l", "--local", action="store_true",
162 parser.add_option("-l", "--local", action="store_true",
163 help="shortcut for --with-hg=<testdir>/../hg")
163 help="shortcut for --with-hg=<testdir>/../hg")
164 parser.add_option("--loop", action="store_true",
164 parser.add_option("--loop", action="store_true",
165 help="loop tests repeatedly")
165 help="loop tests repeatedly")
166 parser.add_option("-n", "--nodiff", action="store_true",
166 parser.add_option("-n", "--nodiff", action="store_true",
167 help="skip showing test changes")
167 help="skip showing test changes")
168 parser.add_option("-p", "--port", type="int",
168 parser.add_option("-p", "--port", type="int",
169 help="port on which servers should listen"
169 help="port on which servers should listen"
170 " (default: $%s or %d)" % defaults['port'])
170 " (default: $%s or %d)" % defaults['port'])
171 parser.add_option("--compiler", type="string",
171 parser.add_option("--compiler", type="string",
172 help="compiler to build with")
172 help="compiler to build with")
173 parser.add_option("--pure", action="store_true",
173 parser.add_option("--pure", action="store_true",
174 help="use pure Python code instead of C extensions")
174 help="use pure Python code instead of C extensions")
175 parser.add_option("-R", "--restart", action="store_true",
175 parser.add_option("-R", "--restart", action="store_true",
176 help="restart at last error")
176 help="restart at last error")
177 parser.add_option("-r", "--retest", action="store_true",
177 parser.add_option("-r", "--retest", action="store_true",
178 help="retest failed tests")
178 help="retest failed tests")
179 parser.add_option("-S", "--noskips", action="store_true",
179 parser.add_option("-S", "--noskips", action="store_true",
180 help="don't report skip tests verbosely")
180 help="don't report skip tests verbosely")
181 parser.add_option("--shell", type="string",
181 parser.add_option("--shell", type="string",
182 help="shell to use (default: $%s or %s)" % defaults['shell'])
182 help="shell to use (default: $%s or %s)" % defaults['shell'])
183 parser.add_option("-t", "--timeout", type="int",
183 parser.add_option("-t", "--timeout", type="int",
184 help="kill errant tests after TIMEOUT seconds"
184 help="kill errant tests after TIMEOUT seconds"
185 " (default: $%s or %d)" % defaults['timeout'])
185 " (default: $%s or %d)" % defaults['timeout'])
186 parser.add_option("--time", action="store_true",
186 parser.add_option("--time", action="store_true",
187 help="time how long each test takes")
187 help="time how long each test takes")
188 parser.add_option("--tmpdir", type="string",
188 parser.add_option("--tmpdir", type="string",
189 help="run tests in the given temporary directory"
189 help="run tests in the given temporary directory"
190 " (implies --keep-tmpdir)")
190 " (implies --keep-tmpdir)")
191 parser.add_option("-v", "--verbose", action="store_true",
191 parser.add_option("-v", "--verbose", action="store_true",
192 help="output verbose messages")
192 help="output verbose messages")
193 parser.add_option("--view", type="string",
193 parser.add_option("--view", type="string",
194 help="external diff viewer")
194 help="external diff viewer")
195 parser.add_option("--with-hg", type="string",
195 parser.add_option("--with-hg", type="string",
196 metavar="HG",
196 metavar="HG",
197 help="test using specified hg script rather than a "
197 help="test using specified hg script rather than a "
198 "temporary installation")
198 "temporary installation")
199 parser.add_option("-3", "--py3k-warnings", action="store_true",
199 parser.add_option("-3", "--py3k-warnings", action="store_true",
200 help="enable Py3k warnings on Python 2.6+")
200 help="enable Py3k warnings on Python 2.6+")
201 parser.add_option('--extra-config-opt', action="append",
201 parser.add_option('--extra-config-opt', action="append",
202 help='set the given config opt in the test hgrc')
202 help='set the given config opt in the test hgrc')
203 parser.add_option('--random', action="store_true",
203 parser.add_option('--random', action="store_true",
204 help='run tests in random order')
204 help='run tests in random order')
205
205
206 for option, (envvar, default) in defaults.items():
206 for option, (envvar, default) in defaults.items():
207 defaults[option] = type(default)(os.environ.get(envvar, default))
207 defaults[option] = type(default)(os.environ.get(envvar, default))
208 parser.set_defaults(**defaults)
208 parser.set_defaults(**defaults)
209
209
210 return parser
210 return parser
211
211
212 def parseargs(args, parser):
212 def parseargs(args, parser):
213 """Parse arguments with our OptionParser and validate results."""
213 """Parse arguments with our OptionParser and validate results."""
214 (options, args) = parser.parse_args(args)
214 (options, args) = parser.parse_args(args)
215
215
216 # jython is always pure
216 # jython is always pure
217 if 'java' in sys.platform or '__pypy__' in sys.modules:
217 if 'java' in sys.platform or '__pypy__' in sys.modules:
218 options.pure = True
218 options.pure = True
219
219
220 if options.with_hg:
220 if options.with_hg:
221 options.with_hg = os.path.expanduser(options.with_hg)
221 options.with_hg = os.path.expanduser(options.with_hg)
222 if not (os.path.isfile(options.with_hg) and
222 if not (os.path.isfile(options.with_hg) and
223 os.access(options.with_hg, os.X_OK)):
223 os.access(options.with_hg, os.X_OK)):
224 parser.error('--with-hg must specify an executable hg script')
224 parser.error('--with-hg must specify an executable hg script')
225 if not os.path.basename(options.with_hg) == 'hg':
225 if not os.path.basename(options.with_hg) == 'hg':
226 sys.stderr.write('warning: --with-hg should specify an hg script\n')
226 sys.stderr.write('warning: --with-hg should specify an hg script\n')
227 if options.local:
227 if options.local:
228 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
228 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
229 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
229 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
230 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
230 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
231 parser.error('--local specified, but %r not found or not executable'
231 parser.error('--local specified, but %r not found or not executable'
232 % hgbin)
232 % hgbin)
233 options.with_hg = hgbin
233 options.with_hg = hgbin
234
234
235 options.anycoverage = options.cover or options.annotate or options.htmlcov
235 options.anycoverage = options.cover or options.annotate or options.htmlcov
236 if options.anycoverage:
236 if options.anycoverage:
237 try:
237 try:
238 import coverage
238 import coverage
239 covver = version.StrictVersion(coverage.__version__).version
239 covver = version.StrictVersion(coverage.__version__).version
240 if covver < (3, 3):
240 if covver < (3, 3):
241 parser.error('coverage options require coverage 3.3 or later')
241 parser.error('coverage options require coverage 3.3 or later')
242 except ImportError:
242 except ImportError:
243 parser.error('coverage options now require the coverage package')
243 parser.error('coverage options now require the coverage package')
244
244
245 if options.anycoverage and options.local:
245 if options.anycoverage and options.local:
246 # this needs some path mangling somewhere, I guess
246 # this needs some path mangling somewhere, I guess
247 parser.error("sorry, coverage options do not work when --local "
247 parser.error("sorry, coverage options do not work when --local "
248 "is specified")
248 "is specified")
249
249
250 global verbose
250 global verbose
251 if options.verbose:
251 if options.verbose:
252 verbose = ''
252 verbose = ''
253
253
254 if options.tmpdir:
254 if options.tmpdir:
255 options.tmpdir = os.path.expanduser(options.tmpdir)
255 options.tmpdir = os.path.expanduser(options.tmpdir)
256
256
257 if options.jobs < 1:
257 if options.jobs < 1:
258 parser.error('--jobs must be positive')
258 parser.error('--jobs must be positive')
259 if options.interactive and options.debug:
259 if options.interactive and options.debug:
260 parser.error("-i/--interactive and -d/--debug are incompatible")
260 parser.error("-i/--interactive and -d/--debug are incompatible")
261 if options.debug:
261 if options.debug:
262 if options.timeout != defaults['timeout']:
262 if options.timeout != defaults['timeout']:
263 sys.stderr.write(
263 sys.stderr.write(
264 'warning: --timeout option ignored with --debug\n')
264 'warning: --timeout option ignored with --debug\n')
265 options.timeout = 0
265 options.timeout = 0
266 if options.py3k_warnings:
266 if options.py3k_warnings:
267 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
267 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
268 parser.error('--py3k-warnings can only be used on Python 2.6+')
268 parser.error('--py3k-warnings can only be used on Python 2.6+')
269 if options.blacklist:
269 if options.blacklist:
270 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
270 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
271 if options.whitelist:
271 if options.whitelist:
272 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
272 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
273 else:
273 else:
274 options.whitelisted = {}
274 options.whitelisted = {}
275
275
276 return (options, args)
276 return (options, args)
277
277
278 def rename(src, dst):
278 def rename(src, dst):
279 """Like os.rename(), trade atomicity and opened files friendliness
279 """Like os.rename(), trade atomicity and opened files friendliness
280 for existing destination support.
280 for existing destination support.
281 """
281 """
282 shutil.copy(src, dst)
282 shutil.copy(src, dst)
283 os.remove(src)
283 os.remove(src)
284
284
285 def showdiff(expected, output, ref, err):
285 def showdiff(expected, output, ref, err):
286 print
286 print
287 servefail = False
287 servefail = False
288 for line in difflib.unified_diff(expected, output, ref, err):
288 for line in difflib.unified_diff(expected, output, ref, err):
289 sys.stdout.write(line)
289 sys.stdout.write(line)
290 if not servefail and line.startswith(
290 if not servefail and line.startswith(
291 '+ abort: child process failed to start'):
291 '+ abort: child process failed to start'):
292 servefail = True
292 servefail = True
293 return {'servefail': servefail}
293 return {'servefail': servefail}
294
294
295
295
296 verbose = False
296 verbose = False
297 def vlog(*msg):
297 def vlog(*msg):
298 if verbose is not False:
298 if verbose is not False:
299 iolock.acquire()
299 iolock.acquire()
300 if verbose:
300 if verbose:
301 print verbose,
301 print verbose,
302 for m in msg:
302 for m in msg:
303 print m,
303 print m,
304 print
304 print
305 sys.stdout.flush()
305 sys.stdout.flush()
306 iolock.release()
306 iolock.release()
307
307
308 def log(*msg):
308 def log(*msg):
309 iolock.acquire()
309 iolock.acquire()
310 if verbose:
310 if verbose:
311 print verbose,
311 print verbose,
312 for m in msg:
312 for m in msg:
313 print m,
313 print m,
314 print
314 print
315 sys.stdout.flush()
315 sys.stdout.flush()
316 iolock.release()
316 iolock.release()
317
317
318 def terminate(proc):
318 def terminate(proc):
319 """Terminate subprocess (with fallback for Python versions < 2.6)"""
319 """Terminate subprocess (with fallback for Python versions < 2.6)"""
320 vlog('# Terminating process %d' % proc.pid)
320 vlog('# Terminating process %d' % proc.pid)
321 try:
321 try:
322 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
322 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
323 except OSError:
323 except OSError:
324 pass
324 pass
325
325
326 def killdaemons(pidfile):
326 def killdaemons(pidfile):
327 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
327 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
328 logfn=vlog)
328 logfn=vlog)
329
329
330 class Test(unittest.TestCase):
330 class Test(unittest.TestCase):
331 """Encapsulates a single, runnable test.
331 """Encapsulates a single, runnable test.
332
332
333 While this class conforms to the unittest.TestCase API, it differs in that
333 While this class conforms to the unittest.TestCase API, it differs in that
334 instances need to be instantiated manually. (Typically, unittest.TestCase
334 instances need to be instantiated manually. (Typically, unittest.TestCase
335 classes are instantiated automatically by scanning modules.)
335 classes are instantiated automatically by scanning modules.)
336 """
336 """
337
337
338 # Status code reserved for skipped tests (used by hghave).
338 # Status code reserved for skipped tests (used by hghave).
339 SKIPPED_STATUS = 80
339 SKIPPED_STATUS = 80
340
340
341 def __init__(self, options, path, count, tmpdir, abort, keeptmpdir=False,
341 def __init__(self, options, path, tmpdir, abort, keeptmpdir=False,
342 debug=False, nodiff=False, diffviewer=None,
342 debug=False, nodiff=False, diffviewer=None,
343 interactive=False, timeout=defaults['timeout']):
343 interactive=False, timeout=defaults['timeout'],
344 startport=defaults['port']):
344 """Create a test from parameters.
345 """Create a test from parameters.
345
346
346 options are parsed command line options that control test execution.
347 options are parsed command line options that control test execution.
347
348
348 path is the full path to the file defining the test.
349 path is the full path to the file defining the test.
349
350
350 count is an identifier used to denote this test instance.
351
352 tmpdir is the main temporary directory to use for this test.
351 tmpdir is the main temporary directory to use for this test.
353
352
354 abort is a flag that turns to True if test execution should be aborted.
353 abort is a flag that turns to True if test execution should be aborted.
355 It is consulted periodically during the execution of tests.
354 It is consulted periodically during the execution of tests.
356
355
357 keeptmpdir determines whether to keep the test's temporary directory
356 keeptmpdir determines whether to keep the test's temporary directory
358 after execution. It defaults to removal (False).
357 after execution. It defaults to removal (False).
359
358
360 debug mode will make the test execute verbosely, with unfiltered
359 debug mode will make the test execute verbosely, with unfiltered
361 output.
360 output.
362
361
363 nodiff will suppress the printing of a diff when output changes.
362 nodiff will suppress the printing of a diff when output changes.
364
363
365 diffviewer is the program that should be used to display diffs. Only
364 diffviewer is the program that should be used to display diffs. Only
366 used when output changes.
365 used when output changes.
367
366
368 interactive controls whether the test will run interactively.
367 interactive controls whether the test will run interactively.
369
368
370 timeout controls the maximum run time of the test. It is ignored when
369 timeout controls the maximum run time of the test. It is ignored when
371 debug is True.
370 debug is True.
371
372 startport controls the starting port number to use for this test. Each
373 test will reserve 3 port numbers for execution. It is the caller's
374 responsibility to allocate a non-overlapping port range to Test
375 instances.
372 """
376 """
373
377
374 self.path = path
378 self.path = path
375 self.name = os.path.basename(path)
379 self.name = os.path.basename(path)
376 self._testdir = os.path.dirname(path)
380 self._testdir = os.path.dirname(path)
377 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
381 self.errpath = os.path.join(self._testdir, '%s.err' % self.name)
378
382
379 self._options = options
383 self._options = options
380 self._count = count
381 self._threadtmp = tmpdir
384 self._threadtmp = tmpdir
382 self._abort = abort
385 self._abort = abort
383 self._keeptmpdir = keeptmpdir
386 self._keeptmpdir = keeptmpdir
384 self._debug = debug
387 self._debug = debug
385 self._nodiff = nodiff
388 self._nodiff = nodiff
386 self._diffviewer = diffviewer
389 self._diffviewer = diffviewer
387 self._interactive = interactive
390 self._interactive = interactive
388 self._timeout = timeout
391 self._timeout = timeout
392 self._startport = startport
389 self._daemonpids = []
393 self._daemonpids = []
390
394
391 self._finished = None
395 self._finished = None
392 self._ret = None
396 self._ret = None
393 self._out = None
397 self._out = None
394 self._skipped = None
398 self._skipped = None
395 self._testtmp = None
399 self._testtmp = None
396
400
397 # If we're not in --debug mode and reference output file exists,
401 # If we're not in --debug mode and reference output file exists,
398 # check test output against it.
402 # check test output against it.
399 if debug:
403 if debug:
400 self._refout = None # to match "out is None"
404 self._refout = None # to match "out is None"
401 elif os.path.exists(self._refpath):
405 elif os.path.exists(self._refpath):
402 f = open(self._refpath, 'r')
406 f = open(self._refpath, 'r')
403 self._refout = f.read().splitlines(True)
407 self._refout = f.read().splitlines(True)
404 f.close()
408 f.close()
405 else:
409 else:
406 self._refout = []
410 self._refout = []
407
411
408 def __str__(self):
412 def __str__(self):
409 return self.name
413 return self.name
410
414
411 def shortDescription(self):
415 def shortDescription(self):
412 return self.name
416 return self.name
413
417
414 def setUp(self):
418 def setUp(self):
415 """Tasks to perform before run()."""
419 """Tasks to perform before run()."""
416 self._finished = False
420 self._finished = False
417 self._ret = None
421 self._ret = None
418 self._out = None
422 self._out = None
419 self._skipped = None
423 self._skipped = None
420
424
421 try:
425 try:
422 os.mkdir(self._threadtmp)
426 os.mkdir(self._threadtmp)
423 except OSError, e:
427 except OSError, e:
424 if e.errno != errno.EEXIST:
428 if e.errno != errno.EEXIST:
425 raise
429 raise
426
430
427 self._testtmp = os.path.join(self._threadtmp,
431 self._testtmp = os.path.join(self._threadtmp,
428 os.path.basename(self.path))
432 os.path.basename(self.path))
429 os.mkdir(self._testtmp)
433 os.mkdir(self._testtmp)
430
434
431 # Remove any previous output files.
435 # Remove any previous output files.
432 if os.path.exists(self.errpath):
436 if os.path.exists(self.errpath):
433 os.remove(self.errpath)
437 os.remove(self.errpath)
434
438
435 def run(self, result):
439 def run(self, result):
436 result.startTest(self)
440 result.startTest(self)
437 interrupted = False
441 interrupted = False
438 try:
442 try:
439 try:
443 try:
440 self.setUp()
444 self.setUp()
441 except (KeyboardInterrupt, SystemExit):
445 except (KeyboardInterrupt, SystemExit):
442 interrupted = True
446 interrupted = True
443 raise
447 raise
444 except Exception:
448 except Exception:
445 result.addError(self, sys.exc_info())
449 result.addError(self, sys.exc_info())
446 return
450 return
447
451
448 success = False
452 success = False
449 try:
453 try:
450 self.runTest()
454 self.runTest()
451 except KeyboardInterrupt:
455 except KeyboardInterrupt:
452 interrupted = True
456 interrupted = True
453 raise
457 raise
454 except SkipTest, e:
458 except SkipTest, e:
455 result.addSkip(self, str(e))
459 result.addSkip(self, str(e))
456 except IgnoreTest, e:
460 except IgnoreTest, e:
457 result.addIgnore(self, str(e))
461 result.addIgnore(self, str(e))
458 except WarnTest, e:
462 except WarnTest, e:
459 result.addWarn(self, str(e))
463 result.addWarn(self, str(e))
460 except self.failureException, e:
464 except self.failureException, e:
461 # This differs from unittest in that we don't capture
465 # This differs from unittest in that we don't capture
462 # the stack trace. This is for historical reasons and
466 # the stack trace. This is for historical reasons and
463 # this decision could be revisted in the future,
467 # this decision could be revisted in the future,
464 # especially for PythonTest instances.
468 # especially for PythonTest instances.
465 result.addFailure(self, str(e))
469 result.addFailure(self, str(e))
466 except Exception:
470 except Exception:
467 result.addError(self, sys.exc_info())
471 result.addError(self, sys.exc_info())
468 else:
472 else:
469 success = True
473 success = True
470
474
471 try:
475 try:
472 self.tearDown()
476 self.tearDown()
473 except (KeyboardInterrupt, SystemExit):
477 except (KeyboardInterrupt, SystemExit):
474 interrupted = True
478 interrupted = True
475 raise
479 raise
476 except Exception:
480 except Exception:
477 result.addError(self, sys.exc_info())
481 result.addError(self, sys.exc_info())
478 success = False
482 success = False
479
483
480 if success:
484 if success:
481 result.addSuccess(self)
485 result.addSuccess(self)
482 finally:
486 finally:
483 result.stopTest(self, interrupted=interrupted)
487 result.stopTest(self, interrupted=interrupted)
484
488
485 def runTest(self):
489 def runTest(self):
486 """Run this test instance.
490 """Run this test instance.
487
491
488 This will return a tuple describing the result of the test.
492 This will return a tuple describing the result of the test.
489 """
493 """
490 replacements, port = self._getreplacements()
494 replacements = self._getreplacements()
491 env = self._getenv(port)
495 env = self._getenv()
492 self._daemonpids.append(env['DAEMON_PIDS'])
496 self._daemonpids.append(env['DAEMON_PIDS'])
493 self._createhgrc(env['HGRCPATH'])
497 self._createhgrc(env['HGRCPATH'])
494
498
495 vlog('# Test', self.name)
499 vlog('# Test', self.name)
496
500
497 ret, out = self._run(replacements, env)
501 ret, out = self._run(replacements, env)
498 self._finished = True
502 self._finished = True
499 self._ret = ret
503 self._ret = ret
500 self._out = out
504 self._out = out
501
505
502 def describe(ret):
506 def describe(ret):
503 if ret < 0:
507 if ret < 0:
504 return 'killed by signal: %d' % -ret
508 return 'killed by signal: %d' % -ret
505 return 'returned error code %d' % ret
509 return 'returned error code %d' % ret
506
510
507 self._skipped = False
511 self._skipped = False
508
512
509 if ret == self.SKIPPED_STATUS:
513 if ret == self.SKIPPED_STATUS:
510 if out is None: # Debug mode, nothing to parse.
514 if out is None: # Debug mode, nothing to parse.
511 missing = ['unknown']
515 missing = ['unknown']
512 failed = None
516 failed = None
513 else:
517 else:
514 missing, failed = TTest.parsehghaveoutput(out)
518 missing, failed = TTest.parsehghaveoutput(out)
515
519
516 if not missing:
520 if not missing:
517 missing = ['irrelevant']
521 missing = ['irrelevant']
518
522
519 if failed:
523 if failed:
520 self.fail('hg have failed checking for %s' % failed[-1], ret)
524 self.fail('hg have failed checking for %s' % failed[-1], ret)
521 else:
525 else:
522 self._skipped = True
526 self._skipped = True
523 raise SkipTest(missing[-1])
527 raise SkipTest(missing[-1])
524 elif ret == 'timeout':
528 elif ret == 'timeout':
525 self.fail('timed out', ret)
529 self.fail('timed out', ret)
526 elif out != self._refout:
530 elif out != self._refout:
527 info = {}
531 info = {}
528 if not self._nodiff:
532 if not self._nodiff:
529 iolock.acquire()
533 iolock.acquire()
530 if self._diffviewer:
534 if self._diffviewer:
531 os.system("%s %s %s" % (self._diffviewer, self._refpath,
535 os.system("%s %s %s" % (self._diffviewer, self._refpath,
532 self.errpath))
536 self.errpath))
533 else:
537 else:
534 info = showdiff(self._refout, out, self._refpath,
538 info = showdiff(self._refout, out, self._refpath,
535 self.errpath)
539 self.errpath)
536 iolock.release()
540 iolock.release()
537 msg = ''
541 msg = ''
538 if info.get('servefail'):
542 if info.get('servefail'):
539 msg += 'serve failed and '
543 msg += 'serve failed and '
540 if ret:
544 if ret:
541 msg += 'output changed and ' + describe(ret)
545 msg += 'output changed and ' + describe(ret)
542 else:
546 else:
543 msg += 'output changed'
547 msg += 'output changed'
544
548
545 if (ret != 0 or out != self._refout) and not self._skipped \
549 if (ret != 0 or out != self._refout) and not self._skipped \
546 and not self._debug:
550 and not self._debug:
547 f = open(self.errpath, 'wb')
551 f = open(self.errpath, 'wb')
548 for line in out:
552 for line in out:
549 f.write(line)
553 f.write(line)
550 f.close()
554 f.close()
551
555
552 self.fail(msg, ret)
556 self.fail(msg, ret)
553 elif ret:
557 elif ret:
554 self.fail(describe(ret), ret)
558 self.fail(describe(ret), ret)
555
559
556 def tearDown(self):
560 def tearDown(self):
557 """Tasks to perform after run()."""
561 """Tasks to perform after run()."""
558 for entry in self._daemonpids:
562 for entry in self._daemonpids:
559 killdaemons(entry)
563 killdaemons(entry)
560 self._daemonpids = []
564 self._daemonpids = []
561
565
562 if not self._keeptmpdir:
566 if not self._keeptmpdir:
563 shutil.rmtree(self._testtmp, True)
567 shutil.rmtree(self._testtmp, True)
564 shutil.rmtree(self._threadtmp, True)
568 shutil.rmtree(self._threadtmp, True)
565
569
566 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
570 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
567 and not self._debug and self._out:
571 and not self._debug and self._out:
568 f = open(self.errpath, 'wb')
572 f = open(self.errpath, 'wb')
569 for line in self._out:
573 for line in self._out:
570 f.write(line)
574 f.write(line)
571 f.close()
575 f.close()
572
576
573 vlog("# Ret was:", self._ret)
577 vlog("# Ret was:", self._ret)
574
578
575 def _run(self, replacements, env):
579 def _run(self, replacements, env):
576 # This should be implemented in child classes to run tests.
580 # This should be implemented in child classes to run tests.
577 raise SkipTest('unknown test type')
581 raise SkipTest('unknown test type')
578
582
579 def _getreplacements(self):
583 def _getreplacements(self):
580 port = self._options.port + self._count * 3
581 r = [
584 r = [
582 (r':%s\b' % port, ':$HGPORT'),
585 (r':%s\b' % self._startport, ':$HGPORT'),
583 (r':%s\b' % (port + 1), ':$HGPORT1'),
586 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
584 (r':%s\b' % (port + 2), ':$HGPORT2'),
587 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
585 ]
588 ]
586
589
587 if os.name == 'nt':
590 if os.name == 'nt':
588 r.append(
591 r.append(
589 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
592 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
590 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
593 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
591 for c in self._testtmp), '$TESTTMP'))
594 for c in self._testtmp), '$TESTTMP'))
592 else:
595 else:
593 r.append((re.escape(self._testtmp), '$TESTTMP'))
596 r.append((re.escape(self._testtmp), '$TESTTMP'))
594
597
595 return r, port
598 return r
596
599
597 def _getenv(self, port):
600 def _getenv(self):
598 env = os.environ.copy()
601 env = os.environ.copy()
599 env['TESTTMP'] = self._testtmp
602 env['TESTTMP'] = self._testtmp
600 env['HOME'] = self._testtmp
603 env['HOME'] = self._testtmp
601 env["HGPORT"] = str(port)
604 env["HGPORT"] = str(self._startport)
602 env["HGPORT1"] = str(port + 1)
605 env["HGPORT1"] = str(self._startport + 1)
603 env["HGPORT2"] = str(port + 2)
606 env["HGPORT2"] = str(self._startport + 2)
604 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
607 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
605 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
608 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
606 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
609 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
607 env["HGMERGE"] = "internal:merge"
610 env["HGMERGE"] = "internal:merge"
608 env["HGUSER"] = "test"
611 env["HGUSER"] = "test"
609 env["HGENCODING"] = "ascii"
612 env["HGENCODING"] = "ascii"
610 env["HGENCODINGMODE"] = "strict"
613 env["HGENCODINGMODE"] = "strict"
611
614
612 # Reset some environment variables to well-known values so that
615 # Reset some environment variables to well-known values so that
613 # the tests produce repeatable output.
616 # the tests produce repeatable output.
614 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
617 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
615 env['TZ'] = 'GMT'
618 env['TZ'] = 'GMT'
616 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
619 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
617 env['COLUMNS'] = '80'
620 env['COLUMNS'] = '80'
618 env['TERM'] = 'xterm'
621 env['TERM'] = 'xterm'
619
622
620 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
623 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
621 'NO_PROXY').split():
624 'NO_PROXY').split():
622 if k in env:
625 if k in env:
623 del env[k]
626 del env[k]
624
627
625 # unset env related to hooks
628 # unset env related to hooks
626 for k in env.keys():
629 for k in env.keys():
627 if k.startswith('HG_'):
630 if k.startswith('HG_'):
628 del env[k]
631 del env[k]
629
632
630 return env
633 return env
631
634
632 def _createhgrc(self, path):
635 def _createhgrc(self, path):
633 # create a fresh hgrc
636 # create a fresh hgrc
634 hgrc = open(path, 'w')
637 hgrc = open(path, 'w')
635 hgrc.write('[ui]\n')
638 hgrc.write('[ui]\n')
636 hgrc.write('slash = True\n')
639 hgrc.write('slash = True\n')
637 hgrc.write('interactive = False\n')
640 hgrc.write('interactive = False\n')
638 hgrc.write('[defaults]\n')
641 hgrc.write('[defaults]\n')
639 hgrc.write('backout = -d "0 0"\n')
642 hgrc.write('backout = -d "0 0"\n')
640 hgrc.write('commit = -d "0 0"\n')
643 hgrc.write('commit = -d "0 0"\n')
641 hgrc.write('shelve = --date "0 0"\n')
644 hgrc.write('shelve = --date "0 0"\n')
642 hgrc.write('tag = -d "0 0"\n')
645 hgrc.write('tag = -d "0 0"\n')
643 if self._options.extra_config_opt:
646 if self._options.extra_config_opt:
644 for opt in self._options.extra_config_opt:
647 for opt in self._options.extra_config_opt:
645 section, key = opt.split('.', 1)
648 section, key = opt.split('.', 1)
646 assert '=' in key, ('extra config opt %s must '
649 assert '=' in key, ('extra config opt %s must '
647 'have an = for assignment' % opt)
650 'have an = for assignment' % opt)
648 hgrc.write('[%s]\n%s\n' % (section, key))
651 hgrc.write('[%s]\n%s\n' % (section, key))
649 hgrc.close()
652 hgrc.close()
650
653
651 def fail(self, msg, ret):
654 def fail(self, msg, ret):
652 warned = ret is False
655 warned = ret is False
653 if not self._nodiff:
656 if not self._nodiff:
654 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self.name,
657 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', self.name,
655 msg))
658 msg))
656 if (not ret and self._interactive and
659 if (not ret and self._interactive and
657 os.path.exists(self.errpath)):
660 os.path.exists(self.errpath)):
658 iolock.acquire()
661 iolock.acquire()
659 print 'Accept this change? [n] ',
662 print 'Accept this change? [n] ',
660 answer = sys.stdin.readline().strip()
663 answer = sys.stdin.readline().strip()
661 iolock.release()
664 iolock.release()
662 if answer.lower() in ('y', 'yes'):
665 if answer.lower() in ('y', 'yes'):
663 if self.name.endswith('.t'):
666 if self.name.endswith('.t'):
664 rename(self.errpath, self.path)
667 rename(self.errpath, self.path)
665 else:
668 else:
666 rename(self.errpath, '%s.out' % self.path)
669 rename(self.errpath, '%s.out' % self.path)
667
670
668 return '.', self.name, ''
671 return '.', self.name, ''
669
672
670 if warned:
673 if warned:
671 raise WarnTest(msg)
674 raise WarnTest(msg)
672 else:
675 else:
673 # unittest differentiates between errored and failed.
676 # unittest differentiates between errored and failed.
674 # Failed is denoted by AssertionError (by default at least).
677 # Failed is denoted by AssertionError (by default at least).
675 raise AssertionError(msg)
678 raise AssertionError(msg)
676
679
677 class PythonTest(Test):
680 class PythonTest(Test):
678 """A Python-based test."""
681 """A Python-based test."""
679
682
680 @property
683 @property
681 def _refpath(self):
684 def _refpath(self):
682 return os.path.join(self._testdir, '%s.out' % self.name)
685 return os.path.join(self._testdir, '%s.out' % self.name)
683
686
684 def _run(self, replacements, env):
687 def _run(self, replacements, env):
685 py3kswitch = self._options.py3k_warnings and ' -3' or ''
688 py3kswitch = self._options.py3k_warnings and ' -3' or ''
686 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
689 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, self.path)
687 vlog("# Running", cmd)
690 vlog("# Running", cmd)
688 if os.name == 'nt':
691 if os.name == 'nt':
689 replacements.append((r'\r\n', '\n'))
692 replacements.append((r'\r\n', '\n'))
690 return run(cmd, self._testtmp, replacements, env, self._abort,
693 return run(cmd, self._testtmp, replacements, env, self._abort,
691 debug=self._debug, timeout=self._timeout)
694 debug=self._debug, timeout=self._timeout)
692
695
693 class TTest(Test):
696 class TTest(Test):
694 """A "t test" is a test backed by a .t file."""
697 """A "t test" is a test backed by a .t file."""
695
698
696 SKIPPED_PREFIX = 'skipped: '
699 SKIPPED_PREFIX = 'skipped: '
697 FAILED_PREFIX = 'hghave check failed: '
700 FAILED_PREFIX = 'hghave check failed: '
698 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
701 NEEDESCAPE = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
699
702
700 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
703 ESCAPESUB = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
701 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
704 ESCAPEMAP = dict((chr(i), r'\x%02x' % i) for i in range(256)).update(
702 {'\\': '\\\\', '\r': r'\r'})
705 {'\\': '\\\\', '\r': r'\r'})
703
706
704 @property
707 @property
705 def _refpath(self):
708 def _refpath(self):
706 return os.path.join(self._testdir, self.name)
709 return os.path.join(self._testdir, self.name)
707
710
708 def _run(self, replacements, env):
711 def _run(self, replacements, env):
709 f = open(self.path)
712 f = open(self.path)
710 lines = f.readlines()
713 lines = f.readlines()
711 f.close()
714 f.close()
712
715
713 salt, script, after, expected = self._parsetest(lines)
716 salt, script, after, expected = self._parsetest(lines)
714
717
715 # Write out the generated script.
718 # Write out the generated script.
716 fname = '%s.sh' % self._testtmp
719 fname = '%s.sh' % self._testtmp
717 f = open(fname, 'w')
720 f = open(fname, 'w')
718 for l in script:
721 for l in script:
719 f.write(l)
722 f.write(l)
720 f.close()
723 f.close()
721
724
722 cmd = '%s "%s"' % (self._options.shell, fname)
725 cmd = '%s "%s"' % (self._options.shell, fname)
723 vlog("# Running", cmd)
726 vlog("# Running", cmd)
724
727
725 exitcode, output = run(cmd, self._testtmp, replacements, env,
728 exitcode, output = run(cmd, self._testtmp, replacements, env,
726 self._abort, debug=self._debug,
729 self._abort, debug=self._debug,
727 timeout=self._timeout)
730 timeout=self._timeout)
728 # Do not merge output if skipped. Return hghave message instead.
731 # Do not merge output if skipped. Return hghave message instead.
729 # Similarly, with --debug, output is None.
732 # Similarly, with --debug, output is None.
730 if exitcode == self.SKIPPED_STATUS or output is None:
733 if exitcode == self.SKIPPED_STATUS or output is None:
731 return exitcode, output
734 return exitcode, output
732
735
733 return self._processoutput(exitcode, output, salt, after, expected)
736 return self._processoutput(exitcode, output, salt, after, expected)
734
737
735 def _hghave(self, reqs):
738 def _hghave(self, reqs):
736 # TODO do something smarter when all other uses of hghave are gone.
739 # TODO do something smarter when all other uses of hghave are gone.
737 tdir = self._testdir.replace('\\', '/')
740 tdir = self._testdir.replace('\\', '/')
738 proc = Popen4('%s -c "%s/hghave %s"' %
741 proc = Popen4('%s -c "%s/hghave %s"' %
739 (self._options.shell, tdir, ' '.join(reqs)),
742 (self._options.shell, tdir, ' '.join(reqs)),
740 self._testtmp, 0)
743 self._testtmp, 0)
741 stdout, stderr = proc.communicate()
744 stdout, stderr = proc.communicate()
742 ret = proc.wait()
745 ret = proc.wait()
743 if wifexited(ret):
746 if wifexited(ret):
744 ret = os.WEXITSTATUS(ret)
747 ret = os.WEXITSTATUS(ret)
745 if ret == 2:
748 if ret == 2:
746 print stdout
749 print stdout
747 sys.exit(1)
750 sys.exit(1)
748
751
749 return ret == 0
752 return ret == 0
750
753
751 def _parsetest(self, lines):
754 def _parsetest(self, lines):
752 # We generate a shell script which outputs unique markers to line
755 # We generate a shell script which outputs unique markers to line
753 # up script results with our source. These markers include input
756 # up script results with our source. These markers include input
754 # line number and the last return code.
757 # line number and the last return code.
755 salt = "SALT" + str(time.time())
758 salt = "SALT" + str(time.time())
756 def addsalt(line, inpython):
759 def addsalt(line, inpython):
757 if inpython:
760 if inpython:
758 script.append('%s %d 0\n' % (salt, line))
761 script.append('%s %d 0\n' % (salt, line))
759 else:
762 else:
760 script.append('echo %s %s $?\n' % (salt, line))
763 script.append('echo %s %s $?\n' % (salt, line))
761
764
762 script = []
765 script = []
763
766
764 # After we run the shell script, we re-unify the script output
767 # After we run the shell script, we re-unify the script output
765 # with non-active parts of the source, with synchronization by our
768 # with non-active parts of the source, with synchronization by our
766 # SALT line number markers. The after table contains the non-active
769 # SALT line number markers. The after table contains the non-active
767 # components, ordered by line number.
770 # components, ordered by line number.
768 after = {}
771 after = {}
769
772
770 # Expected shell script output.
773 # Expected shell script output.
771 expected = {}
774 expected = {}
772
775
773 pos = prepos = -1
776 pos = prepos = -1
774
777
775 # True or False when in a true or false conditional section
778 # True or False when in a true or false conditional section
776 skipping = None
779 skipping = None
777
780
778 # We keep track of whether or not we're in a Python block so we
781 # We keep track of whether or not we're in a Python block so we
779 # can generate the surrounding doctest magic.
782 # can generate the surrounding doctest magic.
780 inpython = False
783 inpython = False
781
784
782 if self._debug:
785 if self._debug:
783 script.append('set -x\n')
786 script.append('set -x\n')
784 if os.getenv('MSYSTEM'):
787 if os.getenv('MSYSTEM'):
785 script.append('alias pwd="pwd -W"\n')
788 script.append('alias pwd="pwd -W"\n')
786
789
787 for n, l in enumerate(lines):
790 for n, l in enumerate(lines):
788 if not l.endswith('\n'):
791 if not l.endswith('\n'):
789 l += '\n'
792 l += '\n'
790 if l.startswith('#if'):
793 if l.startswith('#if'):
791 lsplit = l.split()
794 lsplit = l.split()
792 if len(lsplit) < 2 or lsplit[0] != '#if':
795 if len(lsplit) < 2 or lsplit[0] != '#if':
793 after.setdefault(pos, []).append(' !!! invalid #if\n')
796 after.setdefault(pos, []).append(' !!! invalid #if\n')
794 if skipping is not None:
797 if skipping is not None:
795 after.setdefault(pos, []).append(' !!! nested #if\n')
798 after.setdefault(pos, []).append(' !!! nested #if\n')
796 skipping = not self._hghave(lsplit[1:])
799 skipping = not self._hghave(lsplit[1:])
797 after.setdefault(pos, []).append(l)
800 after.setdefault(pos, []).append(l)
798 elif l.startswith('#else'):
801 elif l.startswith('#else'):
799 if skipping is None:
802 if skipping is None:
800 after.setdefault(pos, []).append(' !!! missing #if\n')
803 after.setdefault(pos, []).append(' !!! missing #if\n')
801 skipping = not skipping
804 skipping = not skipping
802 after.setdefault(pos, []).append(l)
805 after.setdefault(pos, []).append(l)
803 elif l.startswith('#endif'):
806 elif l.startswith('#endif'):
804 if skipping is None:
807 if skipping is None:
805 after.setdefault(pos, []).append(' !!! missing #if\n')
808 after.setdefault(pos, []).append(' !!! missing #if\n')
806 skipping = None
809 skipping = None
807 after.setdefault(pos, []).append(l)
810 after.setdefault(pos, []).append(l)
808 elif skipping:
811 elif skipping:
809 after.setdefault(pos, []).append(l)
812 after.setdefault(pos, []).append(l)
810 elif l.startswith(' >>> '): # python inlines
813 elif l.startswith(' >>> '): # python inlines
811 after.setdefault(pos, []).append(l)
814 after.setdefault(pos, []).append(l)
812 prepos = pos
815 prepos = pos
813 pos = n
816 pos = n
814 if not inpython:
817 if not inpython:
815 # We've just entered a Python block. Add the header.
818 # We've just entered a Python block. Add the header.
816 inpython = True
819 inpython = True
817 addsalt(prepos, False) # Make sure we report the exit code.
820 addsalt(prepos, False) # Make sure we report the exit code.
818 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
821 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
819 addsalt(n, True)
822 addsalt(n, True)
820 script.append(l[2:])
823 script.append(l[2:])
821 elif l.startswith(' ... '): # python inlines
824 elif l.startswith(' ... '): # python inlines
822 after.setdefault(prepos, []).append(l)
825 after.setdefault(prepos, []).append(l)
823 script.append(l[2:])
826 script.append(l[2:])
824 elif l.startswith(' $ '): # commands
827 elif l.startswith(' $ '): # commands
825 if inpython:
828 if inpython:
826 script.append('EOF\n')
829 script.append('EOF\n')
827 inpython = False
830 inpython = False
828 after.setdefault(pos, []).append(l)
831 after.setdefault(pos, []).append(l)
829 prepos = pos
832 prepos = pos
830 pos = n
833 pos = n
831 addsalt(n, False)
834 addsalt(n, False)
832 cmd = l[4:].split()
835 cmd = l[4:].split()
833 if len(cmd) == 2 and cmd[0] == 'cd':
836 if len(cmd) == 2 and cmd[0] == 'cd':
834 l = ' $ cd %s || exit 1\n' % cmd[1]
837 l = ' $ cd %s || exit 1\n' % cmd[1]
835 script.append(l[4:])
838 script.append(l[4:])
836 elif l.startswith(' > '): # continuations
839 elif l.startswith(' > '): # continuations
837 after.setdefault(prepos, []).append(l)
840 after.setdefault(prepos, []).append(l)
838 script.append(l[4:])
841 script.append(l[4:])
839 elif l.startswith(' '): # results
842 elif l.startswith(' '): # results
840 # Queue up a list of expected results.
843 # Queue up a list of expected results.
841 expected.setdefault(pos, []).append(l[2:])
844 expected.setdefault(pos, []).append(l[2:])
842 else:
845 else:
843 if inpython:
846 if inpython:
844 script.append('EOF\n')
847 script.append('EOF\n')
845 inpython = False
848 inpython = False
846 # Non-command/result. Queue up for merged output.
849 # Non-command/result. Queue up for merged output.
847 after.setdefault(pos, []).append(l)
850 after.setdefault(pos, []).append(l)
848
851
849 if inpython:
852 if inpython:
850 script.append('EOF\n')
853 script.append('EOF\n')
851 if skipping is not None:
854 if skipping is not None:
852 after.setdefault(pos, []).append(' !!! missing #endif\n')
855 after.setdefault(pos, []).append(' !!! missing #endif\n')
853 addsalt(n + 1, False)
856 addsalt(n + 1, False)
854
857
855 return salt, script, after, expected
858 return salt, script, after, expected
856
859
857 def _processoutput(self, exitcode, output, salt, after, expected):
860 def _processoutput(self, exitcode, output, salt, after, expected):
858 # Merge the script output back into a unified test.
861 # Merge the script output back into a unified test.
859 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
862 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
860 if exitcode != 0:
863 if exitcode != 0:
861 warnonly = 3
864 warnonly = 3
862
865
863 pos = -1
866 pos = -1
864 postout = []
867 postout = []
865 for l in output:
868 for l in output:
866 lout, lcmd = l, None
869 lout, lcmd = l, None
867 if salt in l:
870 if salt in l:
868 lout, lcmd = l.split(salt, 1)
871 lout, lcmd = l.split(salt, 1)
869
872
870 if lout:
873 if lout:
871 if not lout.endswith('\n'):
874 if not lout.endswith('\n'):
872 lout += ' (no-eol)\n'
875 lout += ' (no-eol)\n'
873
876
874 # Find the expected output at the current position.
877 # Find the expected output at the current position.
875 el = None
878 el = None
876 if expected.get(pos, None):
879 if expected.get(pos, None):
877 el = expected[pos].pop(0)
880 el = expected[pos].pop(0)
878
881
879 r = TTest.linematch(el, lout)
882 r = TTest.linematch(el, lout)
880 if isinstance(r, str):
883 if isinstance(r, str):
881 if r == '+glob':
884 if r == '+glob':
882 lout = el[:-1] + ' (glob)\n'
885 lout = el[:-1] + ' (glob)\n'
883 r = '' # Warn only this line.
886 r = '' # Warn only this line.
884 elif r == '-glob':
887 elif r == '-glob':
885 lout = ''.join(el.rsplit(' (glob)', 1))
888 lout = ''.join(el.rsplit(' (glob)', 1))
886 r = '' # Warn only this line.
889 r = '' # Warn only this line.
887 else:
890 else:
888 log('\ninfo, unknown linematch result: %r\n' % r)
891 log('\ninfo, unknown linematch result: %r\n' % r)
889 r = False
892 r = False
890 if r:
893 if r:
891 postout.append(' ' + el)
894 postout.append(' ' + el)
892 else:
895 else:
893 if self.NEEDESCAPE(lout):
896 if self.NEEDESCAPE(lout):
894 lout = TTest.stringescape('%s (esc)\n' %
897 lout = TTest.stringescape('%s (esc)\n' %
895 lout.rstrip('\n'))
898 lout.rstrip('\n'))
896 postout.append(' ' + lout) # Let diff deal with it.
899 postout.append(' ' + lout) # Let diff deal with it.
897 if r != '': # If line failed.
900 if r != '': # If line failed.
898 warnonly = 3 # for sure not
901 warnonly = 3 # for sure not
899 elif warnonly == 1: # Is "not yet" and line is warn only.
902 elif warnonly == 1: # Is "not yet" and line is warn only.
900 warnonly = 2 # Yes do warn.
903 warnonly = 2 # Yes do warn.
901
904
902 if lcmd:
905 if lcmd:
903 # Add on last return code.
906 # Add on last return code.
904 ret = int(lcmd.split()[1])
907 ret = int(lcmd.split()[1])
905 if ret != 0:
908 if ret != 0:
906 postout.append(' [%s]\n' % ret)
909 postout.append(' [%s]\n' % ret)
907 if pos in after:
910 if pos in after:
908 # Merge in non-active test bits.
911 # Merge in non-active test bits.
909 postout += after.pop(pos)
912 postout += after.pop(pos)
910 pos = int(lcmd.split()[0])
913 pos = int(lcmd.split()[0])
911
914
912 if pos in after:
915 if pos in after:
913 postout += after.pop(pos)
916 postout += after.pop(pos)
914
917
915 if warnonly == 2:
918 if warnonly == 2:
916 exitcode = False # Set exitcode to warned.
919 exitcode = False # Set exitcode to warned.
917
920
918 return exitcode, postout
921 return exitcode, postout
919
922
920 @staticmethod
923 @staticmethod
921 def rematch(el, l):
924 def rematch(el, l):
922 try:
925 try:
923 # use \Z to ensure that the regex matches to the end of the string
926 # use \Z to ensure that the regex matches to the end of the string
924 if os.name == 'nt':
927 if os.name == 'nt':
925 return re.match(el + r'\r?\n\Z', l)
928 return re.match(el + r'\r?\n\Z', l)
926 return re.match(el + r'\n\Z', l)
929 return re.match(el + r'\n\Z', l)
927 except re.error:
930 except re.error:
928 # el is an invalid regex
931 # el is an invalid regex
929 return False
932 return False
930
933
931 @staticmethod
934 @staticmethod
932 def globmatch(el, l):
935 def globmatch(el, l):
933 # The only supported special characters are * and ? plus / which also
936 # The only supported special characters are * and ? plus / which also
934 # matches \ on windows. Escaping of these characters is supported.
937 # matches \ on windows. Escaping of these characters is supported.
935 if el + '\n' == l:
938 if el + '\n' == l:
936 if os.altsep:
939 if os.altsep:
937 # matching on "/" is not needed for this line
940 # matching on "/" is not needed for this line
938 return '-glob'
941 return '-glob'
939 return True
942 return True
940 i, n = 0, len(el)
943 i, n = 0, len(el)
941 res = ''
944 res = ''
942 while i < n:
945 while i < n:
943 c = el[i]
946 c = el[i]
944 i += 1
947 i += 1
945 if c == '\\' and el[i] in '*?\\/':
948 if c == '\\' and el[i] in '*?\\/':
946 res += el[i - 1:i + 1]
949 res += el[i - 1:i + 1]
947 i += 1
950 i += 1
948 elif c == '*':
951 elif c == '*':
949 res += '.*'
952 res += '.*'
950 elif c == '?':
953 elif c == '?':
951 res += '.'
954 res += '.'
952 elif c == '/' and os.altsep:
955 elif c == '/' and os.altsep:
953 res += '[/\\\\]'
956 res += '[/\\\\]'
954 else:
957 else:
955 res += re.escape(c)
958 res += re.escape(c)
956 return TTest.rematch(res, l)
959 return TTest.rematch(res, l)
957
960
958 @staticmethod
961 @staticmethod
959 def linematch(el, l):
962 def linematch(el, l):
960 if el == l: # perfect match (fast)
963 if el == l: # perfect match (fast)
961 return True
964 return True
962 if el:
965 if el:
963 if el.endswith(" (esc)\n"):
966 if el.endswith(" (esc)\n"):
964 el = el[:-7].decode('string-escape') + '\n'
967 el = el[:-7].decode('string-escape') + '\n'
965 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
968 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
966 return True
969 return True
967 if el.endswith(" (re)\n"):
970 if el.endswith(" (re)\n"):
968 return TTest.rematch(el[:-6], l)
971 return TTest.rematch(el[:-6], l)
969 if el.endswith(" (glob)\n"):
972 if el.endswith(" (glob)\n"):
970 return TTest.globmatch(el[:-8], l)
973 return TTest.globmatch(el[:-8], l)
971 if os.altsep and l.replace('\\', '/') == el:
974 if os.altsep and l.replace('\\', '/') == el:
972 return '+glob'
975 return '+glob'
973 return False
976 return False
974
977
975 @staticmethod
978 @staticmethod
976 def parsehghaveoutput(lines):
979 def parsehghaveoutput(lines):
977 '''Parse hghave log lines.
980 '''Parse hghave log lines.
978
981
979 Return tuple of lists (missing, failed):
982 Return tuple of lists (missing, failed):
980 * the missing/unknown features
983 * the missing/unknown features
981 * the features for which existence check failed'''
984 * the features for which existence check failed'''
982 missing = []
985 missing = []
983 failed = []
986 failed = []
984 for line in lines:
987 for line in lines:
985 if line.startswith(TTest.SKIPPED_PREFIX):
988 if line.startswith(TTest.SKIPPED_PREFIX):
986 line = line.splitlines()[0]
989 line = line.splitlines()[0]
987 missing.append(line[len(TTest.SKIPPED_PREFIX):])
990 missing.append(line[len(TTest.SKIPPED_PREFIX):])
988 elif line.startswith(TTest.FAILED_PREFIX):
991 elif line.startswith(TTest.FAILED_PREFIX):
989 line = line.splitlines()[0]
992 line = line.splitlines()[0]
990 failed.append(line[len(TTest.FAILED_PREFIX):])
993 failed.append(line[len(TTest.FAILED_PREFIX):])
991
994
992 return missing, failed
995 return missing, failed
993
996
994 @staticmethod
997 @staticmethod
995 def _escapef(m):
998 def _escapef(m):
996 return TTest.ESCAPEMAP[m.group(0)]
999 return TTest.ESCAPEMAP[m.group(0)]
997
1000
998 @staticmethod
1001 @staticmethod
999 def _stringescape(s):
1002 def _stringescape(s):
1000 return TTest.ESCAPESUB(TTest._escapef, s)
1003 return TTest.ESCAPESUB(TTest._escapef, s)
1001
1004
1002
1005
1003 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1006 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1004 def run(cmd, wd, replacements, env, abort, debug=False, timeout=None):
1007 def run(cmd, wd, replacements, env, abort, debug=False, timeout=None):
1005 """Run command in a sub-process, capturing the output (stdout and stderr).
1008 """Run command in a sub-process, capturing the output (stdout and stderr).
1006 Return a tuple (exitcode, output). output is None in debug mode."""
1009 Return a tuple (exitcode, output). output is None in debug mode."""
1007 if debug:
1010 if debug:
1008 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1011 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1009 ret = proc.wait()
1012 ret = proc.wait()
1010 return (ret, None)
1013 return (ret, None)
1011
1014
1012 proc = Popen4(cmd, wd, timeout, env)
1015 proc = Popen4(cmd, wd, timeout, env)
1013 def cleanup():
1016 def cleanup():
1014 terminate(proc)
1017 terminate(proc)
1015 ret = proc.wait()
1018 ret = proc.wait()
1016 if ret == 0:
1019 if ret == 0:
1017 ret = signal.SIGTERM << 8
1020 ret = signal.SIGTERM << 8
1018 killdaemons(env['DAEMON_PIDS'])
1021 killdaemons(env['DAEMON_PIDS'])
1019 return ret
1022 return ret
1020
1023
1021 output = ''
1024 output = ''
1022 proc.tochild.close()
1025 proc.tochild.close()
1023
1026
1024 try:
1027 try:
1025 output = proc.fromchild.read()
1028 output = proc.fromchild.read()
1026 except KeyboardInterrupt:
1029 except KeyboardInterrupt:
1027 vlog('# Handling keyboard interrupt')
1030 vlog('# Handling keyboard interrupt')
1028 cleanup()
1031 cleanup()
1029 raise
1032 raise
1030
1033
1031 ret = proc.wait()
1034 ret = proc.wait()
1032 if wifexited(ret):
1035 if wifexited(ret):
1033 ret = os.WEXITSTATUS(ret)
1036 ret = os.WEXITSTATUS(ret)
1034
1037
1035 if proc.timeout:
1038 if proc.timeout:
1036 ret = 'timeout'
1039 ret = 'timeout'
1037
1040
1038 if ret:
1041 if ret:
1039 killdaemons(env['DAEMON_PIDS'])
1042 killdaemons(env['DAEMON_PIDS'])
1040
1043
1041 if abort[0]:
1044 if abort[0]:
1042 raise KeyboardInterrupt()
1045 raise KeyboardInterrupt()
1043
1046
1044 for s, r in replacements:
1047 for s, r in replacements:
1045 output = re.sub(s, r, output)
1048 output = re.sub(s, r, output)
1046 return ret, output.splitlines(True)
1049 return ret, output.splitlines(True)
1047
1050
1048 iolock = threading.Lock()
1051 iolock = threading.Lock()
1049
1052
1050 class SkipTest(Exception):
1053 class SkipTest(Exception):
1051 """Raised to indicate that a test is to be skipped."""
1054 """Raised to indicate that a test is to be skipped."""
1052
1055
1053 class IgnoreTest(Exception):
1056 class IgnoreTest(Exception):
1054 """Raised to indicate that a test is to be ignored."""
1057 """Raised to indicate that a test is to be ignored."""
1055
1058
1056 class WarnTest(Exception):
1059 class WarnTest(Exception):
1057 """Raised to indicate that a test warned."""
1060 """Raised to indicate that a test warned."""
1058
1061
1059 class TestResult(unittest._TextTestResult):
1062 class TestResult(unittest._TextTestResult):
1060 """Holds results when executing via unittest."""
1063 """Holds results when executing via unittest."""
1061 # Don't worry too much about accessing the non-public _TextTestResult.
1064 # Don't worry too much about accessing the non-public _TextTestResult.
1062 # It is relatively common in Python testing tools.
1065 # It is relatively common in Python testing tools.
1063 def __init__(self, options, *args, **kwargs):
1066 def __init__(self, options, *args, **kwargs):
1064 super(TestResult, self).__init__(*args, **kwargs)
1067 super(TestResult, self).__init__(*args, **kwargs)
1065
1068
1066 self._options = options
1069 self._options = options
1067
1070
1068 # unittest.TestResult didn't have skipped until 2.7. We need to
1071 # unittest.TestResult didn't have skipped until 2.7. We need to
1069 # polyfill it.
1072 # polyfill it.
1070 self.skipped = []
1073 self.skipped = []
1071
1074
1072 # We have a custom "ignored" result that isn't present in any Python
1075 # We have a custom "ignored" result that isn't present in any Python
1073 # unittest implementation. It is very similar to skipped. It may make
1076 # unittest implementation. It is very similar to skipped. It may make
1074 # sense to map it into skip some day.
1077 # sense to map it into skip some day.
1075 self.ignored = []
1078 self.ignored = []
1076
1079
1077 # We have a custom "warned" result that isn't present in any Python
1080 # We have a custom "warned" result that isn't present in any Python
1078 # unittest implementation. It is very similar to failed. It may make
1081 # unittest implementation. It is very similar to failed. It may make
1079 # sense to map it into fail some day.
1082 # sense to map it into fail some day.
1080 self.warned = []
1083 self.warned = []
1081
1084
1082 self.times = []
1085 self.times = []
1083 self._started = {}
1086 self._started = {}
1084
1087
1085 def addFailure(self, test, reason):
1088 def addFailure(self, test, reason):
1086 self.failures.append((test, reason))
1089 self.failures.append((test, reason))
1087
1090
1088 if self._options.first:
1091 if self._options.first:
1089 self.stop()
1092 self.stop()
1090
1093
1091 def addError(self, *args, **kwargs):
1094 def addError(self, *args, **kwargs):
1092 super(TestResult, self).addError(*args, **kwargs)
1095 super(TestResult, self).addError(*args, **kwargs)
1093
1096
1094 if self._options.first:
1097 if self._options.first:
1095 self.stop()
1098 self.stop()
1096
1099
1097 # Polyfill.
1100 # Polyfill.
1098 def addSkip(self, test, reason):
1101 def addSkip(self, test, reason):
1099 self.skipped.append((test, reason))
1102 self.skipped.append((test, reason))
1100
1103
1101 if self.showAll:
1104 if self.showAll:
1102 self.stream.writeln('skipped %s' % reason)
1105 self.stream.writeln('skipped %s' % reason)
1103 else:
1106 else:
1104 self.stream.write('s')
1107 self.stream.write('s')
1105 self.stream.flush()
1108 self.stream.flush()
1106
1109
1107 def addIgnore(self, test, reason):
1110 def addIgnore(self, test, reason):
1108 self.ignored.append((test, reason))
1111 self.ignored.append((test, reason))
1109
1112
1110 if self.showAll:
1113 if self.showAll:
1111 self.stream.writeln('ignored %s' % reason)
1114 self.stream.writeln('ignored %s' % reason)
1112 else:
1115 else:
1113 self.stream.write('i')
1116 self.stream.write('i')
1114 self.stream.flush()
1117 self.stream.flush()
1115
1118
1116 def addWarn(self, test, reason):
1119 def addWarn(self, test, reason):
1117 self.warned.append((test, reason))
1120 self.warned.append((test, reason))
1118
1121
1119 if self._options.first:
1122 if self._options.first:
1120 self.stop()
1123 self.stop()
1121
1124
1122 if self.showAll:
1125 if self.showAll:
1123 self.stream.writeln('warned %s' % reason)
1126 self.stream.writeln('warned %s' % reason)
1124 else:
1127 else:
1125 self.stream.write('~')
1128 self.stream.write('~')
1126 self.stream.flush()
1129 self.stream.flush()
1127
1130
1128 def startTest(self, test):
1131 def startTest(self, test):
1129 super(TestResult, self).startTest(test)
1132 super(TestResult, self).startTest(test)
1130
1133
1131 self._started[test.name] = time.time()
1134 self._started[test.name] = time.time()
1132
1135
1133 def stopTest(self, test, interrupted=False):
1136 def stopTest(self, test, interrupted=False):
1134 super(TestResult, self).stopTest(test)
1137 super(TestResult, self).stopTest(test)
1135
1138
1136 self.times.append((test.name, time.time() - self._started[test.name]))
1139 self.times.append((test.name, time.time() - self._started[test.name]))
1137 del self._started[test.name]
1140 del self._started[test.name]
1138
1141
1139 if interrupted:
1142 if interrupted:
1140 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1143 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1141 test.name, self.times[-1][1]))
1144 test.name, self.times[-1][1]))
1142
1145
1143 class TestSuite(unittest.TestSuite):
1146 class TestSuite(unittest.TestSuite):
1144 """Custom unitest TestSuite that knows how to execute concurrently."""
1147 """Custom unitest TestSuite that knows how to execute concurrently."""
1145
1148
1146 def __init__(self, runner, *args, **kwargs):
1149 def __init__(self, runner, *args, **kwargs):
1147 super(TestSuite, self).__init__(*args, **kwargs)
1150 super(TestSuite, self).__init__(*args, **kwargs)
1148
1151
1149 self._runner = runner
1152 self._runner = runner
1150
1153
1151 def run(self, result):
1154 def run(self, result):
1152 options = self._runner.options
1155 options = self._runner.options
1153
1156
1154 # We have a number of filters that need to be applied. We do this
1157 # We have a number of filters that need to be applied. We do this
1155 # here instead of inside Test because it makes the running logic for
1158 # here instead of inside Test because it makes the running logic for
1156 # Test simpler.
1159 # Test simpler.
1157 tests = []
1160 tests = []
1158 for test in self._tests:
1161 for test in self._tests:
1159 if not os.path.exists(test.path):
1162 if not os.path.exists(test.path):
1160 result.addSkip(test, "Doesn't exist")
1163 result.addSkip(test, "Doesn't exist")
1161 continue
1164 continue
1162
1165
1163 if not (options.whitelisted and test.name in options.whitelisted):
1166 if not (options.whitelisted and test.name in options.whitelisted):
1164 if options.blacklist and test.name in options.blacklist:
1167 if options.blacklist and test.name in options.blacklist:
1165 result.addSkip(test, 'blacklisted')
1168 result.addSkip(test, 'blacklisted')
1166 continue
1169 continue
1167
1170
1168 if options.retest and not os.path.exists(test.errpath):
1171 if options.retest and not os.path.exists(test.errpath):
1169 result.addIgnore(test, 'not retesting')
1172 result.addIgnore(test, 'not retesting')
1170 continue
1173 continue
1171
1174
1172 if options.keywords:
1175 if options.keywords:
1173 f = open(test.path)
1176 f = open(test.path)
1174 t = f.read().lower() + test.name.lower()
1177 t = f.read().lower() + test.name.lower()
1175 f.close()
1178 f.close()
1176 ignored = False
1179 ignored = False
1177 for k in options.keywords.lower().split():
1180 for k in options.keywords.lower().split():
1178 if k not in t:
1181 if k not in t:
1179 result.addIgnore(test, "doesn't match keyword")
1182 result.addIgnore(test, "doesn't match keyword")
1180 ignored = True
1183 ignored = True
1181 break
1184 break
1182
1185
1183 if ignored:
1186 if ignored:
1184 continue
1187 continue
1185
1188
1186 tests.append(test)
1189 tests.append(test)
1187
1190
1188 jobs = self._runner.options.jobs
1191 jobs = self._runner.options.jobs
1189 done = queue.Queue()
1192 done = queue.Queue()
1190 running = 0
1193 running = 0
1191
1194
1192 def job(test, result):
1195 def job(test, result):
1193 try:
1196 try:
1194 test(result)
1197 test(result)
1195 done.put(None)
1198 done.put(None)
1196 except KeyboardInterrupt:
1199 except KeyboardInterrupt:
1197 pass
1200 pass
1198 except: # re-raises
1201 except: # re-raises
1199 done.put(('!', test, 'run-test raised an error, see traceback'))
1202 done.put(('!', test, 'run-test raised an error, see traceback'))
1200 raise
1203 raise
1201
1204
1202 try:
1205 try:
1203 while tests or running:
1206 while tests or running:
1204 if not done.empty() or running == jobs or not tests:
1207 if not done.empty() or running == jobs or not tests:
1205 try:
1208 try:
1206 done.get(True, 1)
1209 done.get(True, 1)
1207 if result and result.shouldStop:
1210 if result and result.shouldStop:
1208 break
1211 break
1209 except queue.Empty:
1212 except queue.Empty:
1210 continue
1213 continue
1211 running -= 1
1214 running -= 1
1212 if tests and not running == jobs:
1215 if tests and not running == jobs:
1213 test = tests.pop(0)
1216 test = tests.pop(0)
1214 if self._runner.options.loop:
1217 if self._runner.options.loop:
1215 tests.append(test)
1218 tests.append(test)
1216 t = threading.Thread(target=job, name=test.name,
1219 t = threading.Thread(target=job, name=test.name,
1217 args=(test, result))
1220 args=(test, result))
1218 t.start()
1221 t.start()
1219 running += 1
1222 running += 1
1220 except KeyboardInterrupt:
1223 except KeyboardInterrupt:
1221 self._runner.abort[0] = True
1224 self._runner.abort[0] = True
1222
1225
1223 return result
1226 return result
1224
1227
1225 class TextTestRunner(unittest.TextTestRunner):
1228 class TextTestRunner(unittest.TextTestRunner):
1226 """Custom unittest test runner that uses appropriate settings."""
1229 """Custom unittest test runner that uses appropriate settings."""
1227
1230
1228 def __init__(self, runner, *args, **kwargs):
1231 def __init__(self, runner, *args, **kwargs):
1229 super(TextTestRunner, self).__init__(*args, **kwargs)
1232 super(TextTestRunner, self).__init__(*args, **kwargs)
1230
1233
1231 self._runner = runner
1234 self._runner = runner
1232
1235
1233 def run(self, test):
1236 def run(self, test):
1234 result = TestResult(self._runner.options, self.stream,
1237 result = TestResult(self._runner.options, self.stream,
1235 self.descriptions, self.verbosity)
1238 self.descriptions, self.verbosity)
1236
1239
1237 test(result)
1240 test(result)
1238
1241
1239 failed = len(result.failures)
1242 failed = len(result.failures)
1240 warned = len(result.warned)
1243 warned = len(result.warned)
1241 skipped = len(result.skipped)
1244 skipped = len(result.skipped)
1242 ignored = len(result.ignored)
1245 ignored = len(result.ignored)
1243
1246
1244 self.stream.writeln('')
1247 self.stream.writeln('')
1245
1248
1246 if not self._runner.options.noskips:
1249 if not self._runner.options.noskips:
1247 for test, msg in result.skipped:
1250 for test, msg in result.skipped:
1248 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1251 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1249 for test, msg in result.warned:
1252 for test, msg in result.warned:
1250 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1253 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1251 for test, msg in result.failures:
1254 for test, msg in result.failures:
1252 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1255 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1253 for test, msg in result.errors:
1256 for test, msg in result.errors:
1254 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1257 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1255
1258
1256 self._runner._checkhglib('Tested')
1259 self._runner._checkhglib('Tested')
1257
1260
1258 # This differs from unittest's default output in that we don't count
1261 # This differs from unittest's default output in that we don't count
1259 # skipped and ignored tests as part of the total test count.
1262 # skipped and ignored tests as part of the total test count.
1260 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1263 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1261 % (result.testsRun - skipped - ignored,
1264 % (result.testsRun - skipped - ignored,
1262 skipped + ignored, warned, failed))
1265 skipped + ignored, warned, failed))
1263 if failed:
1266 if failed:
1264 self.stream.writeln('python hash seed: %s' %
1267 self.stream.writeln('python hash seed: %s' %
1265 os.environ['PYTHONHASHSEED'])
1268 os.environ['PYTHONHASHSEED'])
1266 if self._runner.options.time:
1269 if self._runner.options.time:
1267 self.printtimes(result.times)
1270 self.printtimes(result.times)
1268
1271
1269 def printtimes(self, times):
1272 def printtimes(self, times):
1270 self.stream.writeln('# Producing time report')
1273 self.stream.writeln('# Producing time report')
1271 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1274 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
1272 cols = '%7.3f %s'
1275 cols = '%7.3f %s'
1273 self.stream.writeln('%-7s %s' % ('Time', 'Test'))
1276 self.stream.writeln('%-7s %s' % ('Time', 'Test'))
1274 for test, timetaken in times:
1277 for test, timetaken in times:
1275 self.stream.writeln(cols % (timetaken, test))
1278 self.stream.writeln(cols % (timetaken, test))
1276
1279
1277 class TestRunner(object):
1280 class TestRunner(object):
1278 """Holds context for executing tests.
1281 """Holds context for executing tests.
1279
1282
1280 Tests rely on a lot of state. This object holds it for them.
1283 Tests rely on a lot of state. This object holds it for them.
1281 """
1284 """
1282
1285
1283 REQUIREDTOOLS = [
1286 REQUIREDTOOLS = [
1284 os.path.basename(sys.executable),
1287 os.path.basename(sys.executable),
1285 'diff',
1288 'diff',
1286 'grep',
1289 'grep',
1287 'unzip',
1290 'unzip',
1288 'gunzip',
1291 'gunzip',
1289 'bunzip2',
1292 'bunzip2',
1290 'sed',
1293 'sed',
1291 ]
1294 ]
1292
1295
1293 TESTTYPES = [
1296 TESTTYPES = [
1294 ('.py', PythonTest),
1297 ('.py', PythonTest),
1295 ('.t', TTest),
1298 ('.t', TTest),
1296 ]
1299 ]
1297
1300
1298 def __init__(self):
1301 def __init__(self):
1299 self.options = None
1302 self.options = None
1300 self.testdir = None
1303 self.testdir = None
1301 self.hgtmp = None
1304 self.hgtmp = None
1302 self.inst = None
1305 self.inst = None
1303 self.bindir = None
1306 self.bindir = None
1304 self.tmpbinddir = None
1307 self.tmpbinddir = None
1305 self.pythondir = None
1308 self.pythondir = None
1306 self.coveragefile = None
1309 self.coveragefile = None
1307 self.abort = [False]
1310 self.abort = [False]
1308 self._createdfiles = []
1311 self._createdfiles = []
1309 self._hgpath = None
1312 self._hgpath = None
1310
1313
1311 def run(self, args, parser=None):
1314 def run(self, args, parser=None):
1312 """Run the test suite."""
1315 """Run the test suite."""
1313 oldmask = os.umask(022)
1316 oldmask = os.umask(022)
1314 try:
1317 try:
1315 parser = parser or getparser()
1318 parser = parser or getparser()
1316 options, args = parseargs(args, parser)
1319 options, args = parseargs(args, parser)
1317 self.options = options
1320 self.options = options
1318
1321
1319 self._checktools()
1322 self._checktools()
1320 tests = self.findtests(args)
1323 tests = self.findtests(args)
1321 return self._run(tests)
1324 return self._run(tests)
1322 finally:
1325 finally:
1323 os.umask(oldmask)
1326 os.umask(oldmask)
1324
1327
1325 def _run(self, tests):
1328 def _run(self, tests):
1326 if self.options.random:
1329 if self.options.random:
1327 random.shuffle(tests)
1330 random.shuffle(tests)
1328 else:
1331 else:
1329 # keywords for slow tests
1332 # keywords for slow tests
1330 slow = 'svn gendoc check-code-hg'.split()
1333 slow = 'svn gendoc check-code-hg'.split()
1331 def sortkey(f):
1334 def sortkey(f):
1332 # run largest tests first, as they tend to take the longest
1335 # run largest tests first, as they tend to take the longest
1333 try:
1336 try:
1334 val = -os.stat(f).st_size
1337 val = -os.stat(f).st_size
1335 except OSError, e:
1338 except OSError, e:
1336 if e.errno != errno.ENOENT:
1339 if e.errno != errno.ENOENT:
1337 raise
1340 raise
1338 return -1e9 # file does not exist, tell early
1341 return -1e9 # file does not exist, tell early
1339 for kw in slow:
1342 for kw in slow:
1340 if kw in f:
1343 if kw in f:
1341 val *= 10
1344 val *= 10
1342 return val
1345 return val
1343 tests.sort(key=sortkey)
1346 tests.sort(key=sortkey)
1344
1347
1345 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1348 self.testdir = os.environ['TESTDIR'] = os.getcwd()
1346
1349
1347 if 'PYTHONHASHSEED' not in os.environ:
1350 if 'PYTHONHASHSEED' not in os.environ:
1348 # use a random python hash seed all the time
1351 # use a random python hash seed all the time
1349 # we do the randomness ourself to know what seed is used
1352 # we do the randomness ourself to know what seed is used
1350 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1353 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1351
1354
1352 if self.options.tmpdir:
1355 if self.options.tmpdir:
1353 self.options.keep_tmpdir = True
1356 self.options.keep_tmpdir = True
1354 tmpdir = self.options.tmpdir
1357 tmpdir = self.options.tmpdir
1355 if os.path.exists(tmpdir):
1358 if os.path.exists(tmpdir):
1356 # Meaning of tmpdir has changed since 1.3: we used to create
1359 # Meaning of tmpdir has changed since 1.3: we used to create
1357 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1360 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1358 # tmpdir already exists.
1361 # tmpdir already exists.
1359 print "error: temp dir %r already exists" % tmpdir
1362 print "error: temp dir %r already exists" % tmpdir
1360 return 1
1363 return 1
1361
1364
1362 # Automatically removing tmpdir sounds convenient, but could
1365 # Automatically removing tmpdir sounds convenient, but could
1363 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1366 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1364 # or "--tmpdir=$HOME".
1367 # or "--tmpdir=$HOME".
1365 #vlog("# Removing temp dir", tmpdir)
1368 #vlog("# Removing temp dir", tmpdir)
1366 #shutil.rmtree(tmpdir)
1369 #shutil.rmtree(tmpdir)
1367 os.makedirs(tmpdir)
1370 os.makedirs(tmpdir)
1368 else:
1371 else:
1369 d = None
1372 d = None
1370 if os.name == 'nt':
1373 if os.name == 'nt':
1371 # without this, we get the default temp dir location, but
1374 # without this, we get the default temp dir location, but
1372 # in all lowercase, which causes troubles with paths (issue3490)
1375 # in all lowercase, which causes troubles with paths (issue3490)
1373 d = os.getenv('TMP')
1376 d = os.getenv('TMP')
1374 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1377 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1375 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1378 self.hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1376
1379
1377 if self.options.with_hg:
1380 if self.options.with_hg:
1378 self.inst = None
1381 self.inst = None
1379 self.bindir = os.path.dirname(os.path.realpath(
1382 self.bindir = os.path.dirname(os.path.realpath(
1380 self.options.with_hg))
1383 self.options.with_hg))
1381 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1384 self.tmpbindir = os.path.join(self.hgtmp, 'install', 'bin')
1382 os.makedirs(self.tmpbindir)
1385 os.makedirs(self.tmpbindir)
1383
1386
1384 # This looks redundant with how Python initializes sys.path from
1387 # This looks redundant with how Python initializes sys.path from
1385 # the location of the script being executed. Needed because the
1388 # the location of the script being executed. Needed because the
1386 # "hg" specified by --with-hg is not the only Python script
1389 # "hg" specified by --with-hg is not the only Python script
1387 # executed in the test suite that needs to import 'mercurial'
1390 # executed in the test suite that needs to import 'mercurial'
1388 # ... which means it's not really redundant at all.
1391 # ... which means it's not really redundant at all.
1389 self.pythondir = self.bindir
1392 self.pythondir = self.bindir
1390 else:
1393 else:
1391 self.inst = os.path.join(self.hgtmp, "install")
1394 self.inst = os.path.join(self.hgtmp, "install")
1392 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1395 self.bindir = os.environ["BINDIR"] = os.path.join(self.inst,
1393 "bin")
1396 "bin")
1394 self.tmpbindir = self.bindir
1397 self.tmpbindir = self.bindir
1395 self.pythondir = os.path.join(self.inst, "lib", "python")
1398 self.pythondir = os.path.join(self.inst, "lib", "python")
1396
1399
1397 os.environ["BINDIR"] = self.bindir
1400 os.environ["BINDIR"] = self.bindir
1398 os.environ["PYTHON"] = PYTHON
1401 os.environ["PYTHON"] = PYTHON
1399
1402
1400 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1403 path = [self.bindir] + os.environ["PATH"].split(os.pathsep)
1401 if self.tmpbindir != self.bindir:
1404 if self.tmpbindir != self.bindir:
1402 path = [self.tmpbindir] + path
1405 path = [self.tmpbindir] + path
1403 os.environ["PATH"] = os.pathsep.join(path)
1406 os.environ["PATH"] = os.pathsep.join(path)
1404
1407
1405 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1408 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1406 # can run .../tests/run-tests.py test-foo where test-foo
1409 # can run .../tests/run-tests.py test-foo where test-foo
1407 # adds an extension to HGRC. Also include run-test.py directory to
1410 # adds an extension to HGRC. Also include run-test.py directory to
1408 # import modules like heredoctest.
1411 # import modules like heredoctest.
1409 pypath = [self.pythondir, self.testdir,
1412 pypath = [self.pythondir, self.testdir,
1410 os.path.abspath(os.path.dirname(__file__))]
1413 os.path.abspath(os.path.dirname(__file__))]
1411 # We have to augment PYTHONPATH, rather than simply replacing
1414 # We have to augment PYTHONPATH, rather than simply replacing
1412 # it, in case external libraries are only available via current
1415 # it, in case external libraries are only available via current
1413 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1416 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1414 # are in /opt/subversion.)
1417 # are in /opt/subversion.)
1415 oldpypath = os.environ.get(IMPL_PATH)
1418 oldpypath = os.environ.get(IMPL_PATH)
1416 if oldpypath:
1419 if oldpypath:
1417 pypath.append(oldpypath)
1420 pypath.append(oldpypath)
1418 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1421 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1419
1422
1420 self.coveragefile = os.path.join(self.testdir, '.coverage')
1423 self.coveragefile = os.path.join(self.testdir, '.coverage')
1421
1424
1422 vlog("# Using TESTDIR", self.testdir)
1425 vlog("# Using TESTDIR", self.testdir)
1423 vlog("# Using HGTMP", self.hgtmp)
1426 vlog("# Using HGTMP", self.hgtmp)
1424 vlog("# Using PATH", os.environ["PATH"])
1427 vlog("# Using PATH", os.environ["PATH"])
1425 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1428 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1426
1429
1427 try:
1430 try:
1428 return self._runtests(tests) or 0
1431 return self._runtests(tests) or 0
1429 finally:
1432 finally:
1430 time.sleep(.1)
1433 time.sleep(.1)
1431 self._cleanup()
1434 self._cleanup()
1432
1435
1433 def findtests(self, args):
1436 def findtests(self, args):
1434 """Finds possible test files from arguments.
1437 """Finds possible test files from arguments.
1435
1438
1436 If you wish to inject custom tests into the test harness, this would
1439 If you wish to inject custom tests into the test harness, this would
1437 be a good function to monkeypatch or override in a derived class.
1440 be a good function to monkeypatch or override in a derived class.
1438 """
1441 """
1439 if not args:
1442 if not args:
1440 if self.options.changed:
1443 if self.options.changed:
1441 proc = Popen4('hg st --rev "%s" -man0 .' %
1444 proc = Popen4('hg st --rev "%s" -man0 .' %
1442 self.options.changed, None, 0)
1445 self.options.changed, None, 0)
1443 stdout, stderr = proc.communicate()
1446 stdout, stderr = proc.communicate()
1444 args = stdout.strip('\0').split('\0')
1447 args = stdout.strip('\0').split('\0')
1445 else:
1448 else:
1446 args = os.listdir('.')
1449 args = os.listdir('.')
1447
1450
1448 return [t for t in args
1451 return [t for t in args
1449 if os.path.basename(t).startswith('test-')
1452 if os.path.basename(t).startswith('test-')
1450 and (t.endswith('.py') or t.endswith('.t'))]
1453 and (t.endswith('.py') or t.endswith('.t'))]
1451
1454
1452 def _runtests(self, tests):
1455 def _runtests(self, tests):
1453 try:
1456 try:
1454 if self.inst:
1457 if self.inst:
1455 self._installhg()
1458 self._installhg()
1456 self._checkhglib("Testing")
1459 self._checkhglib("Testing")
1457 else:
1460 else:
1458 self._usecorrectpython()
1461 self._usecorrectpython()
1459
1462
1460 if self.options.restart:
1463 if self.options.restart:
1461 orig = list(tests)
1464 orig = list(tests)
1462 while tests:
1465 while tests:
1463 if os.path.exists(tests[0] + ".err"):
1466 if os.path.exists(tests[0] + ".err"):
1464 break
1467 break
1465 tests.pop(0)
1468 tests.pop(0)
1466 if not tests:
1469 if not tests:
1467 print "running all tests"
1470 print "running all tests"
1468 tests = orig
1471 tests = orig
1469
1472
1470 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1473 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1471
1474
1472 failed = False
1475 failed = False
1473 warned = False
1476 warned = False
1474
1477
1475 suite = TestSuite(self, tests=tests)
1478 suite = TestSuite(self, tests=tests)
1476 verbosity = 1
1479 verbosity = 1
1477 if self.options.verbose:
1480 if self.options.verbose:
1478 verbosity = 2
1481 verbosity = 2
1479 runner = TextTestRunner(self, verbosity=verbosity)
1482 runner = TextTestRunner(self, verbosity=verbosity)
1480 runner.run(suite)
1483 runner.run(suite)
1481
1484
1482 if self.options.anycoverage:
1485 if self.options.anycoverage:
1483 self._outputcoverage()
1486 self._outputcoverage()
1484 except KeyboardInterrupt:
1487 except KeyboardInterrupt:
1485 failed = True
1488 failed = True
1486 print "\ninterrupted!"
1489 print "\ninterrupted!"
1487
1490
1488 if failed:
1491 if failed:
1489 return 1
1492 return 1
1490 if warned:
1493 if warned:
1491 return 80
1494 return 80
1492
1495
1493 def _gettest(self, test, count):
1496 def _gettest(self, test, count):
1494 """Obtain a Test by looking at its filename.
1497 """Obtain a Test by looking at its filename.
1495
1498
1496 Returns a Test instance. The Test may not be runnable if it doesn't
1499 Returns a Test instance. The Test may not be runnable if it doesn't
1497 map to a known type.
1500 map to a known type.
1498 """
1501 """
1499 lctest = test.lower()
1502 lctest = test.lower()
1500 testcls = Test
1503 testcls = Test
1501
1504
1502 for ext, cls in self.TESTTYPES:
1505 for ext, cls in self.TESTTYPES:
1503 if lctest.endswith(ext):
1506 if lctest.endswith(ext):
1504 testcls = cls
1507 testcls = cls
1505 break
1508 break
1506
1509
1507 refpath = os.path.join(self.testdir, test)
1510 refpath = os.path.join(self.testdir, test)
1508 tmpdir = os.path.join(self.hgtmp, 'child%d' % count)
1511 tmpdir = os.path.join(self.hgtmp, 'child%d' % count)
1509
1512
1510 return testcls(self.options, refpath, count, tmpdir, self.abort,
1513 return testcls(self.options, refpath, tmpdir, self.abort,
1511 keeptmpdir=self.options.keep_tmpdir,
1514 keeptmpdir=self.options.keep_tmpdir,
1512 debug=self.options.debug,
1515 debug=self.options.debug,
1513 nodiff = self.options.nodiff,
1516 nodiff = self.options.nodiff,
1514 diffviewer=self.options.view,
1517 diffviewer=self.options.view,
1515 interactive=self.options.interactive,
1518 interactive=self.options.interactive,
1516 timeout=self.options.timeout)
1519 timeout=self.options.timeout,
1520 startport=self.options.port + count * 3)
1517
1521
1518 def _cleanup(self):
1522 def _cleanup(self):
1519 """Clean up state from this test invocation."""
1523 """Clean up state from this test invocation."""
1520
1524
1521 if self.options.keep_tmpdir:
1525 if self.options.keep_tmpdir:
1522 return
1526 return
1523
1527
1524 vlog("# Cleaning up HGTMP", self.hgtmp)
1528 vlog("# Cleaning up HGTMP", self.hgtmp)
1525 shutil.rmtree(self.hgtmp, True)
1529 shutil.rmtree(self.hgtmp, True)
1526 for f in self._createdfiles:
1530 for f in self._createdfiles:
1527 try:
1531 try:
1528 os.remove(f)
1532 os.remove(f)
1529 except OSError:
1533 except OSError:
1530 pass
1534 pass
1531
1535
1532 def _usecorrectpython(self):
1536 def _usecorrectpython(self):
1533 # Some tests run the Python interpreter. They must use the
1537 # Some tests run the Python interpreter. They must use the
1534 # same interpreter or bad things will happen.
1538 # same interpreter or bad things will happen.
1535 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1539 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1536 if getattr(os, 'symlink', None):
1540 if getattr(os, 'symlink', None):
1537 vlog("# Making python executable in test path a symlink to '%s'" %
1541 vlog("# Making python executable in test path a symlink to '%s'" %
1538 sys.executable)
1542 sys.executable)
1539 mypython = os.path.join(self.tmpbindir, pyexename)
1543 mypython = os.path.join(self.tmpbindir, pyexename)
1540 try:
1544 try:
1541 if os.readlink(mypython) == sys.executable:
1545 if os.readlink(mypython) == sys.executable:
1542 return
1546 return
1543 os.unlink(mypython)
1547 os.unlink(mypython)
1544 except OSError, err:
1548 except OSError, err:
1545 if err.errno != errno.ENOENT:
1549 if err.errno != errno.ENOENT:
1546 raise
1550 raise
1547 if self._findprogram(pyexename) != sys.executable:
1551 if self._findprogram(pyexename) != sys.executable:
1548 try:
1552 try:
1549 os.symlink(sys.executable, mypython)
1553 os.symlink(sys.executable, mypython)
1550 self._createdfiles.append(mypython)
1554 self._createdfiles.append(mypython)
1551 except OSError, err:
1555 except OSError, err:
1552 # child processes may race, which is harmless
1556 # child processes may race, which is harmless
1553 if err.errno != errno.EEXIST:
1557 if err.errno != errno.EEXIST:
1554 raise
1558 raise
1555 else:
1559 else:
1556 exedir, exename = os.path.split(sys.executable)
1560 exedir, exename = os.path.split(sys.executable)
1557 vlog("# Modifying search path to find %s as %s in '%s'" %
1561 vlog("# Modifying search path to find %s as %s in '%s'" %
1558 (exename, pyexename, exedir))
1562 (exename, pyexename, exedir))
1559 path = os.environ['PATH'].split(os.pathsep)
1563 path = os.environ['PATH'].split(os.pathsep)
1560 while exedir in path:
1564 while exedir in path:
1561 path.remove(exedir)
1565 path.remove(exedir)
1562 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1566 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1563 if not self._findprogram(pyexename):
1567 if not self._findprogram(pyexename):
1564 print "WARNING: Cannot find %s in search path" % pyexename
1568 print "WARNING: Cannot find %s in search path" % pyexename
1565
1569
1566 def _installhg(self):
1570 def _installhg(self):
1567 vlog("# Performing temporary installation of HG")
1571 vlog("# Performing temporary installation of HG")
1568 installerrs = os.path.join("tests", "install.err")
1572 installerrs = os.path.join("tests", "install.err")
1569 compiler = ''
1573 compiler = ''
1570 if self.options.compiler:
1574 if self.options.compiler:
1571 compiler = '--compiler ' + self.options.compiler
1575 compiler = '--compiler ' + self.options.compiler
1572 pure = self.options.pure and "--pure" or ""
1576 pure = self.options.pure and "--pure" or ""
1573 py3 = ''
1577 py3 = ''
1574 if sys.version_info[0] == 3:
1578 if sys.version_info[0] == 3:
1575 py3 = '--c2to3'
1579 py3 = '--c2to3'
1576
1580
1577 # Run installer in hg root
1581 # Run installer in hg root
1578 script = os.path.realpath(sys.argv[0])
1582 script = os.path.realpath(sys.argv[0])
1579 hgroot = os.path.dirname(os.path.dirname(script))
1583 hgroot = os.path.dirname(os.path.dirname(script))
1580 os.chdir(hgroot)
1584 os.chdir(hgroot)
1581 nohome = '--home=""'
1585 nohome = '--home=""'
1582 if os.name == 'nt':
1586 if os.name == 'nt':
1583 # The --home="" trick works only on OS where os.sep == '/'
1587 # The --home="" trick works only on OS where os.sep == '/'
1584 # because of a distutils convert_path() fast-path. Avoid it at
1588 # because of a distutils convert_path() fast-path. Avoid it at
1585 # least on Windows for now, deal with .pydistutils.cfg bugs
1589 # least on Windows for now, deal with .pydistutils.cfg bugs
1586 # when they happen.
1590 # when they happen.
1587 nohome = ''
1591 nohome = ''
1588 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1592 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1589 ' build %(compiler)s --build-base="%(base)s"'
1593 ' build %(compiler)s --build-base="%(base)s"'
1590 ' install --force --prefix="%(prefix)s"'
1594 ' install --force --prefix="%(prefix)s"'
1591 ' --install-lib="%(libdir)s"'
1595 ' --install-lib="%(libdir)s"'
1592 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1596 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1593 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1597 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1594 'compiler': compiler,
1598 'compiler': compiler,
1595 'base': os.path.join(self.hgtmp, "build"),
1599 'base': os.path.join(self.hgtmp, "build"),
1596 'prefix': self.inst, 'libdir': self.pythondir,
1600 'prefix': self.inst, 'libdir': self.pythondir,
1597 'bindir': self.bindir,
1601 'bindir': self.bindir,
1598 'nohome': nohome, 'logfile': installerrs})
1602 'nohome': nohome, 'logfile': installerrs})
1599 vlog("# Running", cmd)
1603 vlog("# Running", cmd)
1600 if os.system(cmd) == 0:
1604 if os.system(cmd) == 0:
1601 if not self.options.verbose:
1605 if not self.options.verbose:
1602 os.remove(installerrs)
1606 os.remove(installerrs)
1603 else:
1607 else:
1604 f = open(installerrs)
1608 f = open(installerrs)
1605 for line in f:
1609 for line in f:
1606 print line,
1610 print line,
1607 f.close()
1611 f.close()
1608 sys.exit(1)
1612 sys.exit(1)
1609 os.chdir(self.testdir)
1613 os.chdir(self.testdir)
1610
1614
1611 self._usecorrectpython()
1615 self._usecorrectpython()
1612
1616
1613 if self.options.py3k_warnings and not self.options.anycoverage:
1617 if self.options.py3k_warnings and not self.options.anycoverage:
1614 vlog("# Updating hg command to enable Py3k Warnings switch")
1618 vlog("# Updating hg command to enable Py3k Warnings switch")
1615 f = open(os.path.join(self.bindir, 'hg'), 'r')
1619 f = open(os.path.join(self.bindir, 'hg'), 'r')
1616 lines = [line.rstrip() for line in f]
1620 lines = [line.rstrip() for line in f]
1617 lines[0] += ' -3'
1621 lines[0] += ' -3'
1618 f.close()
1622 f.close()
1619 f = open(os.path.join(self.bindir, 'hg'), 'w')
1623 f = open(os.path.join(self.bindir, 'hg'), 'w')
1620 for line in lines:
1624 for line in lines:
1621 f.write(line + '\n')
1625 f.write(line + '\n')
1622 f.close()
1626 f.close()
1623
1627
1624 hgbat = os.path.join(self.bindir, 'hg.bat')
1628 hgbat = os.path.join(self.bindir, 'hg.bat')
1625 if os.path.isfile(hgbat):
1629 if os.path.isfile(hgbat):
1626 # hg.bat expects to be put in bin/scripts while run-tests.py
1630 # hg.bat expects to be put in bin/scripts while run-tests.py
1627 # installation layout put it in bin/ directly. Fix it
1631 # installation layout put it in bin/ directly. Fix it
1628 f = open(hgbat, 'rb')
1632 f = open(hgbat, 'rb')
1629 data = f.read()
1633 data = f.read()
1630 f.close()
1634 f.close()
1631 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1635 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1632 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1636 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1633 '"%~dp0python" "%~dp0hg" %*')
1637 '"%~dp0python" "%~dp0hg" %*')
1634 f = open(hgbat, 'wb')
1638 f = open(hgbat, 'wb')
1635 f.write(data)
1639 f.write(data)
1636 f.close()
1640 f.close()
1637 else:
1641 else:
1638 print 'WARNING: cannot fix hg.bat reference to python.exe'
1642 print 'WARNING: cannot fix hg.bat reference to python.exe'
1639
1643
1640 if self.options.anycoverage:
1644 if self.options.anycoverage:
1641 custom = os.path.join(self.testdir, 'sitecustomize.py')
1645 custom = os.path.join(self.testdir, 'sitecustomize.py')
1642 target = os.path.join(self.pythondir, 'sitecustomize.py')
1646 target = os.path.join(self.pythondir, 'sitecustomize.py')
1643 vlog('# Installing coverage trigger to %s' % target)
1647 vlog('# Installing coverage trigger to %s' % target)
1644 shutil.copyfile(custom, target)
1648 shutil.copyfile(custom, target)
1645 rc = os.path.join(self.testdir, '.coveragerc')
1649 rc = os.path.join(self.testdir, '.coveragerc')
1646 vlog('# Installing coverage rc to %s' % rc)
1650 vlog('# Installing coverage rc to %s' % rc)
1647 os.environ['COVERAGE_PROCESS_START'] = rc
1651 os.environ['COVERAGE_PROCESS_START'] = rc
1648 fn = os.path.join(self.inst, '..', '.coverage')
1652 fn = os.path.join(self.inst, '..', '.coverage')
1649 os.environ['COVERAGE_FILE'] = fn
1653 os.environ['COVERAGE_FILE'] = fn
1650
1654
1651 def _checkhglib(self, verb):
1655 def _checkhglib(self, verb):
1652 """Ensure that the 'mercurial' package imported by python is
1656 """Ensure that the 'mercurial' package imported by python is
1653 the one we expect it to be. If not, print a warning to stderr."""
1657 the one we expect it to be. If not, print a warning to stderr."""
1654 expecthg = os.path.join(self.pythondir, 'mercurial')
1658 expecthg = os.path.join(self.pythondir, 'mercurial')
1655 actualhg = self._gethgpath()
1659 actualhg = self._gethgpath()
1656 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1660 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1657 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1661 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1658 ' (expected %s)\n'
1662 ' (expected %s)\n'
1659 % (verb, actualhg, expecthg))
1663 % (verb, actualhg, expecthg))
1660 def _gethgpath(self):
1664 def _gethgpath(self):
1661 """Return the path to the mercurial package that is actually found by
1665 """Return the path to the mercurial package that is actually found by
1662 the current Python interpreter."""
1666 the current Python interpreter."""
1663 if self._hgpath is not None:
1667 if self._hgpath is not None:
1664 return self._hgpath
1668 return self._hgpath
1665
1669
1666 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1670 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1667 pipe = os.popen(cmd % PYTHON)
1671 pipe = os.popen(cmd % PYTHON)
1668 try:
1672 try:
1669 self._hgpath = pipe.read().strip()
1673 self._hgpath = pipe.read().strip()
1670 finally:
1674 finally:
1671 pipe.close()
1675 pipe.close()
1672
1676
1673 return self._hgpath
1677 return self._hgpath
1674
1678
1675 def _outputcoverage(self):
1679 def _outputcoverage(self):
1676 vlog('# Producing coverage report')
1680 vlog('# Producing coverage report')
1677 os.chdir(self.pythondir)
1681 os.chdir(self.pythondir)
1678
1682
1679 def covrun(*args):
1683 def covrun(*args):
1680 cmd = 'coverage %s' % ' '.join(args)
1684 cmd = 'coverage %s' % ' '.join(args)
1681 vlog('# Running: %s' % cmd)
1685 vlog('# Running: %s' % cmd)
1682 os.system(cmd)
1686 os.system(cmd)
1683
1687
1684 covrun('-c')
1688 covrun('-c')
1685 omit = ','.join(os.path.join(x, '*') for x in
1689 omit = ','.join(os.path.join(x, '*') for x in
1686 [self.bindir, self.testdir])
1690 [self.bindir, self.testdir])
1687 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1691 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1688 if self.options.htmlcov:
1692 if self.options.htmlcov:
1689 htmldir = os.path.join(self.testdir, 'htmlcov')
1693 htmldir = os.path.join(self.testdir, 'htmlcov')
1690 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1694 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1691 '"--omit=%s"' % omit)
1695 '"--omit=%s"' % omit)
1692 if self.options.annotate:
1696 if self.options.annotate:
1693 adir = os.path.join(self.testdir, 'annotated')
1697 adir = os.path.join(self.testdir, 'annotated')
1694 if not os.path.isdir(adir):
1698 if not os.path.isdir(adir):
1695 os.mkdir(adir)
1699 os.mkdir(adir)
1696 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1700 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1697
1701
1698 def _findprogram(self, program):
1702 def _findprogram(self, program):
1699 """Search PATH for a executable program"""
1703 """Search PATH for a executable program"""
1700 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1704 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1701 name = os.path.join(p, program)
1705 name = os.path.join(p, program)
1702 if os.name == 'nt' or os.access(name, os.X_OK):
1706 if os.name == 'nt' or os.access(name, os.X_OK):
1703 return name
1707 return name
1704 return None
1708 return None
1705
1709
1706 def _checktools(self):
1710 def _checktools(self):
1707 # Before we go any further, check for pre-requisite tools
1711 # Before we go any further, check for pre-requisite tools
1708 # stuff from coreutils (cat, rm, etc) are not tested
1712 # stuff from coreutils (cat, rm, etc) are not tested
1709 for p in self.REQUIREDTOOLS:
1713 for p in self.REQUIREDTOOLS:
1710 if os.name == 'nt' and not p.endswith('.exe'):
1714 if os.name == 'nt' and not p.endswith('.exe'):
1711 p += '.exe'
1715 p += '.exe'
1712 found = self._findprogram(p)
1716 found = self._findprogram(p)
1713 if found:
1717 if found:
1714 vlog("# Found prerequisite", p, "at", found)
1718 vlog("# Found prerequisite", p, "at", found)
1715 else:
1719 else:
1716 print "WARNING: Did not find prerequisite tool: %s " % p
1720 print "WARNING: Did not find prerequisite tool: %s " % p
1717
1721
1718 if __name__ == '__main__':
1722 if __name__ == '__main__':
1719 runner = TestRunner()
1723 runner = TestRunner()
1720 sys.exit(runner.run(sys.argv[1:]))
1724 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now