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