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