##// END OF EJS Templates
run-tests: even more bytestring annotations for Python 3
Augie Fackler -
r25041:09c71e3d default
parent child Browse files
Show More
@@ -1,2139 +1,2152 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 = 'PYTHONPATH'
131 IMPL_PATH = b'PYTHONPATH'
132 if 'java' in sys.platform:
132 if 'java' in sys.platform:
133 IMPL_PATH = '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('#', 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]))
266 testdir = os.path.dirname(os.path.realpath(sys.argv[0]).encode('utf-8'))
267 hgbin = os.path.join(os.path.dirname(testdir), '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 (r':%s\b' % self._startport, ':$HGPORT'),
669 (br':%d\b' % self._startport, b':$HGPORT'),
670 (r':%s\b' % (self._startport + 1), ':$HGPORT1'),
670 (br':%d\b' % (self._startport + 1), b':$HGPORT1'),
671 (r':%s\b' % (self._startport + 2), ':$HGPORT2'),
671 (br':%d\b' % (self._startport + 2), b':$HGPORT2'),
672 (r'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
672 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
673 r'\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 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
678 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
679 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
679 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
680 for c in self._testtmp), '$TESTTMP'))
680 for c in self._testtmp), b'$TESTTMP'))
681 else:
681 else:
682 r.append((re.escape(self._testtmp), '$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(r'[\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(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
842 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
843 ESCAPEMAP = dict((chr(i), r'\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({'\\': '\\\\', '\r': r'\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.name)
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 = '%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 = '%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('\\', '/')
881 tdir = self._testdir.replace(b'\\', b'/')
882 proc = Popen4('%s -c "%s/hghave %s"' %
882 proc = Popen4(b'%s -c "%s/hghave %s"' %
883 (self._shell, tdir, ' '.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 = "SALT" + str(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('%s %d 0\n' % (salt, line))
902 script.append(b'%s %d 0\n' % (salt, line))
903 else:
903 else:
904 script.append('echo %s %s $?\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('\n'):
1023 if not lout.endswith(b'\n'):
1024 lout += ' (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(' ' + el)
1043 postout.append(b' ' + el)
1044 else:
1044 else:
1045 if self.NEEDESCAPE(lout):
1045 if self.NEEDESCAPE(lout):
1046 lout = TTest._stringescape('%s (esc)\n' %
1046 lout = TTest._stringescape(b'%s (esc)\n' %
1047 lout.rstrip('\n'))
1047 lout.rstrip(b'\n'))
1048 postout.append(' ' + 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(' [%s]\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 + r'\r?\n\Z', l)
1077 return re.match(el + br'\r?\n\Z', l)
1078 return re.match(el + r'\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 + '\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 '-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 = ''
1096 res = b''
1097 while i < n:
1097 while i < n:
1098 c = el[i]
1098 c = el[i:i + 1]
1099 i += 1
1099 i += 1
1100 if c == '\\' and i < n and el[i] in '*?\\/':
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 == '*':
1103 elif c == b'*':
1104 res += '.*'
1104 res += b'.*'
1105 elif c == '?':
1105 elif c == b'?':
1106 res += '.'
1106 res += b'.'
1107 elif c == '/' and os.altsep:
1107 elif c == b'/' and os.altsep:
1108 res += '[/\\\\]'
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(" (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 el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
1120 if sys.version_info[0] == 3:
1121 el.encode('utf-8')
1122 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1121 return True
1123 return True
1122 if el.endswith(" (re)\n"):
1124 if el.endswith(b" (re)\n"):
1123 return TTest.rematch(el[:-6], l)
1125 return TTest.rematch(el[:-6], l)
1124 if el.endswith(" (glob)\n"):
1126 if el.endswith(b" (glob)\n"):
1125 # ignore '(glob)' added to l by 'replacements'
1127 # ignore '(glob)' added to l by 'replacements'
1126 if l.endswith(" (glob)\n"):
1128 if l.endswith(b" (glob)\n"):
1127 l = l[:-8] + "\n"
1129 l = l[:-8] + b"\n"
1128 return TTest.globmatch(el[:-8], l)
1130 return TTest.globmatch(el[:-8], l)
1129 if os.altsep and l.replace('\\', '/') == el:
1131 if os.altsep and l.replace(b'\\', b'/') == el:
1130 return '+glob'
1132 return b'+glob'
1131 return False
1133 return False
1132
1134
1133 @staticmethod
1135 @staticmethod
1134 def parsehghaveoutput(lines):
1136 def parsehghaveoutput(lines):
1135 '''Parse hghave log lines.
1137 '''Parse hghave log lines.
1136
1138
1137 Return tuple of lists (missing, failed):
1139 Return tuple of lists (missing, failed):
1138 * the missing/unknown features
1140 * the missing/unknown features
1139 * the features for which existence check failed'''
1141 * the features for which existence check failed'''
1140 missing = []
1142 missing = []
1141 failed = []
1143 failed = []
1142 for line in lines:
1144 for line in lines:
1143 if line.startswith(TTest.SKIPPED_PREFIX):
1145 if line.startswith(TTest.SKIPPED_PREFIX):
1144 line = line.splitlines()[0]
1146 line = line.splitlines()[0]
1145 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1147 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1146 elif line.startswith(TTest.FAILED_PREFIX):
1148 elif line.startswith(TTest.FAILED_PREFIX):
1147 line = line.splitlines()[0]
1149 line = line.splitlines()[0]
1148 failed.append(line[len(TTest.FAILED_PREFIX):])
1150 failed.append(line[len(TTest.FAILED_PREFIX):])
1149
1151
1150 return missing, failed
1152 return missing, failed
1151
1153
1152 @staticmethod
1154 @staticmethod
1153 def _escapef(m):
1155 def _escapef(m):
1154 return TTest.ESCAPEMAP[m.group(0)]
1156 return TTest.ESCAPEMAP[m.group(0)]
1155
1157
1156 @staticmethod
1158 @staticmethod
1157 def _stringescape(s):
1159 def _stringescape(s):
1158 return TTest.ESCAPESUB(TTest._escapef, s)
1160 return TTest.ESCAPESUB(TTest._escapef, s)
1159
1161
1160 iolock = threading.RLock()
1162 iolock = threading.RLock()
1161
1163
1162 class SkipTest(Exception):
1164 class SkipTest(Exception):
1163 """Raised to indicate that a test is to be skipped."""
1165 """Raised to indicate that a test is to be skipped."""
1164
1166
1165 class IgnoreTest(Exception):
1167 class IgnoreTest(Exception):
1166 """Raised to indicate that a test is to be ignored."""
1168 """Raised to indicate that a test is to be ignored."""
1167
1169
1168 class WarnTest(Exception):
1170 class WarnTest(Exception):
1169 """Raised to indicate that a test warned."""
1171 """Raised to indicate that a test warned."""
1170
1172
1171 class TestResult(unittest._TextTestResult):
1173 class TestResult(unittest._TextTestResult):
1172 """Holds results when executing via unittest."""
1174 """Holds results when executing via unittest."""
1173 # Don't worry too much about accessing the non-public _TextTestResult.
1175 # Don't worry too much about accessing the non-public _TextTestResult.
1174 # It is relatively common in Python testing tools.
1176 # It is relatively common in Python testing tools.
1175 def __init__(self, options, *args, **kwargs):
1177 def __init__(self, options, *args, **kwargs):
1176 super(TestResult, self).__init__(*args, **kwargs)
1178 super(TestResult, self).__init__(*args, **kwargs)
1177
1179
1178 self._options = options
1180 self._options = options
1179
1181
1180 # 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
1181 # polyfill it.
1183 # polyfill it.
1182 self.skipped = []
1184 self.skipped = []
1183
1185
1184 # 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
1185 # unittest implementation. It is very similar to skipped. It may make
1187 # unittest implementation. It is very similar to skipped. It may make
1186 # sense to map it into skip some day.
1188 # sense to map it into skip some day.
1187 self.ignored = []
1189 self.ignored = []
1188
1190
1189 # 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
1190 # unittest implementation. It is very similar to failed. It may make
1192 # unittest implementation. It is very similar to failed. It may make
1191 # sense to map it into fail some day.
1193 # sense to map it into fail some day.
1192 self.warned = []
1194 self.warned = []
1193
1195
1194 self.times = []
1196 self.times = []
1195 # Data stored for the benefit of generating xunit reports.
1197 # Data stored for the benefit of generating xunit reports.
1196 self.successes = []
1198 self.successes = []
1197 self.faildata = {}
1199 self.faildata = {}
1198
1200
1199 def addFailure(self, test, reason):
1201 def addFailure(self, test, reason):
1200 self.failures.append((test, reason))
1202 self.failures.append((test, reason))
1201
1203
1202 if self._options.first:
1204 if self._options.first:
1203 self.stop()
1205 self.stop()
1204 else:
1206 else:
1205 iolock.acquire()
1207 iolock.acquire()
1206 if not self._options.nodiff:
1208 if not self._options.nodiff:
1207 self.stream.write('\nERROR: %s output changed\n' % test)
1209 self.stream.write('\nERROR: %s output changed\n' % test)
1208
1210
1209 self.stream.write('!')
1211 self.stream.write('!')
1210 self.stream.flush()
1212 self.stream.flush()
1211 iolock.release()
1213 iolock.release()
1212
1214
1213 def addSuccess(self, test):
1215 def addSuccess(self, test):
1214 iolock.acquire()
1216 iolock.acquire()
1215 super(TestResult, self).addSuccess(test)
1217 super(TestResult, self).addSuccess(test)
1216 iolock.release()
1218 iolock.release()
1217 self.successes.append(test)
1219 self.successes.append(test)
1218
1220
1219 def addError(self, test, err):
1221 def addError(self, test, err):
1220 super(TestResult, self).addError(test, err)
1222 super(TestResult, self).addError(test, err)
1221 if self._options.first:
1223 if self._options.first:
1222 self.stop()
1224 self.stop()
1223
1225
1224 # Polyfill.
1226 # Polyfill.
1225 def addSkip(self, test, reason):
1227 def addSkip(self, test, reason):
1226 self.skipped.append((test, reason))
1228 self.skipped.append((test, reason))
1227 iolock.acquire()
1229 iolock.acquire()
1228 if self.showAll:
1230 if self.showAll:
1229 self.stream.writeln('skipped %s' % reason)
1231 self.stream.writeln('skipped %s' % reason)
1230 else:
1232 else:
1231 self.stream.write('s')
1233 self.stream.write('s')
1232 self.stream.flush()
1234 self.stream.flush()
1233 iolock.release()
1235 iolock.release()
1234
1236
1235 def addIgnore(self, test, reason):
1237 def addIgnore(self, test, reason):
1236 self.ignored.append((test, reason))
1238 self.ignored.append((test, reason))
1237 iolock.acquire()
1239 iolock.acquire()
1238 if self.showAll:
1240 if self.showAll:
1239 self.stream.writeln('ignored %s' % reason)
1241 self.stream.writeln('ignored %s' % reason)
1240 else:
1242 else:
1241 if reason != 'not retesting' and reason != "doesn't match keyword":
1243 if reason != 'not retesting' and reason != "doesn't match keyword":
1242 self.stream.write('i')
1244 self.stream.write('i')
1243 else:
1245 else:
1244 self.testsRun += 1
1246 self.testsRun += 1
1245 self.stream.flush()
1247 self.stream.flush()
1246 iolock.release()
1248 iolock.release()
1247
1249
1248 def addWarn(self, test, reason):
1250 def addWarn(self, test, reason):
1249 self.warned.append((test, reason))
1251 self.warned.append((test, reason))
1250
1252
1251 if self._options.first:
1253 if self._options.first:
1252 self.stop()
1254 self.stop()
1253
1255
1254 iolock.acquire()
1256 iolock.acquire()
1255 if self.showAll:
1257 if self.showAll:
1256 self.stream.writeln('warned %s' % reason)
1258 self.stream.writeln('warned %s' % reason)
1257 else:
1259 else:
1258 self.stream.write('~')
1260 self.stream.write('~')
1259 self.stream.flush()
1261 self.stream.flush()
1260 iolock.release()
1262 iolock.release()
1261
1263
1262 def addOutputMismatch(self, test, ret, got, expected):
1264 def addOutputMismatch(self, test, ret, got, expected):
1263 """Record a mismatch in test output for a particular test."""
1265 """Record a mismatch in test output for a particular test."""
1264 if self.shouldStop:
1266 if self.shouldStop:
1265 # don't print, some other test case already failed and
1267 # don't print, some other test case already failed and
1266 # printed, we're just stale and probably failed due to our
1268 # printed, we're just stale and probably failed due to our
1267 # temp dir getting cleaned up.
1269 # temp dir getting cleaned up.
1268 return
1270 return
1269
1271
1270 accepted = False
1272 accepted = False
1271 failed = False
1273 failed = False
1272 lines = []
1274 lines = []
1273
1275
1274 iolock.acquire()
1276 iolock.acquire()
1275 if self._options.nodiff:
1277 if self._options.nodiff:
1276 pass
1278 pass
1277 elif self._options.view:
1279 elif self._options.view:
1278 os.system("%s %s %s" %
1280 os.system("%s %s %s" %
1279 (self._options.view, test.refpath, test.errpath))
1281 (self._options.view, test.refpath, test.errpath))
1280 else:
1282 else:
1281 servefail, lines = getdiff(expected, got,
1283 servefail, lines = getdiff(expected, got,
1282 test.refpath, test.errpath)
1284 test.refpath, test.errpath)
1283 if servefail:
1285 if servefail:
1284 self.addFailure(
1286 self.addFailure(
1285 test,
1287 test,
1286 'server failed to start (HGPORT=%s)' % test._startport)
1288 'server failed to start (HGPORT=%s)' % test._startport)
1287 else:
1289 else:
1288 self.stream.write('\n')
1290 self.stream.write('\n')
1289 for line in lines:
1291 for line in lines:
1290 self.stream.write(line)
1292 self.stream.write(line)
1291 self.stream.flush()
1293 self.stream.flush()
1292
1294
1293 # handle interactive prompt without releasing iolock
1295 # handle interactive prompt without releasing iolock
1294 if self._options.interactive:
1296 if self._options.interactive:
1295 self.stream.write('Accept this change? [n] ')
1297 self.stream.write('Accept this change? [n] ')
1296 answer = sys.stdin.readline().strip()
1298 answer = sys.stdin.readline().strip()
1297 if answer.lower() in ('y', 'yes'):
1299 if answer.lower() in ('y', 'yes'):
1298 if test.name.endswith('.t'):
1300 if test.name.endswith('.t'):
1299 rename(test.errpath, test.path)
1301 rename(test.errpath, test.path)
1300 else:
1302 else:
1301 rename(test.errpath, '%s.out' % test.path)
1303 rename(test.errpath, '%s.out' % test.path)
1302 accepted = True
1304 accepted = True
1303 if not accepted and not failed:
1305 if not accepted and not failed:
1304 self.faildata[test.name] = ''.join(lines)
1306 self.faildata[test.name] = ''.join(lines)
1305 iolock.release()
1307 iolock.release()
1306
1308
1307 return accepted
1309 return accepted
1308
1310
1309 def startTest(self, test):
1311 def startTest(self, test):
1310 super(TestResult, self).startTest(test)
1312 super(TestResult, self).startTest(test)
1311
1313
1312 # 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
1313 # 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.
1314 # 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
1315 # and not for Windows.
1317 # and not for Windows.
1316 test.started = os.times()
1318 test.started = os.times()
1317
1319
1318 def stopTest(self, test, interrupted=False):
1320 def stopTest(self, test, interrupted=False):
1319 super(TestResult, self).stopTest(test)
1321 super(TestResult, self).stopTest(test)
1320
1322
1321 test.stopped = os.times()
1323 test.stopped = os.times()
1322
1324
1323 starttime = test.started
1325 starttime = test.started
1324 endtime = test.stopped
1326 endtime = test.stopped
1325 self.times.append((test.name,
1327 self.times.append((test.name,
1326 endtime[2] - starttime[2], # user space CPU time
1328 endtime[2] - starttime[2], # user space CPU time
1327 endtime[3] - starttime[3], # sys space CPU time
1329 endtime[3] - starttime[3], # sys space CPU time
1328 endtime[4] - starttime[4], # real time
1330 endtime[4] - starttime[4], # real time
1329 ))
1331 ))
1330
1332
1331 if interrupted:
1333 if interrupted:
1332 iolock.acquire()
1334 iolock.acquire()
1333 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1335 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1334 test.name, self.times[-1][3]))
1336 test.name, self.times[-1][3]))
1335 iolock.release()
1337 iolock.release()
1336
1338
1337 class TestSuite(unittest.TestSuite):
1339 class TestSuite(unittest.TestSuite):
1338 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1340 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1339
1341
1340 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1342 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1341 retest=False, keywords=None, loop=False, runs_per_test=1,
1343 retest=False, keywords=None, loop=False, runs_per_test=1,
1342 loadtest=None,
1344 loadtest=None,
1343 *args, **kwargs):
1345 *args, **kwargs):
1344 """Create a new instance that can run tests with a configuration.
1346 """Create a new instance that can run tests with a configuration.
1345
1347
1346 testdir specifies the directory where tests are executed from. This
1348 testdir specifies the directory where tests are executed from. This
1347 is typically the ``tests`` directory from Mercurial's source
1349 is typically the ``tests`` directory from Mercurial's source
1348 repository.
1350 repository.
1349
1351
1350 jobs specifies the number of jobs to run concurrently. Each test
1352 jobs specifies the number of jobs to run concurrently. Each test
1351 executes on its own thread. Tests actually spawn new processes, so
1353 executes on its own thread. Tests actually spawn new processes, so
1352 state mutation should not be an issue.
1354 state mutation should not be an issue.
1353
1355
1354 whitelist and blacklist denote tests that have been whitelisted and
1356 whitelist and blacklist denote tests that have been whitelisted and
1355 blacklisted, respectively. These arguments don't belong in TestSuite.
1357 blacklisted, respectively. These arguments don't belong in TestSuite.
1356 Instead, whitelist and blacklist should be handled by the thing that
1358 Instead, whitelist and blacklist should be handled by the thing that
1357 populates the TestSuite with tests. They are present to preserve
1359 populates the TestSuite with tests. They are present to preserve
1358 backwards compatible behavior which reports skipped tests as part
1360 backwards compatible behavior which reports skipped tests as part
1359 of the results.
1361 of the results.
1360
1362
1361 retest denotes whether to retest failed tests. This arguably belongs
1363 retest denotes whether to retest failed tests. This arguably belongs
1362 outside of TestSuite.
1364 outside of TestSuite.
1363
1365
1364 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
1365 to execute. This arguably belongs outside of TestSuite.
1367 to execute. This arguably belongs outside of TestSuite.
1366
1368
1367 loop denotes whether to loop over tests forever.
1369 loop denotes whether to loop over tests forever.
1368 """
1370 """
1369 super(TestSuite, self).__init__(*args, **kwargs)
1371 super(TestSuite, self).__init__(*args, **kwargs)
1370
1372
1371 self._jobs = jobs
1373 self._jobs = jobs
1372 self._whitelist = whitelist
1374 self._whitelist = whitelist
1373 self._blacklist = blacklist
1375 self._blacklist = blacklist
1374 self._retest = retest
1376 self._retest = retest
1375 self._keywords = keywords
1377 self._keywords = keywords
1376 self._loop = loop
1378 self._loop = loop
1377 self._runs_per_test = runs_per_test
1379 self._runs_per_test = runs_per_test
1378 self._loadtest = loadtest
1380 self._loadtest = loadtest
1379
1381
1380 def run(self, result):
1382 def run(self, result):
1381 # 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
1382 # 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
1383 # Test simpler.
1385 # Test simpler.
1384 tests = []
1386 tests = []
1385 num_tests = [0]
1387 num_tests = [0]
1386 for test in self._tests:
1388 for test in self._tests:
1387 def get():
1389 def get():
1388 num_tests[0] += 1
1390 num_tests[0] += 1
1389 if getattr(test, 'should_reload', False):
1391 if getattr(test, 'should_reload', False):
1390 return self._loadtest(test.name, num_tests[0])
1392 return self._loadtest(test.bname, num_tests[0])
1391 return test
1393 return test
1392 if not os.path.exists(test.path):
1394 if not os.path.exists(test.path):
1393 result.addSkip(test, "Doesn't exist")
1395 result.addSkip(test, "Doesn't exist")
1394 continue
1396 continue
1395
1397
1396 if not (self._whitelist and test.name in self._whitelist):
1398 if not (self._whitelist and test.name in self._whitelist):
1397 if self._blacklist and test.name in self._blacklist:
1399 if self._blacklist and test.name in self._blacklist:
1398 result.addSkip(test, 'blacklisted')
1400 result.addSkip(test, 'blacklisted')
1399 continue
1401 continue
1400
1402
1401 if self._retest and not os.path.exists(test.errpath):
1403 if self._retest and not os.path.exists(test.errpath):
1402 result.addIgnore(test, 'not retesting')
1404 result.addIgnore(test, 'not retesting')
1403 continue
1405 continue
1404
1406
1405 if self._keywords:
1407 if self._keywords:
1406 f = open(test.path, 'rb')
1408 f = open(test.path, 'rb')
1407 t = f.read().lower() + test.name.lower()
1409 t = f.read().lower() + test.name.lower()
1408 f.close()
1410 f.close()
1409 ignored = False
1411 ignored = False
1410 for k in self._keywords.lower().split():
1412 for k in self._keywords.lower().split():
1411 if k not in t:
1413 if k not in t:
1412 result.addIgnore(test, "doesn't match keyword")
1414 result.addIgnore(test, "doesn't match keyword")
1413 ignored = True
1415 ignored = True
1414 break
1416 break
1415
1417
1416 if ignored:
1418 if ignored:
1417 continue
1419 continue
1418 for _ in xrange(self._runs_per_test):
1420 for _ in xrange(self._runs_per_test):
1419 tests.append(get())
1421 tests.append(get())
1420
1422
1421 runtests = list(tests)
1423 runtests = list(tests)
1422 done = queue.Queue()
1424 done = queue.Queue()
1423 running = 0
1425 running = 0
1424
1426
1425 def job(test, result):
1427 def job(test, result):
1426 try:
1428 try:
1427 test(result)
1429 test(result)
1428 done.put(None)
1430 done.put(None)
1429 except KeyboardInterrupt:
1431 except KeyboardInterrupt:
1430 pass
1432 pass
1431 except: # re-raises
1433 except: # re-raises
1432 done.put(('!', test, 'run-test raised an error, see traceback'))
1434 done.put(('!', test, 'run-test raised an error, see traceback'))
1433 raise
1435 raise
1434
1436
1435 stoppedearly = False
1437 stoppedearly = False
1436
1438
1437 try:
1439 try:
1438 while tests or running:
1440 while tests or running:
1439 if not done.empty() or running == self._jobs or not tests:
1441 if not done.empty() or running == self._jobs or not tests:
1440 try:
1442 try:
1441 done.get(True, 1)
1443 done.get(True, 1)
1442 running -= 1
1444 running -= 1
1443 if result and result.shouldStop:
1445 if result and result.shouldStop:
1444 stoppedearly = True
1446 stoppedearly = True
1445 break
1447 break
1446 except queue.Empty:
1448 except queue.Empty:
1447 continue
1449 continue
1448 if tests and not running == self._jobs:
1450 if tests and not running == self._jobs:
1449 test = tests.pop(0)
1451 test = tests.pop(0)
1450 if self._loop:
1452 if self._loop:
1451 if getattr(test, 'should_reload', False):
1453 if getattr(test, 'should_reload', False):
1452 num_tests[0] += 1
1454 num_tests[0] += 1
1453 tests.append(
1455 tests.append(
1454 self._loadtest(test.name, num_tests[0]))
1456 self._loadtest(test.name, num_tests[0]))
1455 else:
1457 else:
1456 tests.append(test)
1458 tests.append(test)
1457 t = threading.Thread(target=job, name=test.name,
1459 t = threading.Thread(target=job, name=test.name,
1458 args=(test, result))
1460 args=(test, result))
1459 t.start()
1461 t.start()
1460 running += 1
1462 running += 1
1461
1463
1462 # 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
1463 # finish. Otherwise, there is a race between the test completing
1465 # finish. Otherwise, there is a race between the test completing
1464 # 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
1465 # test reporting incorrect.
1467 # test reporting incorrect.
1466 if stoppedearly:
1468 if stoppedearly:
1467 while running:
1469 while running:
1468 try:
1470 try:
1469 done.get(True, 1)
1471 done.get(True, 1)
1470 running -= 1
1472 running -= 1
1471 except queue.Empty:
1473 except queue.Empty:
1472 continue
1474 continue
1473 except KeyboardInterrupt:
1475 except KeyboardInterrupt:
1474 for test in runtests:
1476 for test in runtests:
1475 test.abort()
1477 test.abort()
1476
1478
1477 return result
1479 return result
1478
1480
1479 class TextTestRunner(unittest.TextTestRunner):
1481 class TextTestRunner(unittest.TextTestRunner):
1480 """Custom unittest test runner that uses appropriate settings."""
1482 """Custom unittest test runner that uses appropriate settings."""
1481
1483
1482 def __init__(self, runner, *args, **kwargs):
1484 def __init__(self, runner, *args, **kwargs):
1483 super(TextTestRunner, self).__init__(*args, **kwargs)
1485 super(TextTestRunner, self).__init__(*args, **kwargs)
1484
1486
1485 self._runner = runner
1487 self._runner = runner
1486
1488
1487 def run(self, test):
1489 def run(self, test):
1488 result = TestResult(self._runner.options, self.stream,
1490 result = TestResult(self._runner.options, self.stream,
1489 self.descriptions, self.verbosity)
1491 self.descriptions, self.verbosity)
1490
1492
1491 test(result)
1493 test(result)
1492
1494
1493 failed = len(result.failures)
1495 failed = len(result.failures)
1494 warned = len(result.warned)
1496 warned = len(result.warned)
1495 skipped = len(result.skipped)
1497 skipped = len(result.skipped)
1496 ignored = len(result.ignored)
1498 ignored = len(result.ignored)
1497
1499
1498 iolock.acquire()
1500 iolock.acquire()
1499 self.stream.writeln('')
1501 self.stream.writeln('')
1500
1502
1501 if not self._runner.options.noskips:
1503 if not self._runner.options.noskips:
1502 for test, msg in result.skipped:
1504 for test, msg in result.skipped:
1503 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1505 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1504 for test, msg in result.warned:
1506 for test, msg in result.warned:
1505 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1507 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1506 for test, msg in result.failures:
1508 for test, msg in result.failures:
1507 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1509 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1508 for test, msg in result.errors:
1510 for test, msg in result.errors:
1509 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1511 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1510
1512
1511 if self._runner.options.xunit:
1513 if self._runner.options.xunit:
1512 xuf = open(self._runner.options.xunit, 'wb')
1514 xuf = open(self._runner.options.xunit, 'wb')
1513 try:
1515 try:
1514 timesd = dict((t[0], t[3]) for t in result.times)
1516 timesd = dict((t[0], t[3]) for t in result.times)
1515 doc = minidom.Document()
1517 doc = minidom.Document()
1516 s = doc.createElement('testsuite')
1518 s = doc.createElement('testsuite')
1517 s.setAttribute('name', 'run-tests')
1519 s.setAttribute('name', 'run-tests')
1518 s.setAttribute('tests', str(result.testsRun))
1520 s.setAttribute('tests', str(result.testsRun))
1519 s.setAttribute('errors', "0") # TODO
1521 s.setAttribute('errors', "0") # TODO
1520 s.setAttribute('failures', str(failed))
1522 s.setAttribute('failures', str(failed))
1521 s.setAttribute('skipped', str(skipped + ignored))
1523 s.setAttribute('skipped', str(skipped + ignored))
1522 doc.appendChild(s)
1524 doc.appendChild(s)
1523 for tc in result.successes:
1525 for tc in result.successes:
1524 t = doc.createElement('testcase')
1526 t = doc.createElement('testcase')
1525 t.setAttribute('name', tc.name)
1527 t.setAttribute('name', tc.name)
1526 t.setAttribute('time', '%.3f' % timesd[tc.name])
1528 t.setAttribute('time', '%.3f' % timesd[tc.name])
1527 s.appendChild(t)
1529 s.appendChild(t)
1528 for tc, err in sorted(result.faildata.iteritems()):
1530 for tc, err in sorted(result.faildata.iteritems()):
1529 t = doc.createElement('testcase')
1531 t = doc.createElement('testcase')
1530 t.setAttribute('name', tc)
1532 t.setAttribute('name', tc)
1531 t.setAttribute('time', '%.3f' % timesd[tc])
1533 t.setAttribute('time', '%.3f' % timesd[tc])
1532 # createCDATASection expects a unicode or it will convert
1534 # createCDATASection expects a unicode or it will convert
1533 # using default conversion rules, which will fail if
1535 # using default conversion rules, which will fail if
1534 # string isn't ASCII.
1536 # string isn't ASCII.
1535 err = cdatasafe(err).decode('utf-8', 'replace')
1537 err = cdatasafe(err).decode('utf-8', 'replace')
1536 cd = doc.createCDATASection(err)
1538 cd = doc.createCDATASection(err)
1537 t.appendChild(cd)
1539 t.appendChild(cd)
1538 s.appendChild(t)
1540 s.appendChild(t)
1539 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1541 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1540 finally:
1542 finally:
1541 xuf.close()
1543 xuf.close()
1542
1544
1543 if self._runner.options.json:
1545 if self._runner.options.json:
1544 if json is None:
1546 if json is None:
1545 raise ImportError("json module not installed")
1547 raise ImportError("json module not installed")
1546 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1548 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1547 fp = open(jsonpath, 'w')
1549 fp = open(jsonpath, 'w')
1548 try:
1550 try:
1549 timesd = {}
1551 timesd = {}
1550 for tdata in result.times:
1552 for tdata in result.times:
1551 test = tdata[0]
1553 test = tdata[0]
1552 timesd[test] = tdata[1:]
1554 timesd[test] = tdata[1:]
1553
1555
1554 outcome = {}
1556 outcome = {}
1555 groups = [('success', ((tc, None) for tc in result.successes)),
1557 groups = [('success', ((tc, None) for tc in result.successes)),
1556 ('failure', result.failures),
1558 ('failure', result.failures),
1557 ('skip', result.skipped)]
1559 ('skip', result.skipped)]
1558 for res, testcases in groups:
1560 for res, testcases in groups:
1559 for tc, __ in testcases:
1561 for tc, __ in testcases:
1560 testresult = {'result': res,
1562 testresult = {'result': res,
1561 'time': ('%0.3f' % timesd[tc.name][2]),
1563 'time': ('%0.3f' % timesd[tc.name][2]),
1562 'cuser': ('%0.3f' % timesd[tc.name][0]),
1564 'cuser': ('%0.3f' % timesd[tc.name][0]),
1563 'csys': ('%0.3f' % timesd[tc.name][1])}
1565 'csys': ('%0.3f' % timesd[tc.name][1])}
1564 outcome[tc.name] = testresult
1566 outcome[tc.name] = testresult
1565
1567
1566 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1568 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1567 fp.writelines(("testreport =", jsonout))
1569 fp.writelines(("testreport =", jsonout))
1568 finally:
1570 finally:
1569 fp.close()
1571 fp.close()
1570
1572
1571 self._runner._checkhglib('Tested')
1573 self._runner._checkhglib('Tested')
1572
1574
1573 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.'
1574 % (result.testsRun,
1576 % (result.testsRun,
1575 skipped + ignored, warned, failed))
1577 skipped + ignored, warned, failed))
1576 if failed:
1578 if failed:
1577 self.stream.writeln('python hash seed: %s' %
1579 self.stream.writeln('python hash seed: %s' %
1578 os.environ['PYTHONHASHSEED'])
1580 os.environ['PYTHONHASHSEED'])
1579 if self._runner.options.time:
1581 if self._runner.options.time:
1580 self.printtimes(result.times)
1582 self.printtimes(result.times)
1581
1583
1582 iolock.release()
1584 iolock.release()
1583
1585
1584 return result
1586 return result
1585
1587
1586 def printtimes(self, times):
1588 def printtimes(self, times):
1587 # iolock held by run
1589 # iolock held by run
1588 self.stream.writeln('# Producing time report')
1590 self.stream.writeln('# Producing time report')
1589 times.sort(key=lambda t: (t[3]))
1591 times.sort(key=lambda t: (t[3]))
1590 cols = '%7.3f %7.3f %7.3f %s'
1592 cols = '%7.3f %7.3f %7.3f %s'
1591 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1593 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1592 'Test'))
1594 'Test'))
1593 for tdata in times:
1595 for tdata in times:
1594 test = tdata[0]
1596 test = tdata[0]
1595 cuser, csys, real = tdata[1:4]
1597 cuser, csys, real = tdata[1:4]
1596 self.stream.writeln(cols % (cuser, csys, real, test))
1598 self.stream.writeln(cols % (cuser, csys, real, test))
1597
1599
1598 class TestRunner(object):
1600 class TestRunner(object):
1599 """Holds context for executing tests.
1601 """Holds context for executing tests.
1600
1602
1601 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.
1602 """
1604 """
1603
1605
1604 # Programs required to run tests.
1606 # Programs required to run tests.
1605 REQUIREDTOOLS = [
1607 REQUIREDTOOLS = [
1606 os.path.basename(sys.executable),
1608 os.path.basename(sys.executable).encode('utf-8'),
1607 'diff',
1609 b'diff',
1608 'grep',
1610 b'grep',
1609 'unzip',
1611 b'unzip',
1610 'gunzip',
1612 b'gunzip',
1611 'bunzip2',
1613 b'bunzip2',
1612 'sed',
1614 b'sed',
1613 ]
1615 ]
1614
1616
1615 # Maps file extensions to test class.
1617 # Maps file extensions to test class.
1616 TESTTYPES = [
1618 TESTTYPES = [
1617 ('.py', PythonTest),
1619 (b'.py', PythonTest),
1618 ('.t', TTest),
1620 (b'.t', TTest),
1619 ]
1621 ]
1620
1622
1621 def __init__(self):
1623 def __init__(self):
1622 self.options = None
1624 self.options = None
1623 self._hgroot = None
1625 self._hgroot = None
1624 self._testdir = None
1626 self._testdir = None
1625 self._hgtmp = None
1627 self._hgtmp = None
1626 self._installdir = None
1628 self._installdir = None
1627 self._bindir = None
1629 self._bindir = None
1628 self._tmpbinddir = None
1630 self._tmpbinddir = None
1629 self._pythondir = None
1631 self._pythondir = None
1630 self._coveragefile = None
1632 self._coveragefile = None
1631 self._createdfiles = []
1633 self._createdfiles = []
1632 self._hgpath = None
1634 self._hgpath = None
1633 self._portoffset = 0
1635 self._portoffset = 0
1634 self._ports = {}
1636 self._ports = {}
1635
1637
1636 def run(self, args, parser=None):
1638 def run(self, args, parser=None):
1637 """Run the test suite."""
1639 """Run the test suite."""
1638 oldmask = os.umask(0o22)
1640 oldmask = os.umask(0o22)
1639 try:
1641 try:
1640 parser = parser or getparser()
1642 parser = parser or getparser()
1641 options, args = parseargs(args, parser)
1643 options, args = parseargs(args, parser)
1644 args = [a.encode('utf-8') for a in args]
1642 self.options = options
1645 self.options = options
1643
1646
1644 self._checktools()
1647 self._checktools()
1645 tests = self.findtests(args)
1648 tests = self.findtests(args)
1646 return self._run(tests)
1649 return self._run(tests)
1647 finally:
1650 finally:
1648 os.umask(oldmask)
1651 os.umask(oldmask)
1649
1652
1650 def _run(self, tests):
1653 def _run(self, tests):
1651 if self.options.random:
1654 if self.options.random:
1652 random.shuffle(tests)
1655 random.shuffle(tests)
1653 else:
1656 else:
1654 # keywords for slow tests
1657 # keywords for slow tests
1655 slow = 'svn gendoc check-code-hg'.split()
1658 slow = b'svn gendoc check-code-hg'.split()
1656 def sortkey(f):
1659 def sortkey(f):
1657 # run largest tests first, as they tend to take the longest
1660 # run largest tests first, as they tend to take the longest
1658 try:
1661 try:
1659 val = -os.stat(f).st_size
1662 val = -os.stat(f).st_size
1660 except OSError as e:
1663 except OSError as e:
1661 if e.errno != errno.ENOENT:
1664 if e.errno != errno.ENOENT:
1662 raise
1665 raise
1663 return -1e9 # file does not exist, tell early
1666 return -1e9 # file does not exist, tell early
1664 for kw in slow:
1667 for kw in slow:
1665 if kw in f:
1668 if kw in f:
1666 val *= 10
1669 val *= 10
1667 return val
1670 return val
1668 tests.sort(key=sortkey)
1671 tests.sort(key=sortkey)
1669
1672
1670 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1673 self._testdir = osenvironb[b'TESTDIR'] = getattr(
1674 os, 'getcwdb', os.getcwd)()
1671
1675
1672 if 'PYTHONHASHSEED' not in os.environ:
1676 if 'PYTHONHASHSEED' not in os.environ:
1673 # use a random python hash seed all the time
1677 # use a random python hash seed all the time
1674 # we do the randomness ourself to know what seed is used
1678 # we do the randomness ourself to know what seed is used
1675 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1679 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1676
1680
1677 if self.options.tmpdir:
1681 if self.options.tmpdir:
1678 self.options.keep_tmpdir = True
1682 self.options.keep_tmpdir = True
1679 tmpdir = self.options.tmpdir
1683 tmpdir = self.options.tmpdir.encode('utf-8')
1680 if os.path.exists(tmpdir):
1684 if os.path.exists(tmpdir):
1681 # 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
1682 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1686 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1683 # tmpdir already exists.
1687 # tmpdir already exists.
1684 print("error: temp dir %r already exists" % tmpdir)
1688 print("error: temp dir %r already exists" % tmpdir)
1685 return 1
1689 return 1
1686
1690
1687 # Automatically removing tmpdir sounds convenient, but could
1691 # Automatically removing tmpdir sounds convenient, but could
1688 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1692 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1689 # or "--tmpdir=$HOME".
1693 # or "--tmpdir=$HOME".
1690 #vlog("# Removing temp dir", tmpdir)
1694 #vlog("# Removing temp dir", tmpdir)
1691 #shutil.rmtree(tmpdir)
1695 #shutil.rmtree(tmpdir)
1692 os.makedirs(tmpdir)
1696 os.makedirs(tmpdir)
1693 else:
1697 else:
1694 d = None
1698 d = None
1695 if os.name == 'nt':
1699 if os.name == 'nt':
1696 # without this, we get the default temp dir location, but
1700 # without this, we get the default temp dir location, but
1697 # in all lowercase, which causes troubles with paths (issue3490)
1701 # in all lowercase, which causes troubles with paths (issue3490)
1698 d = os.getenv('TMP')
1702 d = osenvironb.get(b'TMP', None)
1699 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1703 # FILE BUG: mkdtemp works only on unicode in Python 3
1700 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1704 tmpdir = tempfile.mkdtemp('', 'hgtests.',
1705 d and d.decode('utf-8')).encode('utf-8')
1706
1707 self._hgtmp = osenvironb[b'HGTMP'] = (
1708 os.path.realpath(tmpdir))
1701
1709
1702 if self.options.with_hg:
1710 if self.options.with_hg:
1703 self._installdir = None
1711 self._installdir = None
1704 self._bindir = os.path.dirname(os.path.realpath(
1712 self._bindir = os.path.dirname(os.path.realpath(
1705 self.options.with_hg))
1713 self.options.with_hg))
1706 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1714 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
1707 os.makedirs(self._tmpbindir)
1715 os.makedirs(self._tmpbindir)
1708
1716
1709 # This looks redundant with how Python initializes sys.path from
1717 # This looks redundant with how Python initializes sys.path from
1710 # the location of the script being executed. Needed because the
1718 # the location of the script being executed. Needed because the
1711 # "hg" specified by --with-hg is not the only Python script
1719 # "hg" specified by --with-hg is not the only Python script
1712 # executed in the test suite that needs to import 'mercurial'
1720 # executed in the test suite that needs to import 'mercurial'
1713 # ... which means it's not really redundant at all.
1721 # ... which means it's not really redundant at all.
1714 self._pythondir = self._bindir
1722 self._pythondir = self._bindir
1715 else:
1723 else:
1716 self._installdir = os.path.join(self._hgtmp, "install")
1724 self._installdir = os.path.join(self._hgtmp, b"install")
1717 self._bindir = os.environ["BINDIR"] = \
1725 self._bindir = osenvironb[b"BINDIR"] = \
1718 os.path.join(self._installdir, "bin")
1726 os.path.join(self._installdir, b"bin")
1719 self._tmpbindir = self._bindir
1727 self._tmpbindir = self._bindir
1720 self._pythondir = os.path.join(self._installdir, "lib", "python")
1728 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
1721
1729
1722 os.environ["BINDIR"] = self._bindir
1730 osenvironb[b"BINDIR"] = self._bindir
1723 os.environ["PYTHON"] = PYTHON
1731 os.environ["PYTHON"] = PYTHON
1724
1732
1725 runtestdir = os.path.abspath(os.path.dirname(__file__))
1733 fileb = __file__.encode('utf-8')
1726 path = [self._bindir, runtestdir] + os.environ["PATH"].split(os.pathsep)
1734 runtestdir = os.path.abspath(os.path.dirname(fileb))
1735 if sys.version_info[0] == 3:
1736 sepb = os.pathsep.encode('utf-8')
1737 else:
1738 sepb = os.pathsep
1739 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
1727 if os.path.islink(__file__):
1740 if os.path.islink(__file__):
1728 # test helper will likely be at the end of the symlink
1741 # test helper will likely be at the end of the symlink
1729 realfile = os.path.realpath(__file__)
1742 realfile = os.path.realpath(fileb)
1730 realdir = os.path.abspath(os.path.dirname(realfile))
1743 realdir = os.path.abspath(os.path.dirname(realfile))
1731 path.insert(2, realdir)
1744 path.insert(2, realdir)
1732 if self._tmpbindir != self._bindir:
1745 if self._tmpbindir != self._bindir:
1733 path = [self._tmpbindir] + path
1746 path = [self._tmpbindir] + path
1734 os.environ["PATH"] = os.pathsep.join(path)
1747 osenvironb[b"PATH"] = sepb.join(path)
1735
1748
1736 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1749 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1737 # can run .../tests/run-tests.py test-foo where test-foo
1750 # can run .../tests/run-tests.py test-foo where test-foo
1738 # adds an extension to HGRC. Also include run-test.py directory to
1751 # adds an extension to HGRC. Also include run-test.py directory to
1739 # import modules like heredoctest.
1752 # import modules like heredoctest.
1740 pypath = [self._pythondir, self._testdir, runtestdir]
1753 pypath = [self._pythondir, self._testdir, runtestdir]
1741 # We have to augment PYTHONPATH, rather than simply replacing
1754 # We have to augment PYTHONPATH, rather than simply replacing
1742 # it, in case external libraries are only available via current
1755 # it, in case external libraries are only available via current
1743 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1756 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1744 # are in /opt/subversion.)
1757 # are in /opt/subversion.)
1745 oldpypath = os.environ.get(IMPL_PATH)
1758 oldpypath = osenvironb.get(IMPL_PATH)
1746 if oldpypath:
1759 if oldpypath:
1747 pypath.append(oldpypath)
1760 pypath.append(oldpypath)
1748 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1761 osenvironb[IMPL_PATH] = sepb.join(pypath)
1749
1762
1750 if self.options.pure:
1763 if self.options.pure:
1751 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
1764 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
1752
1765
1753 self._coveragefile = os.path.join(self._testdir, '.coverage')
1766 self._coveragefile = os.path.join(self._testdir, b'.coverage')
1754
1767
1755 vlog("# Using TESTDIR", self._testdir)
1768 vlog("# Using TESTDIR", self._testdir)
1756 vlog("# Using HGTMP", self._hgtmp)
1769 vlog("# Using HGTMP", self._hgtmp)
1757 vlog("# Using PATH", os.environ["PATH"])
1770 vlog("# Using PATH", os.environ["PATH"])
1758 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1771 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
1759
1772
1760 try:
1773 try:
1761 return self._runtests(tests) or 0
1774 return self._runtests(tests) or 0
1762 finally:
1775 finally:
1763 time.sleep(.1)
1776 time.sleep(.1)
1764 self._cleanup()
1777 self._cleanup()
1765
1778
1766 def findtests(self, args):
1779 def findtests(self, args):
1767 """Finds possible test files from arguments.
1780 """Finds possible test files from arguments.
1768
1781
1769 If you wish to inject custom tests into the test harness, this would
1782 If you wish to inject custom tests into the test harness, this would
1770 be a good function to monkeypatch or override in a derived class.
1783 be a good function to monkeypatch or override in a derived class.
1771 """
1784 """
1772 if not args:
1785 if not args:
1773 if self.options.changed:
1786 if self.options.changed:
1774 proc = Popen4('hg st --rev "%s" -man0 .' %
1787 proc = Popen4('hg st --rev "%s" -man0 .' %
1775 self.options.changed, None, 0)
1788 self.options.changed, None, 0)
1776 stdout, stderr = proc.communicate()
1789 stdout, stderr = proc.communicate()
1777 args = stdout.strip('\0').split('\0')
1790 args = stdout.strip(b'\0').split(b'\0')
1778 else:
1791 else:
1779 args = os.listdir('.')
1792 args = os.listdir(b'.')
1780
1793
1781 return [t for t in args
1794 return [t for t in args
1782 if os.path.basename(t).startswith('test-')
1795 if os.path.basename(t).startswith(b'test-')
1783 and (t.endswith('.py') or t.endswith('.t'))]
1796 and (t.endswith(b'.py') or t.endswith(b'.t'))]
1784
1797
1785 def _runtests(self, tests):
1798 def _runtests(self, tests):
1786 try:
1799 try:
1787 if self._installdir:
1800 if self._installdir:
1788 self._installhg()
1801 self._installhg()
1789 self._checkhglib("Testing")
1802 self._checkhglib("Testing")
1790 else:
1803 else:
1791 self._usecorrectpython()
1804 self._usecorrectpython()
1792
1805
1793 if self.options.restart:
1806 if self.options.restart:
1794 orig = list(tests)
1807 orig = list(tests)
1795 while tests:
1808 while tests:
1796 if os.path.exists(tests[0] + ".err"):
1809 if os.path.exists(tests[0] + ".err"):
1797 break
1810 break
1798 tests.pop(0)
1811 tests.pop(0)
1799 if not tests:
1812 if not tests:
1800 print("running all tests")
1813 print("running all tests")
1801 tests = orig
1814 tests = orig
1802
1815
1803 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1816 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1804
1817
1805 failed = False
1818 failed = False
1806 warned = False
1819 warned = False
1807
1820
1808 suite = TestSuite(self._testdir,
1821 suite = TestSuite(self._testdir,
1809 jobs=self.options.jobs,
1822 jobs=self.options.jobs,
1810 whitelist=self.options.whitelisted,
1823 whitelist=self.options.whitelisted,
1811 blacklist=self.options.blacklist,
1824 blacklist=self.options.blacklist,
1812 retest=self.options.retest,
1825 retest=self.options.retest,
1813 keywords=self.options.keywords,
1826 keywords=self.options.keywords,
1814 loop=self.options.loop,
1827 loop=self.options.loop,
1815 runs_per_test=self.options.runs_per_test,
1828 runs_per_test=self.options.runs_per_test,
1816 tests=tests, loadtest=self._gettest)
1829 tests=tests, loadtest=self._gettest)
1817 verbosity = 1
1830 verbosity = 1
1818 if self.options.verbose:
1831 if self.options.verbose:
1819 verbosity = 2
1832 verbosity = 2
1820 runner = TextTestRunner(self, verbosity=verbosity)
1833 runner = TextTestRunner(self, verbosity=verbosity)
1821 result = runner.run(suite)
1834 result = runner.run(suite)
1822
1835
1823 if result.failures:
1836 if result.failures:
1824 failed = True
1837 failed = True
1825 if result.warned:
1838 if result.warned:
1826 warned = True
1839 warned = True
1827
1840
1828 if self.options.anycoverage:
1841 if self.options.anycoverage:
1829 self._outputcoverage()
1842 self._outputcoverage()
1830 except KeyboardInterrupt:
1843 except KeyboardInterrupt:
1831 failed = True
1844 failed = True
1832 print("\ninterrupted!")
1845 print("\ninterrupted!")
1833
1846
1834 if failed:
1847 if failed:
1835 return 1
1848 return 1
1836 if warned:
1849 if warned:
1837 return 80
1850 return 80
1838
1851
1839 def _getport(self, count):
1852 def _getport(self, count):
1840 port = self._ports.get(count) # do we have a cached entry?
1853 port = self._ports.get(count) # do we have a cached entry?
1841 if port is None:
1854 if port is None:
1842 port = self.options.port + self._portoffset
1855 port = self.options.port + self._portoffset
1843 portneeded = 3
1856 portneeded = 3
1844 # above 100 tries we just give up and let test reports failure
1857 # above 100 tries we just give up and let test reports failure
1845 for tries in xrange(100):
1858 for tries in xrange(100):
1846 allfree = True
1859 allfree = True
1847 for idx in xrange(portneeded):
1860 for idx in xrange(portneeded):
1848 if not checkportisavailable(port + idx):
1861 if not checkportisavailable(port + idx):
1849 allfree = False
1862 allfree = False
1850 break
1863 break
1851 self._portoffset += portneeded
1864 self._portoffset += portneeded
1852 if allfree:
1865 if allfree:
1853 break
1866 break
1854 self._ports[count] = port
1867 self._ports[count] = port
1855 return port
1868 return port
1856
1869
1857 def _gettest(self, test, count):
1870 def _gettest(self, test, count):
1858 """Obtain a Test by looking at its filename.
1871 """Obtain a Test by looking at its filename.
1859
1872
1860 Returns a Test instance. The Test may not be runnable if it doesn't
1873 Returns a Test instance. The Test may not be runnable if it doesn't
1861 map to a known type.
1874 map to a known type.
1862 """
1875 """
1863 lctest = test.lower()
1876 lctest = test.lower()
1864 testcls = Test
1877 testcls = Test
1865
1878
1866 for ext, cls in self.TESTTYPES:
1879 for ext, cls in self.TESTTYPES:
1867 if lctest.endswith(ext):
1880 if lctest.endswith(ext):
1868 testcls = cls
1881 testcls = cls
1869 break
1882 break
1870
1883
1871 refpath = os.path.join(self._testdir, test)
1884 refpath = os.path.join(self._testdir, test)
1872 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1885 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
1873
1886
1874 t = testcls(refpath, tmpdir,
1887 t = testcls(refpath, tmpdir,
1875 keeptmpdir=self.options.keep_tmpdir,
1888 keeptmpdir=self.options.keep_tmpdir,
1876 debug=self.options.debug,
1889 debug=self.options.debug,
1877 timeout=self.options.timeout,
1890 timeout=self.options.timeout,
1878 startport=self._getport(count),
1891 startport=self._getport(count),
1879 extraconfigopts=self.options.extra_config_opt,
1892 extraconfigopts=self.options.extra_config_opt,
1880 py3kwarnings=self.options.py3k_warnings,
1893 py3kwarnings=self.options.py3k_warnings,
1881 shell=self.options.shell)
1894 shell=self.options.shell)
1882 t.should_reload = True
1895 t.should_reload = True
1883 return t
1896 return t
1884
1897
1885 def _cleanup(self):
1898 def _cleanup(self):
1886 """Clean up state from this test invocation."""
1899 """Clean up state from this test invocation."""
1887
1900
1888 if self.options.keep_tmpdir:
1901 if self.options.keep_tmpdir:
1889 return
1902 return
1890
1903
1891 vlog("# Cleaning up HGTMP", self._hgtmp)
1904 vlog("# Cleaning up HGTMP", self._hgtmp)
1892 shutil.rmtree(self._hgtmp, True)
1905 shutil.rmtree(self._hgtmp, True)
1893 for f in self._createdfiles:
1906 for f in self._createdfiles:
1894 try:
1907 try:
1895 os.remove(f)
1908 os.remove(f)
1896 except OSError:
1909 except OSError:
1897 pass
1910 pass
1898
1911
1899 def _usecorrectpython(self):
1912 def _usecorrectpython(self):
1900 """Configure the environment to use the appropriate Python in tests."""
1913 """Configure the environment to use the appropriate Python in tests."""
1901 # Tests must use the same interpreter as us or bad things will happen.
1914 # Tests must use the same interpreter as us or bad things will happen.
1902 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1915 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
1903 if getattr(os, 'symlink', None):
1916 if getattr(os, 'symlink', None):
1904 vlog("# Making python executable in test path a symlink to '%s'" %
1917 vlog("# Making python executable in test path a symlink to '%s'" %
1905 sys.executable)
1918 sys.executable)
1906 mypython = os.path.join(self._tmpbindir, pyexename)
1919 mypython = os.path.join(self._tmpbindir, pyexename)
1907 try:
1920 try:
1908 if os.readlink(mypython) == sys.executable:
1921 if os.readlink(mypython) == sys.executable:
1909 return
1922 return
1910 os.unlink(mypython)
1923 os.unlink(mypython)
1911 except OSError as err:
1924 except OSError as err:
1912 if err.errno != errno.ENOENT:
1925 if err.errno != errno.ENOENT:
1913 raise
1926 raise
1914 if self._findprogram(pyexename) != sys.executable:
1927 if self._findprogram(pyexename) != sys.executable:
1915 try:
1928 try:
1916 os.symlink(sys.executable, mypython)
1929 os.symlink(sys.executable, mypython)
1917 self._createdfiles.append(mypython)
1930 self._createdfiles.append(mypython)
1918 except OSError as err:
1931 except OSError as err:
1919 # child processes may race, which is harmless
1932 # child processes may race, which is harmless
1920 if err.errno != errno.EEXIST:
1933 if err.errno != errno.EEXIST:
1921 raise
1934 raise
1922 else:
1935 else:
1923 exedir, exename = os.path.split(sys.executable)
1936 exedir, exename = os.path.split(sys.executable)
1924 vlog("# Modifying search path to find %s as %s in '%s'" %
1937 vlog("# Modifying search path to find %s as %s in '%s'" %
1925 (exename, pyexename, exedir))
1938 (exename, pyexename, exedir))
1926 path = os.environ['PATH'].split(os.pathsep)
1939 path = os.environ['PATH'].split(os.pathsep)
1927 while exedir in path:
1940 while exedir in path:
1928 path.remove(exedir)
1941 path.remove(exedir)
1929 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1942 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1930 if not self._findprogram(pyexename):
1943 if not self._findprogram(pyexename):
1931 print("WARNING: Cannot find %s in search path" % pyexename)
1944 print("WARNING: Cannot find %s in search path" % pyexename)
1932
1945
1933 def _installhg(self):
1946 def _installhg(self):
1934 """Install hg into the test environment.
1947 """Install hg into the test environment.
1935
1948
1936 This will also configure hg with the appropriate testing settings.
1949 This will also configure hg with the appropriate testing settings.
1937 """
1950 """
1938 vlog("# Performing temporary installation of HG")
1951 vlog("# Performing temporary installation of HG")
1939 installerrs = os.path.join("tests", "install.err")
1952 installerrs = os.path.join("tests", "install.err")
1940 compiler = ''
1953 compiler = ''
1941 if self.options.compiler:
1954 if self.options.compiler:
1942 compiler = '--compiler ' + self.options.compiler
1955 compiler = '--compiler ' + self.options.compiler
1943 if self.options.pure:
1956 if self.options.pure:
1944 pure = "--pure"
1957 pure = "--pure"
1945 else:
1958 else:
1946 pure = ""
1959 pure = ""
1947 py3 = ''
1960 py3 = ''
1948 if sys.version_info[0] == 3:
1961 if sys.version_info[0] == 3:
1949 py3 = '--c2to3'
1962 py3 = '--c2to3'
1950
1963
1951 # Run installer in hg root
1964 # Run installer in hg root
1952 script = os.path.realpath(sys.argv[0])
1965 script = os.path.realpath(sys.argv[0])
1953 hgroot = os.path.dirname(os.path.dirname(script))
1966 hgroot = os.path.dirname(os.path.dirname(script))
1954 self._hgroot = hgroot
1967 self._hgroot = hgroot
1955 os.chdir(hgroot)
1968 os.chdir(hgroot)
1956 nohome = '--home=""'
1969 nohome = '--home=""'
1957 if os.name == 'nt':
1970 if os.name == 'nt':
1958 # The --home="" trick works only on OS where os.sep == '/'
1971 # The --home="" trick works only on OS where os.sep == '/'
1959 # because of a distutils convert_path() fast-path. Avoid it at
1972 # because of a distutils convert_path() fast-path. Avoid it at
1960 # least on Windows for now, deal with .pydistutils.cfg bugs
1973 # least on Windows for now, deal with .pydistutils.cfg bugs
1961 # when they happen.
1974 # when they happen.
1962 nohome = ''
1975 nohome = ''
1963 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1976 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1964 ' build %(compiler)s --build-base="%(base)s"'
1977 ' build %(compiler)s --build-base="%(base)s"'
1965 ' install --force --prefix="%(prefix)s"'
1978 ' install --force --prefix="%(prefix)s"'
1966 ' --install-lib="%(libdir)s"'
1979 ' --install-lib="%(libdir)s"'
1967 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1980 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1968 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1981 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1969 'compiler': compiler,
1982 'compiler': compiler,
1970 'base': os.path.join(self._hgtmp, "build"),
1983 'base': os.path.join(self._hgtmp, b"build"),
1971 'prefix': self._installdir, 'libdir': self._pythondir,
1984 'prefix': self._installdir, 'libdir': self._pythondir,
1972 'bindir': self._bindir,
1985 'bindir': self._bindir,
1973 'nohome': nohome, 'logfile': installerrs})
1986 'nohome': nohome, 'logfile': installerrs})
1974
1987
1975 # setuptools requires install directories to exist.
1988 # setuptools requires install directories to exist.
1976 def makedirs(p):
1989 def makedirs(p):
1977 try:
1990 try:
1978 os.makedirs(p)
1991 os.makedirs(p)
1979 except OSError as e:
1992 except OSError as e:
1980 if e.errno != errno.EEXIST:
1993 if e.errno != errno.EEXIST:
1981 raise
1994 raise
1982 makedirs(self._pythondir)
1995 makedirs(self._pythondir)
1983 makedirs(self._bindir)
1996 makedirs(self._bindir)
1984
1997
1985 vlog("# Running", cmd)
1998 vlog("# Running", cmd)
1986 if os.system(cmd) == 0:
1999 if os.system(cmd) == 0:
1987 if not self.options.verbose:
2000 if not self.options.verbose:
1988 os.remove(installerrs)
2001 os.remove(installerrs)
1989 else:
2002 else:
1990 f = open(installerrs, 'rb')
2003 f = open(installerrs, 'rb')
1991 for line in f:
2004 for line in f:
1992 if sys.version_info[0] > 2:
2005 if sys.version_info[0] > 2:
1993 sys.stdout.buffer.write(line)
2006 sys.stdout.buffer.write(line)
1994 else:
2007 else:
1995 sys.stdout.write(line)
2008 sys.stdout.write(line)
1996 f.close()
2009 f.close()
1997 sys.exit(1)
2010 sys.exit(1)
1998 os.chdir(self._testdir)
2011 os.chdir(self._testdir)
1999
2012
2000 self._usecorrectpython()
2013 self._usecorrectpython()
2001
2014
2002 if self.options.py3k_warnings and not self.options.anycoverage:
2015 if self.options.py3k_warnings and not self.options.anycoverage:
2003 vlog("# Updating hg command to enable Py3k Warnings switch")
2016 vlog("# Updating hg command to enable Py3k Warnings switch")
2004 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2017 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2005 lines = [line.rstrip() for line in f]
2018 lines = [line.rstrip() for line in f]
2006 lines[0] += ' -3'
2019 lines[0] += ' -3'
2007 f.close()
2020 f.close()
2008 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2021 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2009 for line in lines:
2022 for line in lines:
2010 f.write(line + '\n')
2023 f.write(line + '\n')
2011 f.close()
2024 f.close()
2012
2025
2013 hgbat = os.path.join(self._bindir, 'hg.bat')
2026 hgbat = os.path.join(self._bindir, 'hg.bat')
2014 if os.path.isfile(hgbat):
2027 if os.path.isfile(hgbat):
2015 # hg.bat expects to be put in bin/scripts while run-tests.py
2028 # hg.bat expects to be put in bin/scripts while run-tests.py
2016 # installation layout put it in bin/ directly. Fix it
2029 # installation layout put it in bin/ directly. Fix it
2017 f = open(hgbat, 'rb')
2030 f = open(hgbat, 'rb')
2018 data = f.read()
2031 data = f.read()
2019 f.close()
2032 f.close()
2020 if '"%~dp0..\python" "%~dp0hg" %*' in data:
2033 if '"%~dp0..\python" "%~dp0hg" %*' in data:
2021 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
2034 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
2022 '"%~dp0python" "%~dp0hg" %*')
2035 '"%~dp0python" "%~dp0hg" %*')
2023 f = open(hgbat, 'wb')
2036 f = open(hgbat, 'wb')
2024 f.write(data)
2037 f.write(data)
2025 f.close()
2038 f.close()
2026 else:
2039 else:
2027 print('WARNING: cannot fix hg.bat reference to python.exe')
2040 print('WARNING: cannot fix hg.bat reference to python.exe')
2028
2041
2029 if self.options.anycoverage:
2042 if self.options.anycoverage:
2030 custom = os.path.join(self._testdir, 'sitecustomize.py')
2043 custom = os.path.join(self._testdir, 'sitecustomize.py')
2031 target = os.path.join(self._pythondir, 'sitecustomize.py')
2044 target = os.path.join(self._pythondir, 'sitecustomize.py')
2032 vlog('# Installing coverage trigger to %s' % target)
2045 vlog('# Installing coverage trigger to %s' % target)
2033 shutil.copyfile(custom, target)
2046 shutil.copyfile(custom, target)
2034 rc = os.path.join(self._testdir, '.coveragerc')
2047 rc = os.path.join(self._testdir, '.coveragerc')
2035 vlog('# Installing coverage rc to %s' % rc)
2048 vlog('# Installing coverage rc to %s' % rc)
2036 os.environ['COVERAGE_PROCESS_START'] = rc
2049 os.environ['COVERAGE_PROCESS_START'] = rc
2037 covdir = os.path.join(self._installdir, '..', 'coverage')
2050 covdir = os.path.join(self._installdir, '..', 'coverage')
2038 try:
2051 try:
2039 os.mkdir(covdir)
2052 os.mkdir(covdir)
2040 except OSError as e:
2053 except OSError as e:
2041 if e.errno != errno.EEXIST:
2054 if e.errno != errno.EEXIST:
2042 raise
2055 raise
2043
2056
2044 os.environ['COVERAGE_DIR'] = covdir
2057 os.environ['COVERAGE_DIR'] = covdir
2045
2058
2046 def _checkhglib(self, verb):
2059 def _checkhglib(self, verb):
2047 """Ensure that the 'mercurial' package imported by python is
2060 """Ensure that the 'mercurial' package imported by python is
2048 the one we expect it to be. If not, print a warning to stderr."""
2061 the one we expect it to be. If not, print a warning to stderr."""
2049 if ((self._bindir == self._pythondir) and
2062 if ((self._bindir == self._pythondir) and
2050 (self._bindir != self._tmpbindir)):
2063 (self._bindir != self._tmpbindir)):
2051 # The pythondir has been inferred from --with-hg flag.
2064 # The pythondir has been inferred from --with-hg flag.
2052 # We cannot expect anything sensible here.
2065 # We cannot expect anything sensible here.
2053 return
2066 return
2054 expecthg = os.path.join(self._pythondir, 'mercurial')
2067 expecthg = os.path.join(self._pythondir, 'mercurial')
2055 actualhg = self._gethgpath()
2068 actualhg = self._gethgpath()
2056 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2069 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2057 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2070 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2058 ' (expected %s)\n'
2071 ' (expected %s)\n'
2059 % (verb, actualhg, expecthg))
2072 % (verb, actualhg, expecthg))
2060 def _gethgpath(self):
2073 def _gethgpath(self):
2061 """Return the path to the mercurial package that is actually found by
2074 """Return the path to the mercurial package that is actually found by
2062 the current Python interpreter."""
2075 the current Python interpreter."""
2063 if self._hgpath is not None:
2076 if self._hgpath is not None:
2064 return self._hgpath
2077 return self._hgpath
2065
2078
2066 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
2079 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
2067 pipe = os.popen(cmd % PYTHON)
2080 pipe = os.popen(cmd % PYTHON)
2068 try:
2081 try:
2069 self._hgpath = pipe.read().strip()
2082 self._hgpath = pipe.read().strip()
2070 finally:
2083 finally:
2071 pipe.close()
2084 pipe.close()
2072
2085
2073 return self._hgpath
2086 return self._hgpath
2074
2087
2075 def _outputcoverage(self):
2088 def _outputcoverage(self):
2076 """Produce code coverage output."""
2089 """Produce code coverage output."""
2077 from coverage import coverage
2090 from coverage import coverage
2078
2091
2079 vlog('# Producing coverage report')
2092 vlog('# Producing coverage report')
2080 # chdir is the easiest way to get short, relative paths in the
2093 # chdir is the easiest way to get short, relative paths in the
2081 # output.
2094 # output.
2082 os.chdir(self._hgroot)
2095 os.chdir(self._hgroot)
2083 covdir = os.path.join(self._installdir, '..', 'coverage')
2096 covdir = os.path.join(self._installdir, '..', 'coverage')
2084 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2097 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2085
2098
2086 # Map install directory paths back to source directory.
2099 # Map install directory paths back to source directory.
2087 cov.config.paths['srcdir'] = ['.', self._pythondir]
2100 cov.config.paths['srcdir'] = ['.', self._pythondir]
2088
2101
2089 cov.combine()
2102 cov.combine()
2090
2103
2091 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2104 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2092 cov.report(ignore_errors=True, omit=omit)
2105 cov.report(ignore_errors=True, omit=omit)
2093
2106
2094 if self.options.htmlcov:
2107 if self.options.htmlcov:
2095 htmldir = os.path.join(self._testdir, 'htmlcov')
2108 htmldir = os.path.join(self._testdir, 'htmlcov')
2096 cov.html_report(directory=htmldir, omit=omit)
2109 cov.html_report(directory=htmldir, omit=omit)
2097 if self.options.annotate:
2110 if self.options.annotate:
2098 adir = os.path.join(self._testdir, 'annotated')
2111 adir = os.path.join(self._testdir, 'annotated')
2099 if not os.path.isdir(adir):
2112 if not os.path.isdir(adir):
2100 os.mkdir(adir)
2113 os.mkdir(adir)
2101 cov.annotate(directory=adir, omit=omit)
2114 cov.annotate(directory=adir, omit=omit)
2102
2115
2103 def _findprogram(self, program):
2116 def _findprogram(self, program):
2104 """Search PATH for a executable program"""
2117 """Search PATH for a executable program"""
2105 if sys.version_info[0] > 2:
2118 if sys.version_info[0] > 2:
2106 dpb = os.defpath.encode('utf-8')
2119 dpb = os.defpath.encode('utf-8')
2107 sepb = os.pathsep.encode('utf-8')
2120 sepb = os.pathsep.encode('utf-8')
2108 else:
2121 else:
2109 dpb = os.defpath
2122 dpb = os.defpath
2110 sepb = os.pathsep
2123 sepb = os.pathsep
2111 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2124 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2112 name = os.path.join(p, program)
2125 name = os.path.join(p, program)
2113 if os.name == 'nt' or os.access(name, os.X_OK):
2126 if os.name == 'nt' or os.access(name, os.X_OK):
2114 return name
2127 return name
2115 return None
2128 return None
2116
2129
2117 def _checktools(self):
2130 def _checktools(self):
2118 """Ensure tools required to run tests are present."""
2131 """Ensure tools required to run tests are present."""
2119 for p in self.REQUIREDTOOLS:
2132 for p in self.REQUIREDTOOLS:
2120 if os.name == 'nt' and not p.endswith('.exe'):
2133 if os.name == 'nt' and not p.endswith('.exe'):
2121 p += '.exe'
2134 p += '.exe'
2122 found = self._findprogram(p)
2135 found = self._findprogram(p)
2123 if found:
2136 if found:
2124 vlog("# Found prerequisite", p, "at", found)
2137 vlog("# Found prerequisite", p, "at", found)
2125 else:
2138 else:
2126 print("WARNING: Did not find prerequisite tool: %s " % p)
2139 print("WARNING: Did not find prerequisite tool: %s " % p)
2127
2140
2128 if __name__ == '__main__':
2141 if __name__ == '__main__':
2129 runner = TestRunner()
2142 runner = TestRunner()
2130
2143
2131 try:
2144 try:
2132 import msvcrt
2145 import msvcrt
2133 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2146 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2134 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2147 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2135 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2148 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2136 except ImportError:
2149 except ImportError:
2137 pass
2150 pass
2138
2151
2139 sys.exit(runner.run(sys.argv[1:]))
2152 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now