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