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