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