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