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