##// END OF EJS Templates
run-tests: refactor port allocation into functions...
timeless -
r28169:1b07331f default
parent child Browse files
Show More
@@ -1,2448 +1,2457 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, self._threadtmp))
701 (self._testtmp, self._threadtmp))
702 else:
702 else:
703 shutil.rmtree(self._testtmp, True)
703 shutil.rmtree(self._testtmp, True)
704 shutil.rmtree(self._threadtmp, True)
704 shutil.rmtree(self._threadtmp, True)
705
705
706 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
706 if (self._ret != 0 or self._out != self._refout) and not self._skipped \
707 and not self._debug and self._out:
707 and not self._debug and self._out:
708 f = open(self.errpath, 'wb')
708 f = open(self.errpath, 'wb')
709 for line in self._out:
709 for line in self._out:
710 f.write(line)
710 f.write(line)
711 f.close()
711 f.close()
712
712
713 vlog("# Ret was:", self._ret, '(%s)' % self.name)
713 vlog("# Ret was:", self._ret, '(%s)' % self.name)
714
714
715 def _run(self, env):
715 def _run(self, env):
716 # This should be implemented in child classes to run tests.
716 # This should be implemented in child classes to run tests.
717 raise SkipTest('unknown test type')
717 raise SkipTest('unknown test type')
718
718
719 def abort(self):
719 def abort(self):
720 """Terminate execution of this test."""
720 """Terminate execution of this test."""
721 self._aborted = True
721 self._aborted = True
722
722
723 def _portmap(self, i):
724 offset = '' if i == 0 else '%s' % i
725 return (br':%d\b' % (self._startport + i), b':$HGPORT%s' % offset)
726
723 def _getreplacements(self):
727 def _getreplacements(self):
724 """Obtain a mapping of text replacements to apply to test output.
728 """Obtain a mapping of text replacements to apply to test output.
725
729
726 Test output needs to be normalized so it can be compared to expected
730 Test output needs to be normalized so it can be compared to expected
727 output. This function defines how some of that normalization will
731 output. This function defines how some of that normalization will
728 occur.
732 occur.
729 """
733 """
730 r = [
734 r = [
731 (br':%d\b' % self._startport, b':$HGPORT'),
735 # This list should be parallel to defineport in _getenv
732 (br':%d\b' % (self._startport + 1), b':$HGPORT1'),
736 self._portmap(0),
733 (br':%d\b' % (self._startport + 2), b':$HGPORT2'),
737 self._portmap(1),
734 (br':%d\b' % (self._startport + 2), b':$HGPORT3'),
738 self._portmap(2),
735 (br':%d\b' % (self._startport + 2), b':$HGPORT4'),
739 self._portmap(3),
740 self._portmap(4),
736 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
741 (br'(?m)^(saved backup bundle to .*\.hg)( \(glob\))?$',
737 br'\1 (glob)'),
742 br'\1 (glob)'),
738 ]
743 ]
739 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
744 r.append((self._escapepath(self._testtmp), b'$TESTTMP'))
740
745
741 return r
746 return r
742
747
743 def _escapepath(self, p):
748 def _escapepath(self, p):
744 if os.name == 'nt':
749 if os.name == 'nt':
745 return (
750 return (
746 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
751 (b''.join(c.isalpha() and b'[%s%s]' % (c.lower(), c.upper()) or
747 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
752 c in b'/\\' and br'[/\\]' or c.isdigit() and c or b'\\' + c
748 for c in p))
753 for c in p))
749 )
754 )
750 else:
755 else:
751 return re.escape(p)
756 return re.escape(p)
752
757
753 def _getenv(self):
758 def _getenv(self):
754 """Obtain environment variables to use during test execution."""
759 """Obtain environment variables to use during test execution."""
760 def defineport(i):
761 offset = '' if i == 0 else '%s' % i
762 env["HGPORT%s" % offset] = '%s' % (self._startport + i)
755 env = os.environ.copy()
763 env = os.environ.copy()
756 env['TESTTMP'] = self._testtmp
764 env['TESTTMP'] = self._testtmp
757 env['HOME'] = self._testtmp
765 env['HOME'] = self._testtmp
758 env["HGPORT"] = str(self._startport)
766 # This number should match portneeded in _getport
759 env["HGPORT1"] = str(self._startport + 1)
767 # XXX currently it does not, this is a bug that will be fixed
760 env["HGPORT2"] = str(self._startport + 2)
768 # in the next commit.
761 env["HGPORT3"] = str(self._startport + 3)
769 for port in xrange(5):
762 env["HGPORT4"] = str(self._startport + 4)
770 # This list should be parallel to _portmap in _getreplacements
771 defineport(port)
763 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
772 env["HGRCPATH"] = os.path.join(self._threadtmp, b'.hgrc')
764 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
773 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, b'daemon.pids')
765 env["HGEDITOR"] = ('"' + sys.executable + '"'
774 env["HGEDITOR"] = ('"' + sys.executable + '"'
766 + ' -c "import sys; sys.exit(0)"')
775 + ' -c "import sys; sys.exit(0)"')
767 env["HGMERGE"] = "internal:merge"
776 env["HGMERGE"] = "internal:merge"
768 env["HGUSER"] = "test"
777 env["HGUSER"] = "test"
769 env["HGENCODING"] = "ascii"
778 env["HGENCODING"] = "ascii"
770 env["HGENCODINGMODE"] = "strict"
779 env["HGENCODINGMODE"] = "strict"
771
780
772 # Reset some environment variables to well-known values so that
781 # Reset some environment variables to well-known values so that
773 # the tests produce repeatable output.
782 # the tests produce repeatable output.
774 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
783 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
775 env['TZ'] = 'GMT'
784 env['TZ'] = 'GMT'
776 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
785 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
777 env['COLUMNS'] = '80'
786 env['COLUMNS'] = '80'
778 env['TERM'] = 'xterm'
787 env['TERM'] = 'xterm'
779
788
780 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
789 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
781 'NO_PROXY').split():
790 'NO_PROXY').split():
782 if k in env:
791 if k in env:
783 del env[k]
792 del env[k]
784
793
785 # unset env related to hooks
794 # unset env related to hooks
786 for k in env.keys():
795 for k in env.keys():
787 if k.startswith('HG_'):
796 if k.startswith('HG_'):
788 del env[k]
797 del env[k]
789
798
790 return env
799 return env
791
800
792 def _createhgrc(self, path):
801 def _createhgrc(self, path):
793 """Create an hgrc file for this test."""
802 """Create an hgrc file for this test."""
794 hgrc = open(path, 'wb')
803 hgrc = open(path, 'wb')
795 hgrc.write(b'[ui]\n')
804 hgrc.write(b'[ui]\n')
796 hgrc.write(b'slash = True\n')
805 hgrc.write(b'slash = True\n')
797 hgrc.write(b'interactive = False\n')
806 hgrc.write(b'interactive = False\n')
798 hgrc.write(b'mergemarkers = detailed\n')
807 hgrc.write(b'mergemarkers = detailed\n')
799 hgrc.write(b'promptecho = True\n')
808 hgrc.write(b'promptecho = True\n')
800 hgrc.write(b'[defaults]\n')
809 hgrc.write(b'[defaults]\n')
801 hgrc.write(b'backout = -d "0 0"\n')
810 hgrc.write(b'backout = -d "0 0"\n')
802 hgrc.write(b'commit = -d "0 0"\n')
811 hgrc.write(b'commit = -d "0 0"\n')
803 hgrc.write(b'shelve = --date "0 0"\n')
812 hgrc.write(b'shelve = --date "0 0"\n')
804 hgrc.write(b'tag = -d "0 0"\n')
813 hgrc.write(b'tag = -d "0 0"\n')
805 hgrc.write(b'[devel]\n')
814 hgrc.write(b'[devel]\n')
806 hgrc.write(b'all-warnings = true\n')
815 hgrc.write(b'all-warnings = true\n')
807 hgrc.write(b'[largefiles]\n')
816 hgrc.write(b'[largefiles]\n')
808 hgrc.write(b'usercache = %s\n' %
817 hgrc.write(b'usercache = %s\n' %
809 (os.path.join(self._testtmp, b'.cache/largefiles')))
818 (os.path.join(self._testtmp, b'.cache/largefiles')))
810
819
811 for opt in self._extraconfigopts:
820 for opt in self._extraconfigopts:
812 section, key = opt.split('.', 1)
821 section, key = opt.split('.', 1)
813 assert '=' in key, ('extra config opt %s must '
822 assert '=' in key, ('extra config opt %s must '
814 'have an = for assignment' % opt)
823 'have an = for assignment' % opt)
815 hgrc.write(b'[%s]\n%s\n' % (section, key))
824 hgrc.write(b'[%s]\n%s\n' % (section, key))
816 hgrc.close()
825 hgrc.close()
817
826
818 def fail(self, msg):
827 def fail(self, msg):
819 # unittest differentiates between errored and failed.
828 # unittest differentiates between errored and failed.
820 # Failed is denoted by AssertionError (by default at least).
829 # Failed is denoted by AssertionError (by default at least).
821 raise AssertionError(msg)
830 raise AssertionError(msg)
822
831
823 def _runcommand(self, cmd, env, normalizenewlines=False):
832 def _runcommand(self, cmd, env, normalizenewlines=False):
824 """Run command in a sub-process, capturing the output (stdout and
833 """Run command in a sub-process, capturing the output (stdout and
825 stderr).
834 stderr).
826
835
827 Return a tuple (exitcode, output). output is None in debug mode.
836 Return a tuple (exitcode, output). output is None in debug mode.
828 """
837 """
829 if self._debug:
838 if self._debug:
830 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
839 proc = subprocess.Popen(cmd, shell=True, cwd=self._testtmp,
831 env=env)
840 env=env)
832 ret = proc.wait()
841 ret = proc.wait()
833 return (ret, None)
842 return (ret, None)
834
843
835 proc = Popen4(cmd, self._testtmp, self._timeout, env)
844 proc = Popen4(cmd, self._testtmp, self._timeout, env)
836 def cleanup():
845 def cleanup():
837 terminate(proc)
846 terminate(proc)
838 ret = proc.wait()
847 ret = proc.wait()
839 if ret == 0:
848 if ret == 0:
840 ret = signal.SIGTERM << 8
849 ret = signal.SIGTERM << 8
841 killdaemons(env['DAEMON_PIDS'])
850 killdaemons(env['DAEMON_PIDS'])
842 return ret
851 return ret
843
852
844 output = ''
853 output = ''
845 proc.tochild.close()
854 proc.tochild.close()
846
855
847 try:
856 try:
848 output = proc.fromchild.read()
857 output = proc.fromchild.read()
849 except KeyboardInterrupt:
858 except KeyboardInterrupt:
850 vlog('# Handling keyboard interrupt')
859 vlog('# Handling keyboard interrupt')
851 cleanup()
860 cleanup()
852 raise
861 raise
853
862
854 ret = proc.wait()
863 ret = proc.wait()
855 if wifexited(ret):
864 if wifexited(ret):
856 ret = os.WEXITSTATUS(ret)
865 ret = os.WEXITSTATUS(ret)
857
866
858 if proc.timeout:
867 if proc.timeout:
859 ret = 'timeout'
868 ret = 'timeout'
860
869
861 if ret:
870 if ret:
862 killdaemons(env['DAEMON_PIDS'])
871 killdaemons(env['DAEMON_PIDS'])
863
872
864 for s, r in self._getreplacements():
873 for s, r in self._getreplacements():
865 output = re.sub(s, r, output)
874 output = re.sub(s, r, output)
866
875
867 if normalizenewlines:
876 if normalizenewlines:
868 output = output.replace('\r\n', '\n')
877 output = output.replace('\r\n', '\n')
869
878
870 return ret, output.splitlines(True)
879 return ret, output.splitlines(True)
871
880
872 class PythonTest(Test):
881 class PythonTest(Test):
873 """A Python-based test."""
882 """A Python-based test."""
874
883
875 @property
884 @property
876 def refpath(self):
885 def refpath(self):
877 return os.path.join(self._testdir, b'%s.out' % self.bname)
886 return os.path.join(self._testdir, b'%s.out' % self.bname)
878
887
879 def _run(self, env):
888 def _run(self, env):
880 py3kswitch = self._py3kwarnings and b' -3' or b''
889 py3kswitch = self._py3kwarnings and b' -3' or b''
881 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
890 cmd = b'%s%s "%s"' % (PYTHON, py3kswitch, self.path)
882 vlog("# Running", cmd)
891 vlog("# Running", cmd)
883 normalizenewlines = os.name == 'nt'
892 normalizenewlines = os.name == 'nt'
884 result = self._runcommand(cmd, env,
893 result = self._runcommand(cmd, env,
885 normalizenewlines=normalizenewlines)
894 normalizenewlines=normalizenewlines)
886 if self._aborted:
895 if self._aborted:
887 raise KeyboardInterrupt()
896 raise KeyboardInterrupt()
888
897
889 return result
898 return result
890
899
891 # This script may want to drop globs from lines matching these patterns on
900 # This script may want to drop globs from lines matching these patterns on
892 # Windows, but check-code.py wants a glob on these lines unconditionally. Don't
901 # Windows, but check-code.py wants a glob on these lines unconditionally. Don't
893 # warn if that is the case for anything matching these lines.
902 # warn if that is the case for anything matching these lines.
894 checkcodeglobpats = [
903 checkcodeglobpats = [
895 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
904 re.compile(br'^pushing to \$TESTTMP/.*[^)]$'),
896 re.compile(br'^moving \S+/.*[^)]$'),
905 re.compile(br'^moving \S+/.*[^)]$'),
897 re.compile(br'^pulling from \$TESTTMP/.*[^)]$')
906 re.compile(br'^pulling from \$TESTTMP/.*[^)]$')
898 ]
907 ]
899
908
900 bchr = chr
909 bchr = chr
901 if PYTHON3:
910 if PYTHON3:
902 bchr = lambda x: bytes([x])
911 bchr = lambda x: bytes([x])
903
912
904 class TTest(Test):
913 class TTest(Test):
905 """A "t test" is a test backed by a .t file."""
914 """A "t test" is a test backed by a .t file."""
906
915
907 SKIPPED_PREFIX = 'skipped: '
916 SKIPPED_PREFIX = 'skipped: '
908 FAILED_PREFIX = 'hghave check failed: '
917 FAILED_PREFIX = 'hghave check failed: '
909 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
918 NEEDESCAPE = re.compile(br'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
910
919
911 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
920 ESCAPESUB = re.compile(br'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
912 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
921 ESCAPEMAP = dict((bchr(i), br'\x%02x' % i) for i in range(256))
913 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
922 ESCAPEMAP.update({b'\\': b'\\\\', b'\r': br'\r'})
914
923
915 @property
924 @property
916 def refpath(self):
925 def refpath(self):
917 return os.path.join(self._testdir, self.bname)
926 return os.path.join(self._testdir, self.bname)
918
927
919 def _run(self, env):
928 def _run(self, env):
920 f = open(self.path, 'rb')
929 f = open(self.path, 'rb')
921 lines = f.readlines()
930 lines = f.readlines()
922 f.close()
931 f.close()
923
932
924 salt, script, after, expected = self._parsetest(lines)
933 salt, script, after, expected = self._parsetest(lines)
925
934
926 # Write out the generated script.
935 # Write out the generated script.
927 fname = b'%s.sh' % self._testtmp
936 fname = b'%s.sh' % self._testtmp
928 f = open(fname, 'wb')
937 f = open(fname, 'wb')
929 for l in script:
938 for l in script:
930 f.write(l)
939 f.write(l)
931 f.close()
940 f.close()
932
941
933 cmd = b'%s "%s"' % (self._shell, fname)
942 cmd = b'%s "%s"' % (self._shell, fname)
934 vlog("# Running", cmd)
943 vlog("# Running", cmd)
935
944
936 exitcode, output = self._runcommand(cmd, env)
945 exitcode, output = self._runcommand(cmd, env)
937
946
938 if self._aborted:
947 if self._aborted:
939 raise KeyboardInterrupt()
948 raise KeyboardInterrupt()
940
949
941 # Do not merge output if skipped. Return hghave message instead.
950 # Do not merge output if skipped. Return hghave message instead.
942 # Similarly, with --debug, output is None.
951 # Similarly, with --debug, output is None.
943 if exitcode == self.SKIPPED_STATUS or output is None:
952 if exitcode == self.SKIPPED_STATUS or output is None:
944 return exitcode, output
953 return exitcode, output
945
954
946 return self._processoutput(exitcode, output, salt, after, expected)
955 return self._processoutput(exitcode, output, salt, after, expected)
947
956
948 def _hghave(self, reqs):
957 def _hghave(self, reqs):
949 # TODO do something smarter when all other uses of hghave are gone.
958 # TODO do something smarter when all other uses of hghave are gone.
950 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
959 runtestdir = os.path.abspath(os.path.dirname(_bytespath(__file__)))
951 tdir = runtestdir.replace(b'\\', b'/')
960 tdir = runtestdir.replace(b'\\', b'/')
952 proc = Popen4(b'%s -c "%s/hghave %s"' %
961 proc = Popen4(b'%s -c "%s/hghave %s"' %
953 (self._shell, tdir, b' '.join(reqs)),
962 (self._shell, tdir, b' '.join(reqs)),
954 self._testtmp, 0, self._getenv())
963 self._testtmp, 0, self._getenv())
955 stdout, stderr = proc.communicate()
964 stdout, stderr = proc.communicate()
956 ret = proc.wait()
965 ret = proc.wait()
957 if wifexited(ret):
966 if wifexited(ret):
958 ret = os.WEXITSTATUS(ret)
967 ret = os.WEXITSTATUS(ret)
959 if ret == 2:
968 if ret == 2:
960 print(stdout)
969 print(stdout)
961 sys.exit(1)
970 sys.exit(1)
962
971
963 if ret != 0:
972 if ret != 0:
964 return False, stdout
973 return False, stdout
965
974
966 if 'slow' in reqs:
975 if 'slow' in reqs:
967 self._timeout = self._slowtimeout
976 self._timeout = self._slowtimeout
968 return True, None
977 return True, None
969
978
970 def _parsetest(self, lines):
979 def _parsetest(self, lines):
971 # We generate a shell script which outputs unique markers to line
980 # We generate a shell script which outputs unique markers to line
972 # up script results with our source. These markers include input
981 # up script results with our source. These markers include input
973 # line number and the last return code.
982 # line number and the last return code.
974 salt = b"SALT%d" % time.time()
983 salt = b"SALT%d" % time.time()
975 def addsalt(line, inpython):
984 def addsalt(line, inpython):
976 if inpython:
985 if inpython:
977 script.append(b'%s %d 0\n' % (salt, line))
986 script.append(b'%s %d 0\n' % (salt, line))
978 else:
987 else:
979 script.append(b'echo %s %d $?\n' % (salt, line))
988 script.append(b'echo %s %d $?\n' % (salt, line))
980
989
981 script = []
990 script = []
982
991
983 # After we run the shell script, we re-unify the script output
992 # After we run the shell script, we re-unify the script output
984 # with non-active parts of the source, with synchronization by our
993 # with non-active parts of the source, with synchronization by our
985 # SALT line number markers. The after table contains the non-active
994 # SALT line number markers. The after table contains the non-active
986 # components, ordered by line number.
995 # components, ordered by line number.
987 after = {}
996 after = {}
988
997
989 # Expected shell script output.
998 # Expected shell script output.
990 expected = {}
999 expected = {}
991
1000
992 pos = prepos = -1
1001 pos = prepos = -1
993
1002
994 # True or False when in a true or false conditional section
1003 # True or False when in a true or false conditional section
995 skipping = None
1004 skipping = None
996
1005
997 # We keep track of whether or not we're in a Python block so we
1006 # We keep track of whether or not we're in a Python block so we
998 # can generate the surrounding doctest magic.
1007 # can generate the surrounding doctest magic.
999 inpython = False
1008 inpython = False
1000
1009
1001 if self._debug:
1010 if self._debug:
1002 script.append(b'set -x\n')
1011 script.append(b'set -x\n')
1003 if self._hgcommand != b'hg':
1012 if self._hgcommand != b'hg':
1004 script.append(b'alias hg="%s"\n' % self._hgcommand)
1013 script.append(b'alias hg="%s"\n' % self._hgcommand)
1005 if os.getenv('MSYSTEM'):
1014 if os.getenv('MSYSTEM'):
1006 script.append(b'alias pwd="pwd -W"\n')
1015 script.append(b'alias pwd="pwd -W"\n')
1007
1016
1008 for n, l in enumerate(lines):
1017 for n, l in enumerate(lines):
1009 if not l.endswith(b'\n'):
1018 if not l.endswith(b'\n'):
1010 l += b'\n'
1019 l += b'\n'
1011 if l.startswith(b'#require'):
1020 if l.startswith(b'#require'):
1012 lsplit = l.split()
1021 lsplit = l.split()
1013 if len(lsplit) < 2 or lsplit[0] != b'#require':
1022 if len(lsplit) < 2 or lsplit[0] != b'#require':
1014 after.setdefault(pos, []).append(' !!! invalid #require\n')
1023 after.setdefault(pos, []).append(' !!! invalid #require\n')
1015 haveresult, message = self._hghave(lsplit[1:])
1024 haveresult, message = self._hghave(lsplit[1:])
1016 if not haveresult:
1025 if not haveresult:
1017 script = [b'echo "%s"\nexit 80\n' % message]
1026 script = [b'echo "%s"\nexit 80\n' % message]
1018 break
1027 break
1019 after.setdefault(pos, []).append(l)
1028 after.setdefault(pos, []).append(l)
1020 elif l.startswith(b'#if'):
1029 elif l.startswith(b'#if'):
1021 lsplit = l.split()
1030 lsplit = l.split()
1022 if len(lsplit) < 2 or lsplit[0] != b'#if':
1031 if len(lsplit) < 2 or lsplit[0] != b'#if':
1023 after.setdefault(pos, []).append(' !!! invalid #if\n')
1032 after.setdefault(pos, []).append(' !!! invalid #if\n')
1024 if skipping is not None:
1033 if skipping is not None:
1025 after.setdefault(pos, []).append(' !!! nested #if\n')
1034 after.setdefault(pos, []).append(' !!! nested #if\n')
1026 skipping = not self._hghave(lsplit[1:])[0]
1035 skipping = not self._hghave(lsplit[1:])[0]
1027 after.setdefault(pos, []).append(l)
1036 after.setdefault(pos, []).append(l)
1028 elif l.startswith(b'#else'):
1037 elif l.startswith(b'#else'):
1029 if skipping is None:
1038 if skipping is None:
1030 after.setdefault(pos, []).append(' !!! missing #if\n')
1039 after.setdefault(pos, []).append(' !!! missing #if\n')
1031 skipping = not skipping
1040 skipping = not skipping
1032 after.setdefault(pos, []).append(l)
1041 after.setdefault(pos, []).append(l)
1033 elif l.startswith(b'#endif'):
1042 elif l.startswith(b'#endif'):
1034 if skipping is None:
1043 if skipping is None:
1035 after.setdefault(pos, []).append(' !!! missing #if\n')
1044 after.setdefault(pos, []).append(' !!! missing #if\n')
1036 skipping = None
1045 skipping = None
1037 after.setdefault(pos, []).append(l)
1046 after.setdefault(pos, []).append(l)
1038 elif skipping:
1047 elif skipping:
1039 after.setdefault(pos, []).append(l)
1048 after.setdefault(pos, []).append(l)
1040 elif l.startswith(b' >>> '): # python inlines
1049 elif l.startswith(b' >>> '): # python inlines
1041 after.setdefault(pos, []).append(l)
1050 after.setdefault(pos, []).append(l)
1042 prepos = pos
1051 prepos = pos
1043 pos = n
1052 pos = n
1044 if not inpython:
1053 if not inpython:
1045 # We've just entered a Python block. Add the header.
1054 # We've just entered a Python block. Add the header.
1046 inpython = True
1055 inpython = True
1047 addsalt(prepos, False) # Make sure we report the exit code.
1056 addsalt(prepos, False) # Make sure we report the exit code.
1048 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1057 script.append(b'%s -m heredoctest <<EOF\n' % PYTHON)
1049 addsalt(n, True)
1058 addsalt(n, True)
1050 script.append(l[2:])
1059 script.append(l[2:])
1051 elif l.startswith(b' ... '): # python inlines
1060 elif l.startswith(b' ... '): # python inlines
1052 after.setdefault(prepos, []).append(l)
1061 after.setdefault(prepos, []).append(l)
1053 script.append(l[2:])
1062 script.append(l[2:])
1054 elif l.startswith(b' $ '): # commands
1063 elif l.startswith(b' $ '): # commands
1055 if inpython:
1064 if inpython:
1056 script.append(b'EOF\n')
1065 script.append(b'EOF\n')
1057 inpython = False
1066 inpython = False
1058 after.setdefault(pos, []).append(l)
1067 after.setdefault(pos, []).append(l)
1059 prepos = pos
1068 prepos = pos
1060 pos = n
1069 pos = n
1061 addsalt(n, False)
1070 addsalt(n, False)
1062 cmd = l[4:].split()
1071 cmd = l[4:].split()
1063 if len(cmd) == 2 and cmd[0] == b'cd':
1072 if len(cmd) == 2 and cmd[0] == b'cd':
1064 l = b' $ cd %s || exit 1\n' % cmd[1]
1073 l = b' $ cd %s || exit 1\n' % cmd[1]
1065 script.append(l[4:])
1074 script.append(l[4:])
1066 elif l.startswith(b' > '): # continuations
1075 elif l.startswith(b' > '): # continuations
1067 after.setdefault(prepos, []).append(l)
1076 after.setdefault(prepos, []).append(l)
1068 script.append(l[4:])
1077 script.append(l[4:])
1069 elif l.startswith(b' '): # results
1078 elif l.startswith(b' '): # results
1070 # Queue up a list of expected results.
1079 # Queue up a list of expected results.
1071 expected.setdefault(pos, []).append(l[2:])
1080 expected.setdefault(pos, []).append(l[2:])
1072 else:
1081 else:
1073 if inpython:
1082 if inpython:
1074 script.append(b'EOF\n')
1083 script.append(b'EOF\n')
1075 inpython = False
1084 inpython = False
1076 # Non-command/result. Queue up for merged output.
1085 # Non-command/result. Queue up for merged output.
1077 after.setdefault(pos, []).append(l)
1086 after.setdefault(pos, []).append(l)
1078
1087
1079 if inpython:
1088 if inpython:
1080 script.append(b'EOF\n')
1089 script.append(b'EOF\n')
1081 if skipping is not None:
1090 if skipping is not None:
1082 after.setdefault(pos, []).append(' !!! missing #endif\n')
1091 after.setdefault(pos, []).append(' !!! missing #endif\n')
1083 addsalt(n + 1, False)
1092 addsalt(n + 1, False)
1084
1093
1085 return salt, script, after, expected
1094 return salt, script, after, expected
1086
1095
1087 def _processoutput(self, exitcode, output, salt, after, expected):
1096 def _processoutput(self, exitcode, output, salt, after, expected):
1088 # Merge the script output back into a unified test.
1097 # Merge the script output back into a unified test.
1089 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1098 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
1090 if exitcode != 0:
1099 if exitcode != 0:
1091 warnonly = 3
1100 warnonly = 3
1092
1101
1093 pos = -1
1102 pos = -1
1094 postout = []
1103 postout = []
1095 for l in output:
1104 for l in output:
1096 lout, lcmd = l, None
1105 lout, lcmd = l, None
1097 if salt in l:
1106 if salt in l:
1098 lout, lcmd = l.split(salt, 1)
1107 lout, lcmd = l.split(salt, 1)
1099
1108
1100 while lout:
1109 while lout:
1101 if not lout.endswith(b'\n'):
1110 if not lout.endswith(b'\n'):
1102 lout += b' (no-eol)\n'
1111 lout += b' (no-eol)\n'
1103
1112
1104 # Find the expected output at the current position.
1113 # Find the expected output at the current position.
1105 el = None
1114 el = None
1106 if expected.get(pos, None):
1115 if expected.get(pos, None):
1107 el = expected[pos].pop(0)
1116 el = expected[pos].pop(0)
1108
1117
1109 r = TTest.linematch(el, lout)
1118 r = TTest.linematch(el, lout)
1110 if isinstance(r, str):
1119 if isinstance(r, str):
1111 if r == '+glob':
1120 if r == '+glob':
1112 lout = el[:-1] + ' (glob)\n'
1121 lout = el[:-1] + ' (glob)\n'
1113 r = '' # Warn only this line.
1122 r = '' # Warn only this line.
1114 elif r == '-glob':
1123 elif r == '-glob':
1115 lout = ''.join(el.rsplit(' (glob)', 1))
1124 lout = ''.join(el.rsplit(' (glob)', 1))
1116 r = '' # Warn only this line.
1125 r = '' # Warn only this line.
1117 elif r == "retry":
1126 elif r == "retry":
1118 postout.append(b' ' + el)
1127 postout.append(b' ' + el)
1119 continue
1128 continue
1120 else:
1129 else:
1121 log('\ninfo, unknown linematch result: %r\n' % r)
1130 log('\ninfo, unknown linematch result: %r\n' % r)
1122 r = False
1131 r = False
1123 if r:
1132 if r:
1124 postout.append(b' ' + el)
1133 postout.append(b' ' + el)
1125 else:
1134 else:
1126 if self.NEEDESCAPE(lout):
1135 if self.NEEDESCAPE(lout):
1127 lout = TTest._stringescape(b'%s (esc)\n' %
1136 lout = TTest._stringescape(b'%s (esc)\n' %
1128 lout.rstrip(b'\n'))
1137 lout.rstrip(b'\n'))
1129 postout.append(b' ' + lout) # Let diff deal with it.
1138 postout.append(b' ' + lout) # Let diff deal with it.
1130 if r != '': # If line failed.
1139 if r != '': # If line failed.
1131 warnonly = 3 # for sure not
1140 warnonly = 3 # for sure not
1132 elif warnonly == 1: # Is "not yet" and line is warn only.
1141 elif warnonly == 1: # Is "not yet" and line is warn only.
1133 warnonly = 2 # Yes do warn.
1142 warnonly = 2 # Yes do warn.
1134 break
1143 break
1135
1144
1136 # clean up any optional leftovers
1145 # clean up any optional leftovers
1137 while expected.get(pos, None):
1146 while expected.get(pos, None):
1138 el = expected[pos].pop(0)
1147 el = expected[pos].pop(0)
1139 if not el.endswith(b" (?)\n"):
1148 if not el.endswith(b" (?)\n"):
1140 expected[pos].insert(0, el)
1149 expected[pos].insert(0, el)
1141 break
1150 break
1142 postout.append(b' ' + el)
1151 postout.append(b' ' + el)
1143
1152
1144 if lcmd:
1153 if lcmd:
1145 # Add on last return code.
1154 # Add on last return code.
1146 ret = int(lcmd.split()[1])
1155 ret = int(lcmd.split()[1])
1147 if ret != 0:
1156 if ret != 0:
1148 postout.append(b' [%d]\n' % ret)
1157 postout.append(b' [%d]\n' % ret)
1149 if pos in after:
1158 if pos in after:
1150 # Merge in non-active test bits.
1159 # Merge in non-active test bits.
1151 postout += after.pop(pos)
1160 postout += after.pop(pos)
1152 pos = int(lcmd.split()[0])
1161 pos = int(lcmd.split()[0])
1153
1162
1154 if pos in after:
1163 if pos in after:
1155 postout += after.pop(pos)
1164 postout += after.pop(pos)
1156
1165
1157 if warnonly == 2:
1166 if warnonly == 2:
1158 exitcode = False # Set exitcode to warned.
1167 exitcode = False # Set exitcode to warned.
1159
1168
1160 return exitcode, postout
1169 return exitcode, postout
1161
1170
1162 @staticmethod
1171 @staticmethod
1163 def rematch(el, l):
1172 def rematch(el, l):
1164 try:
1173 try:
1165 # use \Z to ensure that the regex matches to the end of the string
1174 # use \Z to ensure that the regex matches to the end of the string
1166 if os.name == 'nt':
1175 if os.name == 'nt':
1167 return re.match(el + br'\r?\n\Z', l)
1176 return re.match(el + br'\r?\n\Z', l)
1168 return re.match(el + br'\n\Z', l)
1177 return re.match(el + br'\n\Z', l)
1169 except re.error:
1178 except re.error:
1170 # el is an invalid regex
1179 # el is an invalid regex
1171 return False
1180 return False
1172
1181
1173 @staticmethod
1182 @staticmethod
1174 def globmatch(el, l):
1183 def globmatch(el, l):
1175 # The only supported special characters are * and ? plus / which also
1184 # The only supported special characters are * and ? plus / which also
1176 # matches \ on windows. Escaping of these characters is supported.
1185 # matches \ on windows. Escaping of these characters is supported.
1177 if el + b'\n' == l:
1186 if el + b'\n' == l:
1178 if os.altsep:
1187 if os.altsep:
1179 # matching on "/" is not needed for this line
1188 # matching on "/" is not needed for this line
1180 for pat in checkcodeglobpats:
1189 for pat in checkcodeglobpats:
1181 if pat.match(el):
1190 if pat.match(el):
1182 return True
1191 return True
1183 return b'-glob'
1192 return b'-glob'
1184 return True
1193 return True
1185 i, n = 0, len(el)
1194 i, n = 0, len(el)
1186 res = b''
1195 res = b''
1187 while i < n:
1196 while i < n:
1188 c = el[i:i + 1]
1197 c = el[i:i + 1]
1189 i += 1
1198 i += 1
1190 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1199 if c == b'\\' and i < n and el[i:i + 1] in b'*?\\/':
1191 res += el[i - 1:i + 1]
1200 res += el[i - 1:i + 1]
1192 i += 1
1201 i += 1
1193 elif c == b'*':
1202 elif c == b'*':
1194 res += b'.*'
1203 res += b'.*'
1195 elif c == b'?':
1204 elif c == b'?':
1196 res += b'.'
1205 res += b'.'
1197 elif c == b'/' and os.altsep:
1206 elif c == b'/' and os.altsep:
1198 res += b'[/\\\\]'
1207 res += b'[/\\\\]'
1199 else:
1208 else:
1200 res += re.escape(c)
1209 res += re.escape(c)
1201 return TTest.rematch(res, l)
1210 return TTest.rematch(res, l)
1202
1211
1203 @staticmethod
1212 @staticmethod
1204 def linematch(el, l):
1213 def linematch(el, l):
1205 retry = False
1214 retry = False
1206 if el == l: # perfect match (fast)
1215 if el == l: # perfect match (fast)
1207 return True
1216 return True
1208 if el:
1217 if el:
1209 if el.endswith(b" (?)\n"):
1218 if el.endswith(b" (?)\n"):
1210 retry = "retry"
1219 retry = "retry"
1211 el = el[:-5] + "\n"
1220 el = el[:-5] + "\n"
1212 if el.endswith(b" (esc)\n"):
1221 if el.endswith(b" (esc)\n"):
1213 if PYTHON3:
1222 if PYTHON3:
1214 el = el[:-7].decode('unicode_escape') + '\n'
1223 el = el[:-7].decode('unicode_escape') + '\n'
1215 el = el.encode('utf-8')
1224 el = el.encode('utf-8')
1216 else:
1225 else:
1217 el = el[:-7].decode('string-escape') + '\n'
1226 el = el[:-7].decode('string-escape') + '\n'
1218 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1227 if el == l or os.name == 'nt' and el[:-1] + b'\r\n' == l:
1219 return True
1228 return True
1220 if el.endswith(b" (re)\n"):
1229 if el.endswith(b" (re)\n"):
1221 return TTest.rematch(el[:-6], l) or retry
1230 return TTest.rematch(el[:-6], l) or retry
1222 if el.endswith(b" (glob)\n"):
1231 if el.endswith(b" (glob)\n"):
1223 # ignore '(glob)' added to l by 'replacements'
1232 # ignore '(glob)' added to l by 'replacements'
1224 if l.endswith(b" (glob)\n"):
1233 if l.endswith(b" (glob)\n"):
1225 l = l[:-8] + b"\n"
1234 l = l[:-8] + b"\n"
1226 return TTest.globmatch(el[:-8], l)
1235 return TTest.globmatch(el[:-8], l)
1227 if os.altsep and l.replace(b'\\', b'/') == el:
1236 if os.altsep and l.replace(b'\\', b'/') == el:
1228 return b'+glob'
1237 return b'+glob'
1229 return retry
1238 return retry
1230
1239
1231 @staticmethod
1240 @staticmethod
1232 def parsehghaveoutput(lines):
1241 def parsehghaveoutput(lines):
1233 '''Parse hghave log lines.
1242 '''Parse hghave log lines.
1234
1243
1235 Return tuple of lists (missing, failed):
1244 Return tuple of lists (missing, failed):
1236 * the missing/unknown features
1245 * the missing/unknown features
1237 * the features for which existence check failed'''
1246 * the features for which existence check failed'''
1238 missing = []
1247 missing = []
1239 failed = []
1248 failed = []
1240 for line in lines:
1249 for line in lines:
1241 if line.startswith(TTest.SKIPPED_PREFIX):
1250 if line.startswith(TTest.SKIPPED_PREFIX):
1242 line = line.splitlines()[0]
1251 line = line.splitlines()[0]
1243 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1252 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1244 elif line.startswith(TTest.FAILED_PREFIX):
1253 elif line.startswith(TTest.FAILED_PREFIX):
1245 line = line.splitlines()[0]
1254 line = line.splitlines()[0]
1246 failed.append(line[len(TTest.FAILED_PREFIX):])
1255 failed.append(line[len(TTest.FAILED_PREFIX):])
1247
1256
1248 return missing, failed
1257 return missing, failed
1249
1258
1250 @staticmethod
1259 @staticmethod
1251 def _escapef(m):
1260 def _escapef(m):
1252 return TTest.ESCAPEMAP[m.group(0)]
1261 return TTest.ESCAPEMAP[m.group(0)]
1253
1262
1254 @staticmethod
1263 @staticmethod
1255 def _stringescape(s):
1264 def _stringescape(s):
1256 return TTest.ESCAPESUB(TTest._escapef, s)
1265 return TTest.ESCAPESUB(TTest._escapef, s)
1257
1266
1258 iolock = threading.RLock()
1267 iolock = threading.RLock()
1259
1268
1260 class SkipTest(Exception):
1269 class SkipTest(Exception):
1261 """Raised to indicate that a test is to be skipped."""
1270 """Raised to indicate that a test is to be skipped."""
1262
1271
1263 class IgnoreTest(Exception):
1272 class IgnoreTest(Exception):
1264 """Raised to indicate that a test is to be ignored."""
1273 """Raised to indicate that a test is to be ignored."""
1265
1274
1266 class WarnTest(Exception):
1275 class WarnTest(Exception):
1267 """Raised to indicate that a test warned."""
1276 """Raised to indicate that a test warned."""
1268
1277
1269 class ReportedTest(Exception):
1278 class ReportedTest(Exception):
1270 """Raised to indicate that a test already reported."""
1279 """Raised to indicate that a test already reported."""
1271
1280
1272 class TestResult(unittest._TextTestResult):
1281 class TestResult(unittest._TextTestResult):
1273 """Holds results when executing via unittest."""
1282 """Holds results when executing via unittest."""
1274 # Don't worry too much about accessing the non-public _TextTestResult.
1283 # Don't worry too much about accessing the non-public _TextTestResult.
1275 # It is relatively common in Python testing tools.
1284 # It is relatively common in Python testing tools.
1276 def __init__(self, options, *args, **kwargs):
1285 def __init__(self, options, *args, **kwargs):
1277 super(TestResult, self).__init__(*args, **kwargs)
1286 super(TestResult, self).__init__(*args, **kwargs)
1278
1287
1279 self._options = options
1288 self._options = options
1280
1289
1281 # unittest.TestResult didn't have skipped until 2.7. We need to
1290 # unittest.TestResult didn't have skipped until 2.7. We need to
1282 # polyfill it.
1291 # polyfill it.
1283 self.skipped = []
1292 self.skipped = []
1284
1293
1285 # We have a custom "ignored" result that isn't present in any Python
1294 # We have a custom "ignored" result that isn't present in any Python
1286 # unittest implementation. It is very similar to skipped. It may make
1295 # unittest implementation. It is very similar to skipped. It may make
1287 # sense to map it into skip some day.
1296 # sense to map it into skip some day.
1288 self.ignored = []
1297 self.ignored = []
1289
1298
1290 # We have a custom "warned" result that isn't present in any Python
1299 # We have a custom "warned" result that isn't present in any Python
1291 # unittest implementation. It is very similar to failed. It may make
1300 # unittest implementation. It is very similar to failed. It may make
1292 # sense to map it into fail some day.
1301 # sense to map it into fail some day.
1293 self.warned = []
1302 self.warned = []
1294
1303
1295 self.times = []
1304 self.times = []
1296 self._firststarttime = None
1305 self._firststarttime = None
1297 # Data stored for the benefit of generating xunit reports.
1306 # Data stored for the benefit of generating xunit reports.
1298 self.successes = []
1307 self.successes = []
1299 self.faildata = {}
1308 self.faildata = {}
1300
1309
1301 def addFailure(self, test, reason):
1310 def addFailure(self, test, reason):
1302 self.failures.append((test, reason))
1311 self.failures.append((test, reason))
1303
1312
1304 if self._options.first:
1313 if self._options.first:
1305 self.stop()
1314 self.stop()
1306 else:
1315 else:
1307 with iolock:
1316 with iolock:
1308 if reason == "timed out":
1317 if reason == "timed out":
1309 self.stream.write('t')
1318 self.stream.write('t')
1310 else:
1319 else:
1311 if not self._options.nodiff:
1320 if not self._options.nodiff:
1312 self.stream.write('\nERROR: %s output changed\n' % test)
1321 self.stream.write('\nERROR: %s output changed\n' % test)
1313 self.stream.write('!')
1322 self.stream.write('!')
1314
1323
1315 self.stream.flush()
1324 self.stream.flush()
1316
1325
1317 def addSuccess(self, test):
1326 def addSuccess(self, test):
1318 with iolock:
1327 with iolock:
1319 super(TestResult, self).addSuccess(test)
1328 super(TestResult, self).addSuccess(test)
1320 self.successes.append(test)
1329 self.successes.append(test)
1321
1330
1322 def addError(self, test, err):
1331 def addError(self, test, err):
1323 super(TestResult, self).addError(test, err)
1332 super(TestResult, self).addError(test, err)
1324 if self._options.first:
1333 if self._options.first:
1325 self.stop()
1334 self.stop()
1326
1335
1327 # Polyfill.
1336 # Polyfill.
1328 def addSkip(self, test, reason):
1337 def addSkip(self, test, reason):
1329 self.skipped.append((test, reason))
1338 self.skipped.append((test, reason))
1330 with iolock:
1339 with iolock:
1331 if self.showAll:
1340 if self.showAll:
1332 self.stream.writeln('skipped %s' % reason)
1341 self.stream.writeln('skipped %s' % reason)
1333 else:
1342 else:
1334 self.stream.write('s')
1343 self.stream.write('s')
1335 self.stream.flush()
1344 self.stream.flush()
1336
1345
1337 def addIgnore(self, test, reason):
1346 def addIgnore(self, test, reason):
1338 self.ignored.append((test, reason))
1347 self.ignored.append((test, reason))
1339 with iolock:
1348 with iolock:
1340 if self.showAll:
1349 if self.showAll:
1341 self.stream.writeln('ignored %s' % reason)
1350 self.stream.writeln('ignored %s' % reason)
1342 else:
1351 else:
1343 if reason not in ('not retesting', "doesn't match keyword"):
1352 if reason not in ('not retesting', "doesn't match keyword"):
1344 self.stream.write('i')
1353 self.stream.write('i')
1345 else:
1354 else:
1346 self.testsRun += 1
1355 self.testsRun += 1
1347 self.stream.flush()
1356 self.stream.flush()
1348
1357
1349 def addWarn(self, test, reason):
1358 def addWarn(self, test, reason):
1350 self.warned.append((test, reason))
1359 self.warned.append((test, reason))
1351
1360
1352 if self._options.first:
1361 if self._options.first:
1353 self.stop()
1362 self.stop()
1354
1363
1355 with iolock:
1364 with iolock:
1356 if self.showAll:
1365 if self.showAll:
1357 self.stream.writeln('warned %s' % reason)
1366 self.stream.writeln('warned %s' % reason)
1358 else:
1367 else:
1359 self.stream.write('~')
1368 self.stream.write('~')
1360 self.stream.flush()
1369 self.stream.flush()
1361
1370
1362 def addOutputMismatch(self, test, ret, got, expected):
1371 def addOutputMismatch(self, test, ret, got, expected):
1363 """Record a mismatch in test output for a particular test."""
1372 """Record a mismatch in test output for a particular test."""
1364 if self.shouldStop:
1373 if self.shouldStop:
1365 # don't print, some other test case already failed and
1374 # don't print, some other test case already failed and
1366 # printed, we're just stale and probably failed due to our
1375 # printed, we're just stale and probably failed due to our
1367 # temp dir getting cleaned up.
1376 # temp dir getting cleaned up.
1368 return
1377 return
1369
1378
1370 accepted = False
1379 accepted = False
1371 lines = []
1380 lines = []
1372
1381
1373 with iolock:
1382 with iolock:
1374 if self._options.nodiff:
1383 if self._options.nodiff:
1375 pass
1384 pass
1376 elif self._options.view:
1385 elif self._options.view:
1377 v = self._options.view
1386 v = self._options.view
1378 if PYTHON3:
1387 if PYTHON3:
1379 v = _bytespath(v)
1388 v = _bytespath(v)
1380 os.system(b"%s %s %s" %
1389 os.system(b"%s %s %s" %
1381 (v, test.refpath, test.errpath))
1390 (v, test.refpath, test.errpath))
1382 else:
1391 else:
1383 servefail, lines = getdiff(expected, got,
1392 servefail, lines = getdiff(expected, got,
1384 test.refpath, test.errpath)
1393 test.refpath, test.errpath)
1385 if servefail:
1394 if servefail:
1386 self.addFailure(
1395 self.addFailure(
1387 test,
1396 test,
1388 'server failed to start (HGPORT=%s)' % test._startport)
1397 'server failed to start (HGPORT=%s)' % test._startport)
1389 raise ReportedTest('server failed to start')
1398 raise ReportedTest('server failed to start')
1390 else:
1399 else:
1391 self.stream.write('\n')
1400 self.stream.write('\n')
1392 for line in lines:
1401 for line in lines:
1393 if PYTHON3:
1402 if PYTHON3:
1394 self.stream.flush()
1403 self.stream.flush()
1395 self.stream.buffer.write(line)
1404 self.stream.buffer.write(line)
1396 self.stream.buffer.flush()
1405 self.stream.buffer.flush()
1397 else:
1406 else:
1398 self.stream.write(line)
1407 self.stream.write(line)
1399 self.stream.flush()
1408 self.stream.flush()
1400
1409
1401 # handle interactive prompt without releasing iolock
1410 # handle interactive prompt without releasing iolock
1402 if self._options.interactive:
1411 if self._options.interactive:
1403 self.stream.write('Accept this change? [n] ')
1412 self.stream.write('Accept this change? [n] ')
1404 answer = sys.stdin.readline().strip()
1413 answer = sys.stdin.readline().strip()
1405 if answer.lower() in ('y', 'yes'):
1414 if answer.lower() in ('y', 'yes'):
1406 if test.name.endswith('.t'):
1415 if test.name.endswith('.t'):
1407 rename(test.errpath, test.path)
1416 rename(test.errpath, test.path)
1408 else:
1417 else:
1409 rename(test.errpath, '%s.out' % test.path)
1418 rename(test.errpath, '%s.out' % test.path)
1410 accepted = True
1419 accepted = True
1411 if not accepted:
1420 if not accepted:
1412 self.faildata[test.name] = b''.join(lines)
1421 self.faildata[test.name] = b''.join(lines)
1413
1422
1414 return accepted
1423 return accepted
1415
1424
1416 def startTest(self, test):
1425 def startTest(self, test):
1417 super(TestResult, self).startTest(test)
1426 super(TestResult, self).startTest(test)
1418
1427
1419 # os.times module computes the user time and system time spent by
1428 # os.times module computes the user time and system time spent by
1420 # child's processes along with real elapsed time taken by a process.
1429 # child's processes along with real elapsed time taken by a process.
1421 # This module has one limitation. It can only work for Linux user
1430 # This module has one limitation. It can only work for Linux user
1422 # and not for Windows.
1431 # and not for Windows.
1423 test.started = os.times()
1432 test.started = os.times()
1424 if self._firststarttime is None: # thread racy but irrelevant
1433 if self._firststarttime is None: # thread racy but irrelevant
1425 self._firststarttime = test.started[4]
1434 self._firststarttime = test.started[4]
1426
1435
1427 def stopTest(self, test, interrupted=False):
1436 def stopTest(self, test, interrupted=False):
1428 super(TestResult, self).stopTest(test)
1437 super(TestResult, self).stopTest(test)
1429
1438
1430 test.stopped = os.times()
1439 test.stopped = os.times()
1431
1440
1432 starttime = test.started
1441 starttime = test.started
1433 endtime = test.stopped
1442 endtime = test.stopped
1434 origin = self._firststarttime
1443 origin = self._firststarttime
1435 self.times.append((test.name,
1444 self.times.append((test.name,
1436 endtime[2] - starttime[2], # user space CPU time
1445 endtime[2] - starttime[2], # user space CPU time
1437 endtime[3] - starttime[3], # sys space CPU time
1446 endtime[3] - starttime[3], # sys space CPU time
1438 endtime[4] - starttime[4], # real time
1447 endtime[4] - starttime[4], # real time
1439 starttime[4] - origin, # start date in run context
1448 starttime[4] - origin, # start date in run context
1440 endtime[4] - origin, # end date in run context
1449 endtime[4] - origin, # end date in run context
1441 ))
1450 ))
1442
1451
1443 if interrupted:
1452 if interrupted:
1444 with iolock:
1453 with iolock:
1445 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1454 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1446 test.name, self.times[-1][3]))
1455 test.name, self.times[-1][3]))
1447
1456
1448 class TestSuite(unittest.TestSuite):
1457 class TestSuite(unittest.TestSuite):
1449 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1458 """Custom unittest TestSuite that knows how to execute Mercurial tests."""
1450
1459
1451 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1460 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1452 retest=False, keywords=None, loop=False, runs_per_test=1,
1461 retest=False, keywords=None, loop=False, runs_per_test=1,
1453 loadtest=None, showchannels=False,
1462 loadtest=None, showchannels=False,
1454 *args, **kwargs):
1463 *args, **kwargs):
1455 """Create a new instance that can run tests with a configuration.
1464 """Create a new instance that can run tests with a configuration.
1456
1465
1457 testdir specifies the directory where tests are executed from. This
1466 testdir specifies the directory where tests are executed from. This
1458 is typically the ``tests`` directory from Mercurial's source
1467 is typically the ``tests`` directory from Mercurial's source
1459 repository.
1468 repository.
1460
1469
1461 jobs specifies the number of jobs to run concurrently. Each test
1470 jobs specifies the number of jobs to run concurrently. Each test
1462 executes on its own thread. Tests actually spawn new processes, so
1471 executes on its own thread. Tests actually spawn new processes, so
1463 state mutation should not be an issue.
1472 state mutation should not be an issue.
1464
1473
1465 If there is only one job, it will use the main thread.
1474 If there is only one job, it will use the main thread.
1466
1475
1467 whitelist and blacklist denote tests that have been whitelisted and
1476 whitelist and blacklist denote tests that have been whitelisted and
1468 blacklisted, respectively. These arguments don't belong in TestSuite.
1477 blacklisted, respectively. These arguments don't belong in TestSuite.
1469 Instead, whitelist and blacklist should be handled by the thing that
1478 Instead, whitelist and blacklist should be handled by the thing that
1470 populates the TestSuite with tests. They are present to preserve
1479 populates the TestSuite with tests. They are present to preserve
1471 backwards compatible behavior which reports skipped tests as part
1480 backwards compatible behavior which reports skipped tests as part
1472 of the results.
1481 of the results.
1473
1482
1474 retest denotes whether to retest failed tests. This arguably belongs
1483 retest denotes whether to retest failed tests. This arguably belongs
1475 outside of TestSuite.
1484 outside of TestSuite.
1476
1485
1477 keywords denotes key words that will be used to filter which tests
1486 keywords denotes key words that will be used to filter which tests
1478 to execute. This arguably belongs outside of TestSuite.
1487 to execute. This arguably belongs outside of TestSuite.
1479
1488
1480 loop denotes whether to loop over tests forever.
1489 loop denotes whether to loop over tests forever.
1481 """
1490 """
1482 super(TestSuite, self).__init__(*args, **kwargs)
1491 super(TestSuite, self).__init__(*args, **kwargs)
1483
1492
1484 self._jobs = jobs
1493 self._jobs = jobs
1485 self._whitelist = whitelist
1494 self._whitelist = whitelist
1486 self._blacklist = blacklist
1495 self._blacklist = blacklist
1487 self._retest = retest
1496 self._retest = retest
1488 self._keywords = keywords
1497 self._keywords = keywords
1489 self._loop = loop
1498 self._loop = loop
1490 self._runs_per_test = runs_per_test
1499 self._runs_per_test = runs_per_test
1491 self._loadtest = loadtest
1500 self._loadtest = loadtest
1492 self._showchannels = showchannels
1501 self._showchannels = showchannels
1493
1502
1494 def run(self, result):
1503 def run(self, result):
1495 # We have a number of filters that need to be applied. We do this
1504 # We have a number of filters that need to be applied. We do this
1496 # here instead of inside Test because it makes the running logic for
1505 # here instead of inside Test because it makes the running logic for
1497 # Test simpler.
1506 # Test simpler.
1498 tests = []
1507 tests = []
1499 num_tests = [0]
1508 num_tests = [0]
1500 for test in self._tests:
1509 for test in self._tests:
1501 def get():
1510 def get():
1502 num_tests[0] += 1
1511 num_tests[0] += 1
1503 if getattr(test, 'should_reload', False):
1512 if getattr(test, 'should_reload', False):
1504 return self._loadtest(test.bname, num_tests[0])
1513 return self._loadtest(test.bname, num_tests[0])
1505 return test
1514 return test
1506 if not os.path.exists(test.path):
1515 if not os.path.exists(test.path):
1507 result.addSkip(test, "Doesn't exist")
1516 result.addSkip(test, "Doesn't exist")
1508 continue
1517 continue
1509
1518
1510 if not (self._whitelist and test.name in self._whitelist):
1519 if not (self._whitelist and test.name in self._whitelist):
1511 if self._blacklist and test.bname in self._blacklist:
1520 if self._blacklist and test.bname in self._blacklist:
1512 result.addSkip(test, 'blacklisted')
1521 result.addSkip(test, 'blacklisted')
1513 continue
1522 continue
1514
1523
1515 if self._retest and not os.path.exists(test.errpath):
1524 if self._retest and not os.path.exists(test.errpath):
1516 result.addIgnore(test, 'not retesting')
1525 result.addIgnore(test, 'not retesting')
1517 continue
1526 continue
1518
1527
1519 if self._keywords:
1528 if self._keywords:
1520 f = open(test.path, 'rb')
1529 f = open(test.path, 'rb')
1521 t = f.read().lower() + test.bname.lower()
1530 t = f.read().lower() + test.bname.lower()
1522 f.close()
1531 f.close()
1523 ignored = False
1532 ignored = False
1524 for k in self._keywords.lower().split():
1533 for k in self._keywords.lower().split():
1525 if k not in t:
1534 if k not in t:
1526 result.addIgnore(test, "doesn't match keyword")
1535 result.addIgnore(test, "doesn't match keyword")
1527 ignored = True
1536 ignored = True
1528 break
1537 break
1529
1538
1530 if ignored:
1539 if ignored:
1531 continue
1540 continue
1532 for _ in xrange(self._runs_per_test):
1541 for _ in xrange(self._runs_per_test):
1533 tests.append(get())
1542 tests.append(get())
1534
1543
1535 runtests = list(tests)
1544 runtests = list(tests)
1536 done = queue.Queue()
1545 done = queue.Queue()
1537 running = 0
1546 running = 0
1538
1547
1539 channels = [""] * self._jobs
1548 channels = [""] * self._jobs
1540
1549
1541 def job(test, result):
1550 def job(test, result):
1542 for n, v in enumerate(channels):
1551 for n, v in enumerate(channels):
1543 if not v:
1552 if not v:
1544 channel = n
1553 channel = n
1545 break
1554 break
1546 channels[channel] = "=" + test.name[5:].split(".")[0]
1555 channels[channel] = "=" + test.name[5:].split(".")[0]
1547 try:
1556 try:
1548 test(result)
1557 test(result)
1549 done.put(None)
1558 done.put(None)
1550 except KeyboardInterrupt:
1559 except KeyboardInterrupt:
1551 pass
1560 pass
1552 except: # re-raises
1561 except: # re-raises
1553 done.put(('!', test, 'run-test raised an error, see traceback'))
1562 done.put(('!', test, 'run-test raised an error, see traceback'))
1554 raise
1563 raise
1555 try:
1564 try:
1556 channels[channel] = ''
1565 channels[channel] = ''
1557 except IndexError:
1566 except IndexError:
1558 pass
1567 pass
1559
1568
1560 def stat():
1569 def stat():
1561 count = 0
1570 count = 0
1562 while channels:
1571 while channels:
1563 d = '\n%03s ' % count
1572 d = '\n%03s ' % count
1564 for n, v in enumerate(channels):
1573 for n, v in enumerate(channels):
1565 if v:
1574 if v:
1566 d += v[0]
1575 d += v[0]
1567 channels[n] = v[1:] or '.'
1576 channels[n] = v[1:] or '.'
1568 else:
1577 else:
1569 d += ' '
1578 d += ' '
1570 d += ' '
1579 d += ' '
1571 with iolock:
1580 with iolock:
1572 sys.stdout.write(d + ' ')
1581 sys.stdout.write(d + ' ')
1573 sys.stdout.flush()
1582 sys.stdout.flush()
1574 for x in xrange(10):
1583 for x in xrange(10):
1575 if channels:
1584 if channels:
1576 time.sleep(.1)
1585 time.sleep(.1)
1577 count += 1
1586 count += 1
1578
1587
1579 stoppedearly = False
1588 stoppedearly = False
1580
1589
1581 if self._showchannels:
1590 if self._showchannels:
1582 statthread = threading.Thread(target=stat, name="stat")
1591 statthread = threading.Thread(target=stat, name="stat")
1583 statthread.start()
1592 statthread.start()
1584
1593
1585 try:
1594 try:
1586 while tests or running:
1595 while tests or running:
1587 if not done.empty() or running == self._jobs or not tests:
1596 if not done.empty() or running == self._jobs or not tests:
1588 try:
1597 try:
1589 done.get(True, 1)
1598 done.get(True, 1)
1590 running -= 1
1599 running -= 1
1591 if result and result.shouldStop:
1600 if result and result.shouldStop:
1592 stoppedearly = True
1601 stoppedearly = True
1593 break
1602 break
1594 except queue.Empty:
1603 except queue.Empty:
1595 continue
1604 continue
1596 if tests and not running == self._jobs:
1605 if tests and not running == self._jobs:
1597 test = tests.pop(0)
1606 test = tests.pop(0)
1598 if self._loop:
1607 if self._loop:
1599 if getattr(test, 'should_reload', False):
1608 if getattr(test, 'should_reload', False):
1600 num_tests[0] += 1
1609 num_tests[0] += 1
1601 tests.append(
1610 tests.append(
1602 self._loadtest(test.name, num_tests[0]))
1611 self._loadtest(test.name, num_tests[0]))
1603 else:
1612 else:
1604 tests.append(test)
1613 tests.append(test)
1605 if self._jobs == 1:
1614 if self._jobs == 1:
1606 job(test, result)
1615 job(test, result)
1607 else:
1616 else:
1608 t = threading.Thread(target=job, name=test.name,
1617 t = threading.Thread(target=job, name=test.name,
1609 args=(test, result))
1618 args=(test, result))
1610 t.start()
1619 t.start()
1611 running += 1
1620 running += 1
1612
1621
1613 # If we stop early we still need to wait on started tests to
1622 # If we stop early we still need to wait on started tests to
1614 # finish. Otherwise, there is a race between the test completing
1623 # finish. Otherwise, there is a race between the test completing
1615 # and the test's cleanup code running. This could result in the
1624 # and the test's cleanup code running. This could result in the
1616 # test reporting incorrect.
1625 # test reporting incorrect.
1617 if stoppedearly:
1626 if stoppedearly:
1618 while running:
1627 while running:
1619 try:
1628 try:
1620 done.get(True, 1)
1629 done.get(True, 1)
1621 running -= 1
1630 running -= 1
1622 except queue.Empty:
1631 except queue.Empty:
1623 continue
1632 continue
1624 except KeyboardInterrupt:
1633 except KeyboardInterrupt:
1625 for test in runtests:
1634 for test in runtests:
1626 test.abort()
1635 test.abort()
1627
1636
1628 channels = []
1637 channels = []
1629
1638
1630 return result
1639 return result
1631
1640
1632 # Save the most recent 5 wall-clock runtimes of each test to a
1641 # Save the most recent 5 wall-clock runtimes of each test to a
1633 # human-readable text file named .testtimes. Tests are sorted
1642 # human-readable text file named .testtimes. Tests are sorted
1634 # alphabetically, while times for each test are listed from oldest to
1643 # alphabetically, while times for each test are listed from oldest to
1635 # newest.
1644 # newest.
1636
1645
1637 def loadtimes(testdir):
1646 def loadtimes(testdir):
1638 times = []
1647 times = []
1639 try:
1648 try:
1640 with open(os.path.join(testdir, '.testtimes-')) as fp:
1649 with open(os.path.join(testdir, '.testtimes-')) as fp:
1641 for line in fp:
1650 for line in fp:
1642 ts = line.split()
1651 ts = line.split()
1643 times.append((ts[0], [float(t) for t in ts[1:]]))
1652 times.append((ts[0], [float(t) for t in ts[1:]]))
1644 except IOError as err:
1653 except IOError as err:
1645 if err.errno != errno.ENOENT:
1654 if err.errno != errno.ENOENT:
1646 raise
1655 raise
1647 return times
1656 return times
1648
1657
1649 def savetimes(testdir, result):
1658 def savetimes(testdir, result):
1650 saved = dict(loadtimes(testdir))
1659 saved = dict(loadtimes(testdir))
1651 maxruns = 5
1660 maxruns = 5
1652 skipped = set([str(t[0]) for t in result.skipped])
1661 skipped = set([str(t[0]) for t in result.skipped])
1653 for tdata in result.times:
1662 for tdata in result.times:
1654 test, real = tdata[0], tdata[3]
1663 test, real = tdata[0], tdata[3]
1655 if test not in skipped:
1664 if test not in skipped:
1656 ts = saved.setdefault(test, [])
1665 ts = saved.setdefault(test, [])
1657 ts.append(real)
1666 ts.append(real)
1658 ts[:] = ts[-maxruns:]
1667 ts[:] = ts[-maxruns:]
1659
1668
1660 fd, tmpname = tempfile.mkstemp(prefix='.testtimes',
1669 fd, tmpname = tempfile.mkstemp(prefix='.testtimes',
1661 dir=testdir, text=True)
1670 dir=testdir, text=True)
1662 with os.fdopen(fd, 'w') as fp:
1671 with os.fdopen(fd, 'w') as fp:
1663 for name, ts in sorted(saved.iteritems()):
1672 for name, ts in sorted(saved.iteritems()):
1664 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1673 fp.write('%s %s\n' % (name, ' '.join(['%.3f' % (t,) for t in ts])))
1665 timepath = os.path.join(testdir, '.testtimes')
1674 timepath = os.path.join(testdir, '.testtimes')
1666 try:
1675 try:
1667 os.unlink(timepath)
1676 os.unlink(timepath)
1668 except OSError:
1677 except OSError:
1669 pass
1678 pass
1670 try:
1679 try:
1671 os.rename(tmpname, timepath)
1680 os.rename(tmpname, timepath)
1672 except OSError:
1681 except OSError:
1673 pass
1682 pass
1674
1683
1675 class TextTestRunner(unittest.TextTestRunner):
1684 class TextTestRunner(unittest.TextTestRunner):
1676 """Custom unittest test runner that uses appropriate settings."""
1685 """Custom unittest test runner that uses appropriate settings."""
1677
1686
1678 def __init__(self, runner, *args, **kwargs):
1687 def __init__(self, runner, *args, **kwargs):
1679 super(TextTestRunner, self).__init__(*args, **kwargs)
1688 super(TextTestRunner, self).__init__(*args, **kwargs)
1680
1689
1681 self._runner = runner
1690 self._runner = runner
1682
1691
1683 def run(self, test):
1692 def run(self, test):
1684 result = TestResult(self._runner.options, self.stream,
1693 result = TestResult(self._runner.options, self.stream,
1685 self.descriptions, self.verbosity)
1694 self.descriptions, self.verbosity)
1686
1695
1687 test(result)
1696 test(result)
1688
1697
1689 failed = len(result.failures)
1698 failed = len(result.failures)
1690 warned = len(result.warned)
1699 warned = len(result.warned)
1691 skipped = len(result.skipped)
1700 skipped = len(result.skipped)
1692 ignored = len(result.ignored)
1701 ignored = len(result.ignored)
1693
1702
1694 with iolock:
1703 with iolock:
1695 self.stream.writeln('')
1704 self.stream.writeln('')
1696
1705
1697 if not self._runner.options.noskips:
1706 if not self._runner.options.noskips:
1698 for test, msg in result.skipped:
1707 for test, msg in result.skipped:
1699 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1708 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1700 for test, msg in result.warned:
1709 for test, msg in result.warned:
1701 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1710 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1702 for test, msg in result.failures:
1711 for test, msg in result.failures:
1703 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1712 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1704 for test, msg in result.errors:
1713 for test, msg in result.errors:
1705 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1714 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1706
1715
1707 if self._runner.options.xunit:
1716 if self._runner.options.xunit:
1708 with open(self._runner.options.xunit, 'wb') as xuf:
1717 with open(self._runner.options.xunit, 'wb') as xuf:
1709 timesd = dict((t[0], t[3]) for t in result.times)
1718 timesd = dict((t[0], t[3]) for t in result.times)
1710 doc = minidom.Document()
1719 doc = minidom.Document()
1711 s = doc.createElement('testsuite')
1720 s = doc.createElement('testsuite')
1712 s.setAttribute('name', 'run-tests')
1721 s.setAttribute('name', 'run-tests')
1713 s.setAttribute('tests', str(result.testsRun))
1722 s.setAttribute('tests', str(result.testsRun))
1714 s.setAttribute('errors', "0") # TODO
1723 s.setAttribute('errors', "0") # TODO
1715 s.setAttribute('failures', str(failed))
1724 s.setAttribute('failures', str(failed))
1716 s.setAttribute('skipped', str(skipped + ignored))
1725 s.setAttribute('skipped', str(skipped + ignored))
1717 doc.appendChild(s)
1726 doc.appendChild(s)
1718 for tc in result.successes:
1727 for tc in result.successes:
1719 t = doc.createElement('testcase')
1728 t = doc.createElement('testcase')
1720 t.setAttribute('name', tc.name)
1729 t.setAttribute('name', tc.name)
1721 t.setAttribute('time', '%.3f' % timesd[tc.name])
1730 t.setAttribute('time', '%.3f' % timesd[tc.name])
1722 s.appendChild(t)
1731 s.appendChild(t)
1723 for tc, err in sorted(result.faildata.items()):
1732 for tc, err in sorted(result.faildata.items()):
1724 t = doc.createElement('testcase')
1733 t = doc.createElement('testcase')
1725 t.setAttribute('name', tc)
1734 t.setAttribute('name', tc)
1726 t.setAttribute('time', '%.3f' % timesd[tc])
1735 t.setAttribute('time', '%.3f' % timesd[tc])
1727 # createCDATASection expects a unicode or it will
1736 # createCDATASection expects a unicode or it will
1728 # convert using default conversion rules, which will
1737 # convert using default conversion rules, which will
1729 # fail if string isn't ASCII.
1738 # fail if string isn't ASCII.
1730 err = cdatasafe(err).decode('utf-8', 'replace')
1739 err = cdatasafe(err).decode('utf-8', 'replace')
1731 cd = doc.createCDATASection(err)
1740 cd = doc.createCDATASection(err)
1732 t.appendChild(cd)
1741 t.appendChild(cd)
1733 s.appendChild(t)
1742 s.appendChild(t)
1734 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1743 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1735
1744
1736 if self._runner.options.json:
1745 if self._runner.options.json:
1737 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1746 jsonpath = os.path.join(self._runner._testdir, 'report.json')
1738 with open(jsonpath, 'w') as fp:
1747 with open(jsonpath, 'w') as fp:
1739 timesd = {}
1748 timesd = {}
1740 for tdata in result.times:
1749 for tdata in result.times:
1741 test = tdata[0]
1750 test = tdata[0]
1742 timesd[test] = tdata[1:]
1751 timesd[test] = tdata[1:]
1743
1752
1744 outcome = {}
1753 outcome = {}
1745 groups = [('success', ((tc, None)
1754 groups = [('success', ((tc, None)
1746 for tc in result.successes)),
1755 for tc in result.successes)),
1747 ('failure', result.failures),
1756 ('failure', result.failures),
1748 ('skip', result.skipped)]
1757 ('skip', result.skipped)]
1749 for res, testcases in groups:
1758 for res, testcases in groups:
1750 for tc, __ in testcases:
1759 for tc, __ in testcases:
1751 if tc.name in timesd:
1760 if tc.name in timesd:
1752 tres = {'result': res,
1761 tres = {'result': res,
1753 'time': ('%0.3f' % timesd[tc.name][2]),
1762 'time': ('%0.3f' % timesd[tc.name][2]),
1754 'cuser': ('%0.3f' % timesd[tc.name][0]),
1763 'cuser': ('%0.3f' % timesd[tc.name][0]),
1755 'csys': ('%0.3f' % timesd[tc.name][1]),
1764 'csys': ('%0.3f' % timesd[tc.name][1]),
1756 'start': ('%0.3f' % timesd[tc.name][3]),
1765 'start': ('%0.3f' % timesd[tc.name][3]),
1757 'end': ('%0.3f' % timesd[tc.name][4]),
1766 'end': ('%0.3f' % timesd[tc.name][4]),
1758 'diff': result.faildata.get(tc.name,
1767 'diff': result.faildata.get(tc.name,
1759 ''),
1768 ''),
1760 }
1769 }
1761 else:
1770 else:
1762 # blacklisted test
1771 # blacklisted test
1763 tres = {'result': res}
1772 tres = {'result': res}
1764
1773
1765 outcome[tc.name] = tres
1774 outcome[tc.name] = tres
1766 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1775 jsonout = json.dumps(outcome, sort_keys=True, indent=4)
1767 fp.writelines(("testreport =", jsonout))
1776 fp.writelines(("testreport =", jsonout))
1768
1777
1769 self._runner._checkhglib('Tested')
1778 self._runner._checkhglib('Tested')
1770
1779
1771 savetimes(self._runner._testdir, result)
1780 savetimes(self._runner._testdir, result)
1772 self.stream.writeln(
1781 self.stream.writeln(
1773 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1782 '# Ran %d tests, %d skipped, %d warned, %d failed.'
1774 % (result.testsRun,
1783 % (result.testsRun,
1775 skipped + ignored, warned, failed))
1784 skipped + ignored, warned, failed))
1776 if failed:
1785 if failed:
1777 self.stream.writeln('python hash seed: %s' %
1786 self.stream.writeln('python hash seed: %s' %
1778 os.environ['PYTHONHASHSEED'])
1787 os.environ['PYTHONHASHSEED'])
1779 if self._runner.options.time:
1788 if self._runner.options.time:
1780 self.printtimes(result.times)
1789 self.printtimes(result.times)
1781
1790
1782 return result
1791 return result
1783
1792
1784 def printtimes(self, times):
1793 def printtimes(self, times):
1785 # iolock held by run
1794 # iolock held by run
1786 self.stream.writeln('# Producing time report')
1795 self.stream.writeln('# Producing time report')
1787 times.sort(key=lambda t: (t[3]))
1796 times.sort(key=lambda t: (t[3]))
1788 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
1797 cols = '%7.3f %7.3f %7.3f %7.3f %7.3f %s'
1789 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
1798 self.stream.writeln('%-7s %-7s %-7s %-7s %-7s %s' %
1790 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
1799 ('start', 'end', 'cuser', 'csys', 'real', 'Test'))
1791 for tdata in times:
1800 for tdata in times:
1792 test = tdata[0]
1801 test = tdata[0]
1793 cuser, csys, real, start, end = tdata[1:6]
1802 cuser, csys, real, start, end = tdata[1:6]
1794 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
1803 self.stream.writeln(cols % (start, end, cuser, csys, real, test))
1795
1804
1796 class TestRunner(object):
1805 class TestRunner(object):
1797 """Holds context for executing tests.
1806 """Holds context for executing tests.
1798
1807
1799 Tests rely on a lot of state. This object holds it for them.
1808 Tests rely on a lot of state. This object holds it for them.
1800 """
1809 """
1801
1810
1802 # Programs required to run tests.
1811 # Programs required to run tests.
1803 REQUIREDTOOLS = [
1812 REQUIREDTOOLS = [
1804 os.path.basename(_bytespath(sys.executable)),
1813 os.path.basename(_bytespath(sys.executable)),
1805 b'diff',
1814 b'diff',
1806 b'grep',
1815 b'grep',
1807 b'unzip',
1816 b'unzip',
1808 b'gunzip',
1817 b'gunzip',
1809 b'bunzip2',
1818 b'bunzip2',
1810 b'sed',
1819 b'sed',
1811 ]
1820 ]
1812
1821
1813 # Maps file extensions to test class.
1822 # Maps file extensions to test class.
1814 TESTTYPES = [
1823 TESTTYPES = [
1815 (b'.py', PythonTest),
1824 (b'.py', PythonTest),
1816 (b'.t', TTest),
1825 (b'.t', TTest),
1817 ]
1826 ]
1818
1827
1819 def __init__(self):
1828 def __init__(self):
1820 self.options = None
1829 self.options = None
1821 self._hgroot = None
1830 self._hgroot = None
1822 self._testdir = None
1831 self._testdir = None
1823 self._hgtmp = None
1832 self._hgtmp = None
1824 self._installdir = None
1833 self._installdir = None
1825 self._bindir = None
1834 self._bindir = None
1826 self._tmpbinddir = None
1835 self._tmpbinddir = None
1827 self._pythondir = None
1836 self._pythondir = None
1828 self._coveragefile = None
1837 self._coveragefile = None
1829 self._createdfiles = []
1838 self._createdfiles = []
1830 self._hgcommand = None
1839 self._hgcommand = None
1831 self._hgpath = None
1840 self._hgpath = None
1832 self._chgsockdir = None
1841 self._chgsockdir = None
1833 self._portoffset = 0
1842 self._portoffset = 0
1834 self._ports = {}
1843 self._ports = {}
1835
1844
1836 def run(self, args, parser=None):
1845 def run(self, args, parser=None):
1837 """Run the test suite."""
1846 """Run the test suite."""
1838 oldmask = os.umask(0o22)
1847 oldmask = os.umask(0o22)
1839 try:
1848 try:
1840 parser = parser or getparser()
1849 parser = parser or getparser()
1841 options, args = parseargs(args, parser)
1850 options, args = parseargs(args, parser)
1842 # positional arguments are paths to test files to run, so
1851 # positional arguments are paths to test files to run, so
1843 # we make sure they're all bytestrings
1852 # we make sure they're all bytestrings
1844 args = [_bytespath(a) for a in args]
1853 args = [_bytespath(a) for a in args]
1845 self.options = options
1854 self.options = options
1846
1855
1847 self._checktools()
1856 self._checktools()
1848 tests = self.findtests(args)
1857 tests = self.findtests(args)
1849 if options.profile_runner:
1858 if options.profile_runner:
1850 import statprof
1859 import statprof
1851 statprof.start()
1860 statprof.start()
1852 result = self._run(tests)
1861 result = self._run(tests)
1853 if options.profile_runner:
1862 if options.profile_runner:
1854 statprof.stop()
1863 statprof.stop()
1855 statprof.display()
1864 statprof.display()
1856 return result
1865 return result
1857
1866
1858 finally:
1867 finally:
1859 os.umask(oldmask)
1868 os.umask(oldmask)
1860
1869
1861 def _run(self, tests):
1870 def _run(self, tests):
1862 if self.options.random:
1871 if self.options.random:
1863 random.shuffle(tests)
1872 random.shuffle(tests)
1864 else:
1873 else:
1865 # keywords for slow tests
1874 # keywords for slow tests
1866 slow = {b'svn': 10,
1875 slow = {b'svn': 10,
1867 b'cvs': 10,
1876 b'cvs': 10,
1868 b'hghave': 10,
1877 b'hghave': 10,
1869 b'largefiles-update': 10,
1878 b'largefiles-update': 10,
1870 b'run-tests': 10,
1879 b'run-tests': 10,
1871 b'corruption': 10,
1880 b'corruption': 10,
1872 b'race': 10,
1881 b'race': 10,
1873 b'i18n': 10,
1882 b'i18n': 10,
1874 b'check': 100,
1883 b'check': 100,
1875 b'gendoc': 100,
1884 b'gendoc': 100,
1876 b'contrib-perf': 200,
1885 b'contrib-perf': 200,
1877 }
1886 }
1878 perf = {}
1887 perf = {}
1879 def sortkey(f):
1888 def sortkey(f):
1880 # run largest tests first, as they tend to take the longest
1889 # run largest tests first, as they tend to take the longest
1881 try:
1890 try:
1882 return perf[f]
1891 return perf[f]
1883 except KeyError:
1892 except KeyError:
1884 try:
1893 try:
1885 val = -os.stat(f).st_size
1894 val = -os.stat(f).st_size
1886 except OSError as e:
1895 except OSError as e:
1887 if e.errno != errno.ENOENT:
1896 if e.errno != errno.ENOENT:
1888 raise
1897 raise
1889 perf[f] = -1e9 # file does not exist, tell early
1898 perf[f] = -1e9 # file does not exist, tell early
1890 return -1e9
1899 return -1e9
1891 for kw, mul in slow.items():
1900 for kw, mul in slow.items():
1892 if kw in f:
1901 if kw in f:
1893 val *= mul
1902 val *= mul
1894 if f.endswith(b'.py'):
1903 if f.endswith(b'.py'):
1895 val /= 10.0
1904 val /= 10.0
1896 perf[f] = val / 1000.0
1905 perf[f] = val / 1000.0
1897 return perf[f]
1906 return perf[f]
1898 tests.sort(key=sortkey)
1907 tests.sort(key=sortkey)
1899
1908
1900 self._testdir = osenvironb[b'TESTDIR'] = getattr(
1909 self._testdir = osenvironb[b'TESTDIR'] = getattr(
1901 os, 'getcwdb', os.getcwd)()
1910 os, 'getcwdb', os.getcwd)()
1902
1911
1903 if 'PYTHONHASHSEED' not in os.environ:
1912 if 'PYTHONHASHSEED' not in os.environ:
1904 # use a random python hash seed all the time
1913 # use a random python hash seed all the time
1905 # we do the randomness ourself to know what seed is used
1914 # we do the randomness ourself to know what seed is used
1906 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1915 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1907
1916
1908 if self.options.tmpdir:
1917 if self.options.tmpdir:
1909 self.options.keep_tmpdir = True
1918 self.options.keep_tmpdir = True
1910 tmpdir = _bytespath(self.options.tmpdir)
1919 tmpdir = _bytespath(self.options.tmpdir)
1911 if os.path.exists(tmpdir):
1920 if os.path.exists(tmpdir):
1912 # Meaning of tmpdir has changed since 1.3: we used to create
1921 # Meaning of tmpdir has changed since 1.3: we used to create
1913 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1922 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1914 # tmpdir already exists.
1923 # tmpdir already exists.
1915 print("error: temp dir %r already exists" % tmpdir)
1924 print("error: temp dir %r already exists" % tmpdir)
1916 return 1
1925 return 1
1917
1926
1918 # Automatically removing tmpdir sounds convenient, but could
1927 # Automatically removing tmpdir sounds convenient, but could
1919 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1928 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1920 # or "--tmpdir=$HOME".
1929 # or "--tmpdir=$HOME".
1921 #vlog("# Removing temp dir", tmpdir)
1930 #vlog("# Removing temp dir", tmpdir)
1922 #shutil.rmtree(tmpdir)
1931 #shutil.rmtree(tmpdir)
1923 os.makedirs(tmpdir)
1932 os.makedirs(tmpdir)
1924 else:
1933 else:
1925 d = None
1934 d = None
1926 if os.name == 'nt':
1935 if os.name == 'nt':
1927 # without this, we get the default temp dir location, but
1936 # without this, we get the default temp dir location, but
1928 # in all lowercase, which causes troubles with paths (issue3490)
1937 # in all lowercase, which causes troubles with paths (issue3490)
1929 d = osenvironb.get(b'TMP', None)
1938 d = osenvironb.get(b'TMP', None)
1930 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
1939 tmpdir = tempfile.mkdtemp(b'', b'hgtests.', d)
1931
1940
1932 self._hgtmp = osenvironb[b'HGTMP'] = (
1941 self._hgtmp = osenvironb[b'HGTMP'] = (
1933 os.path.realpath(tmpdir))
1942 os.path.realpath(tmpdir))
1934
1943
1935 if self.options.with_hg:
1944 if self.options.with_hg:
1936 self._installdir = None
1945 self._installdir = None
1937 whg = self.options.with_hg
1946 whg = self.options.with_hg
1938 self._bindir = os.path.dirname(os.path.realpath(whg))
1947 self._bindir = os.path.dirname(os.path.realpath(whg))
1939 assert isinstance(self._bindir, bytes)
1948 assert isinstance(self._bindir, bytes)
1940 self._hgcommand = os.path.basename(whg)
1949 self._hgcommand = os.path.basename(whg)
1941 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
1950 self._tmpbindir = os.path.join(self._hgtmp, b'install', b'bin')
1942 os.makedirs(self._tmpbindir)
1951 os.makedirs(self._tmpbindir)
1943
1952
1944 # This looks redundant with how Python initializes sys.path from
1953 # This looks redundant with how Python initializes sys.path from
1945 # the location of the script being executed. Needed because the
1954 # the location of the script being executed. Needed because the
1946 # "hg" specified by --with-hg is not the only Python script
1955 # "hg" specified by --with-hg is not the only Python script
1947 # executed in the test suite that needs to import 'mercurial'
1956 # executed in the test suite that needs to import 'mercurial'
1948 # ... which means it's not really redundant at all.
1957 # ... which means it's not really redundant at all.
1949 self._pythondir = self._bindir
1958 self._pythondir = self._bindir
1950 else:
1959 else:
1951 self._installdir = os.path.join(self._hgtmp, b"install")
1960 self._installdir = os.path.join(self._hgtmp, b"install")
1952 self._bindir = os.path.join(self._installdir, b"bin")
1961 self._bindir = os.path.join(self._installdir, b"bin")
1953 self._hgcommand = b'hg'
1962 self._hgcommand = b'hg'
1954 self._tmpbindir = self._bindir
1963 self._tmpbindir = self._bindir
1955 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
1964 self._pythondir = os.path.join(self._installdir, b"lib", b"python")
1956
1965
1957 # set up crafted chg environment, then replace "hg" command by "chg"
1966 # set up crafted chg environment, then replace "hg" command by "chg"
1958 chgbindir = self._bindir
1967 chgbindir = self._bindir
1959 if self.options.chg or self.options.with_chg:
1968 if self.options.chg or self.options.with_chg:
1960 self._chgsockdir = d = os.path.join(self._hgtmp, b'chgsock')
1969 self._chgsockdir = d = os.path.join(self._hgtmp, b'chgsock')
1961 os.mkdir(d)
1970 os.mkdir(d)
1962 osenvironb[b'CHGSOCKNAME'] = os.path.join(d, b"server")
1971 osenvironb[b'CHGSOCKNAME'] = os.path.join(d, b"server")
1963 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
1972 osenvironb[b'CHGHG'] = os.path.join(self._bindir, self._hgcommand)
1964 if self.options.chg:
1973 if self.options.chg:
1965 self._hgcommand = b'chg'
1974 self._hgcommand = b'chg'
1966 elif self.options.with_chg:
1975 elif self.options.with_chg:
1967 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
1976 chgbindir = os.path.dirname(os.path.realpath(self.options.with_chg))
1968 self._hgcommand = os.path.basename(self.options.with_chg)
1977 self._hgcommand = os.path.basename(self.options.with_chg)
1969
1978
1970 osenvironb[b"BINDIR"] = self._bindir
1979 osenvironb[b"BINDIR"] = self._bindir
1971 osenvironb[b"PYTHON"] = PYTHON
1980 osenvironb[b"PYTHON"] = PYTHON
1972
1981
1973 fileb = _bytespath(__file__)
1982 fileb = _bytespath(__file__)
1974 runtestdir = os.path.abspath(os.path.dirname(fileb))
1983 runtestdir = os.path.abspath(os.path.dirname(fileb))
1975 osenvironb[b'RUNTESTDIR'] = runtestdir
1984 osenvironb[b'RUNTESTDIR'] = runtestdir
1976 if PYTHON3:
1985 if PYTHON3:
1977 sepb = _bytespath(os.pathsep)
1986 sepb = _bytespath(os.pathsep)
1978 else:
1987 else:
1979 sepb = os.pathsep
1988 sepb = os.pathsep
1980 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
1989 path = [self._bindir, runtestdir] + osenvironb[b"PATH"].split(sepb)
1981 if os.path.islink(__file__):
1990 if os.path.islink(__file__):
1982 # test helper will likely be at the end of the symlink
1991 # test helper will likely be at the end of the symlink
1983 realfile = os.path.realpath(fileb)
1992 realfile = os.path.realpath(fileb)
1984 realdir = os.path.abspath(os.path.dirname(realfile))
1993 realdir = os.path.abspath(os.path.dirname(realfile))
1985 path.insert(2, realdir)
1994 path.insert(2, realdir)
1986 if chgbindir != self._bindir:
1995 if chgbindir != self._bindir:
1987 path.insert(1, chgbindir)
1996 path.insert(1, chgbindir)
1988 if self._testdir != runtestdir:
1997 if self._testdir != runtestdir:
1989 path = [self._testdir] + path
1998 path = [self._testdir] + path
1990 if self._tmpbindir != self._bindir:
1999 if self._tmpbindir != self._bindir:
1991 path = [self._tmpbindir] + path
2000 path = [self._tmpbindir] + path
1992 osenvironb[b"PATH"] = sepb.join(path)
2001 osenvironb[b"PATH"] = sepb.join(path)
1993
2002
1994 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
2003 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1995 # can run .../tests/run-tests.py test-foo where test-foo
2004 # can run .../tests/run-tests.py test-foo where test-foo
1996 # adds an extension to HGRC. Also include run-test.py directory to
2005 # adds an extension to HGRC. Also include run-test.py directory to
1997 # import modules like heredoctest.
2006 # import modules like heredoctest.
1998 pypath = [self._pythondir, self._testdir, runtestdir]
2007 pypath = [self._pythondir, self._testdir, runtestdir]
1999 # We have to augment PYTHONPATH, rather than simply replacing
2008 # We have to augment PYTHONPATH, rather than simply replacing
2000 # it, in case external libraries are only available via current
2009 # it, in case external libraries are only available via current
2001 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2010 # PYTHONPATH. (In particular, the Subversion bindings on OS X
2002 # are in /opt/subversion.)
2011 # are in /opt/subversion.)
2003 oldpypath = osenvironb.get(IMPL_PATH)
2012 oldpypath = osenvironb.get(IMPL_PATH)
2004 if oldpypath:
2013 if oldpypath:
2005 pypath.append(oldpypath)
2014 pypath.append(oldpypath)
2006 osenvironb[IMPL_PATH] = sepb.join(pypath)
2015 osenvironb[IMPL_PATH] = sepb.join(pypath)
2007
2016
2008 if self.options.pure:
2017 if self.options.pure:
2009 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2018 os.environ["HGTEST_RUN_TESTS_PURE"] = "--pure"
2010
2019
2011 if self.options.allow_slow_tests:
2020 if self.options.allow_slow_tests:
2012 os.environ["HGTEST_SLOW"] = "slow"
2021 os.environ["HGTEST_SLOW"] = "slow"
2013 elif 'HGTEST_SLOW' in os.environ:
2022 elif 'HGTEST_SLOW' in os.environ:
2014 del os.environ['HGTEST_SLOW']
2023 del os.environ['HGTEST_SLOW']
2015
2024
2016 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2025 self._coveragefile = os.path.join(self._testdir, b'.coverage')
2017
2026
2018 vlog("# Using TESTDIR", self._testdir)
2027 vlog("# Using TESTDIR", self._testdir)
2019 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2028 vlog("# Using RUNTESTDIR", osenvironb[b'RUNTESTDIR'])
2020 vlog("# Using HGTMP", self._hgtmp)
2029 vlog("# Using HGTMP", self._hgtmp)
2021 vlog("# Using PATH", os.environ["PATH"])
2030 vlog("# Using PATH", os.environ["PATH"])
2022 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2031 vlog("# Using", IMPL_PATH, osenvironb[IMPL_PATH])
2023
2032
2024 try:
2033 try:
2025 return self._runtests(tests) or 0
2034 return self._runtests(tests) or 0
2026 finally:
2035 finally:
2027 time.sleep(.1)
2036 time.sleep(.1)
2028 self._cleanup()
2037 self._cleanup()
2029
2038
2030 def findtests(self, args):
2039 def findtests(self, args):
2031 """Finds possible test files from arguments.
2040 """Finds possible test files from arguments.
2032
2041
2033 If you wish to inject custom tests into the test harness, this would
2042 If you wish to inject custom tests into the test harness, this would
2034 be a good function to monkeypatch or override in a derived class.
2043 be a good function to monkeypatch or override in a derived class.
2035 """
2044 """
2036 if not args:
2045 if not args:
2037 if self.options.changed:
2046 if self.options.changed:
2038 proc = Popen4('hg st --rev "%s" -man0 .' %
2047 proc = Popen4('hg st --rev "%s" -man0 .' %
2039 self.options.changed, None, 0)
2048 self.options.changed, None, 0)
2040 stdout, stderr = proc.communicate()
2049 stdout, stderr = proc.communicate()
2041 args = stdout.strip(b'\0').split(b'\0')
2050 args = stdout.strip(b'\0').split(b'\0')
2042 else:
2051 else:
2043 args = os.listdir(b'.')
2052 args = os.listdir(b'.')
2044
2053
2045 return [t for t in args
2054 return [t for t in args
2046 if os.path.basename(t).startswith(b'test-')
2055 if os.path.basename(t).startswith(b'test-')
2047 and (t.endswith(b'.py') or t.endswith(b'.t'))]
2056 and (t.endswith(b'.py') or t.endswith(b'.t'))]
2048
2057
2049 def _runtests(self, tests):
2058 def _runtests(self, tests):
2050 try:
2059 try:
2051 if self._installdir:
2060 if self._installdir:
2052 self._installhg()
2061 self._installhg()
2053 self._checkhglib("Testing")
2062 self._checkhglib("Testing")
2054 else:
2063 else:
2055 self._usecorrectpython()
2064 self._usecorrectpython()
2056 if self.options.chg:
2065 if self.options.chg:
2057 assert self._installdir
2066 assert self._installdir
2058 self._installchg()
2067 self._installchg()
2059
2068
2060 if self.options.restart:
2069 if self.options.restart:
2061 orig = list(tests)
2070 orig = list(tests)
2062 while tests:
2071 while tests:
2063 if os.path.exists(tests[0] + ".err"):
2072 if os.path.exists(tests[0] + ".err"):
2064 break
2073 break
2065 tests.pop(0)
2074 tests.pop(0)
2066 if not tests:
2075 if not tests:
2067 print("running all tests")
2076 print("running all tests")
2068 tests = orig
2077 tests = orig
2069
2078
2070 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
2079 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
2071
2080
2072 failed = False
2081 failed = False
2073 warned = False
2082 warned = False
2074 kws = self.options.keywords
2083 kws = self.options.keywords
2075 if kws is not None and PYTHON3:
2084 if kws is not None and PYTHON3:
2076 kws = kws.encode('utf-8')
2085 kws = kws.encode('utf-8')
2077
2086
2078 suite = TestSuite(self._testdir,
2087 suite = TestSuite(self._testdir,
2079 jobs=self.options.jobs,
2088 jobs=self.options.jobs,
2080 whitelist=self.options.whitelisted,
2089 whitelist=self.options.whitelisted,
2081 blacklist=self.options.blacklist,
2090 blacklist=self.options.blacklist,
2082 retest=self.options.retest,
2091 retest=self.options.retest,
2083 keywords=kws,
2092 keywords=kws,
2084 loop=self.options.loop,
2093 loop=self.options.loop,
2085 runs_per_test=self.options.runs_per_test,
2094 runs_per_test=self.options.runs_per_test,
2086 showchannels=self.options.showchannels,
2095 showchannels=self.options.showchannels,
2087 tests=tests, loadtest=self._gettest)
2096 tests=tests, loadtest=self._gettest)
2088 verbosity = 1
2097 verbosity = 1
2089 if self.options.verbose:
2098 if self.options.verbose:
2090 verbosity = 2
2099 verbosity = 2
2091 runner = TextTestRunner(self, verbosity=verbosity)
2100 runner = TextTestRunner(self, verbosity=verbosity)
2092 result = runner.run(suite)
2101 result = runner.run(suite)
2093
2102
2094 if result.failures:
2103 if result.failures:
2095 failed = True
2104 failed = True
2096 if result.warned:
2105 if result.warned:
2097 warned = True
2106 warned = True
2098
2107
2099 if self.options.anycoverage:
2108 if self.options.anycoverage:
2100 self._outputcoverage()
2109 self._outputcoverage()
2101 except KeyboardInterrupt:
2110 except KeyboardInterrupt:
2102 failed = True
2111 failed = True
2103 print("\ninterrupted!")
2112 print("\ninterrupted!")
2104
2113
2105 if failed:
2114 if failed:
2106 return 1
2115 return 1
2107 if warned:
2116 if warned:
2108 return 80
2117 return 80
2109
2118
2110 def _getport(self, count):
2119 def _getport(self, count):
2111 port = self._ports.get(count) # do we have a cached entry?
2120 port = self._ports.get(count) # do we have a cached entry?
2112 if port is None:
2121 if port is None:
2113 portneeded = 3
2122 portneeded = 3
2114 # above 100 tries we just give up and let test reports failure
2123 # above 100 tries we just give up and let test reports failure
2115 for tries in xrange(100):
2124 for tries in xrange(100):
2116 allfree = True
2125 allfree = True
2117 port = self.options.port + self._portoffset
2126 port = self.options.port + self._portoffset
2118 for idx in xrange(portneeded):
2127 for idx in xrange(portneeded):
2119 if not checkportisavailable(port + idx):
2128 if not checkportisavailable(port + idx):
2120 allfree = False
2129 allfree = False
2121 break
2130 break
2122 self._portoffset += portneeded
2131 self._portoffset += portneeded
2123 if allfree:
2132 if allfree:
2124 break
2133 break
2125 self._ports[count] = port
2134 self._ports[count] = port
2126 return port
2135 return port
2127
2136
2128 def _gettest(self, test, count):
2137 def _gettest(self, test, count):
2129 """Obtain a Test by looking at its filename.
2138 """Obtain a Test by looking at its filename.
2130
2139
2131 Returns a Test instance. The Test may not be runnable if it doesn't
2140 Returns a Test instance. The Test may not be runnable if it doesn't
2132 map to a known type.
2141 map to a known type.
2133 """
2142 """
2134 lctest = test.lower()
2143 lctest = test.lower()
2135 testcls = Test
2144 testcls = Test
2136
2145
2137 for ext, cls in self.TESTTYPES:
2146 for ext, cls in self.TESTTYPES:
2138 if lctest.endswith(ext):
2147 if lctest.endswith(ext):
2139 testcls = cls
2148 testcls = cls
2140 break
2149 break
2141
2150
2142 refpath = os.path.join(self._testdir, test)
2151 refpath = os.path.join(self._testdir, test)
2143 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2152 tmpdir = os.path.join(self._hgtmp, b'child%d' % count)
2144
2153
2145 t = testcls(refpath, tmpdir,
2154 t = testcls(refpath, tmpdir,
2146 keeptmpdir=self.options.keep_tmpdir,
2155 keeptmpdir=self.options.keep_tmpdir,
2147 debug=self.options.debug,
2156 debug=self.options.debug,
2148 timeout=self.options.timeout,
2157 timeout=self.options.timeout,
2149 startport=self._getport(count),
2158 startport=self._getport(count),
2150 extraconfigopts=self.options.extra_config_opt,
2159 extraconfigopts=self.options.extra_config_opt,
2151 py3kwarnings=self.options.py3k_warnings,
2160 py3kwarnings=self.options.py3k_warnings,
2152 shell=self.options.shell,
2161 shell=self.options.shell,
2153 hgcommand=self._hgcommand)
2162 hgcommand=self._hgcommand)
2154 t.should_reload = True
2163 t.should_reload = True
2155 return t
2164 return t
2156
2165
2157 def _cleanup(self):
2166 def _cleanup(self):
2158 """Clean up state from this test invocation."""
2167 """Clean up state from this test invocation."""
2159 if self._chgsockdir:
2168 if self._chgsockdir:
2160 self._killchgdaemons()
2169 self._killchgdaemons()
2161
2170
2162 if self.options.keep_tmpdir:
2171 if self.options.keep_tmpdir:
2163 return
2172 return
2164
2173
2165 vlog("# Cleaning up HGTMP", self._hgtmp)
2174 vlog("# Cleaning up HGTMP", self._hgtmp)
2166 shutil.rmtree(self._hgtmp, True)
2175 shutil.rmtree(self._hgtmp, True)
2167 for f in self._createdfiles:
2176 for f in self._createdfiles:
2168 try:
2177 try:
2169 os.remove(f)
2178 os.remove(f)
2170 except OSError:
2179 except OSError:
2171 pass
2180 pass
2172
2181
2173 def _usecorrectpython(self):
2182 def _usecorrectpython(self):
2174 """Configure the environment to use the appropriate Python in tests."""
2183 """Configure the environment to use the appropriate Python in tests."""
2175 # Tests must use the same interpreter as us or bad things will happen.
2184 # Tests must use the same interpreter as us or bad things will happen.
2176 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2185 pyexename = sys.platform == 'win32' and b'python.exe' or b'python'
2177 if getattr(os, 'symlink', None):
2186 if getattr(os, 'symlink', None):
2178 vlog("# Making python executable in test path a symlink to '%s'" %
2187 vlog("# Making python executable in test path a symlink to '%s'" %
2179 sys.executable)
2188 sys.executable)
2180 mypython = os.path.join(self._tmpbindir, pyexename)
2189 mypython = os.path.join(self._tmpbindir, pyexename)
2181 try:
2190 try:
2182 if os.readlink(mypython) == sys.executable:
2191 if os.readlink(mypython) == sys.executable:
2183 return
2192 return
2184 os.unlink(mypython)
2193 os.unlink(mypython)
2185 except OSError as err:
2194 except OSError as err:
2186 if err.errno != errno.ENOENT:
2195 if err.errno != errno.ENOENT:
2187 raise
2196 raise
2188 if self._findprogram(pyexename) != sys.executable:
2197 if self._findprogram(pyexename) != sys.executable:
2189 try:
2198 try:
2190 os.symlink(sys.executable, mypython)
2199 os.symlink(sys.executable, mypython)
2191 self._createdfiles.append(mypython)
2200 self._createdfiles.append(mypython)
2192 except OSError as err:
2201 except OSError as err:
2193 # child processes may race, which is harmless
2202 # child processes may race, which is harmless
2194 if err.errno != errno.EEXIST:
2203 if err.errno != errno.EEXIST:
2195 raise
2204 raise
2196 else:
2205 else:
2197 exedir, exename = os.path.split(sys.executable)
2206 exedir, exename = os.path.split(sys.executable)
2198 vlog("# Modifying search path to find %s as %s in '%s'" %
2207 vlog("# Modifying search path to find %s as %s in '%s'" %
2199 (exename, pyexename, exedir))
2208 (exename, pyexename, exedir))
2200 path = os.environ['PATH'].split(os.pathsep)
2209 path = os.environ['PATH'].split(os.pathsep)
2201 while exedir in path:
2210 while exedir in path:
2202 path.remove(exedir)
2211 path.remove(exedir)
2203 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2212 os.environ['PATH'] = os.pathsep.join([exedir] + path)
2204 if not self._findprogram(pyexename):
2213 if not self._findprogram(pyexename):
2205 print("WARNING: Cannot find %s in search path" % pyexename)
2214 print("WARNING: Cannot find %s in search path" % pyexename)
2206
2215
2207 def _installhg(self):
2216 def _installhg(self):
2208 """Install hg into the test environment.
2217 """Install hg into the test environment.
2209
2218
2210 This will also configure hg with the appropriate testing settings.
2219 This will also configure hg with the appropriate testing settings.
2211 """
2220 """
2212 vlog("# Performing temporary installation of HG")
2221 vlog("# Performing temporary installation of HG")
2213 installerrs = os.path.join(b"tests", b"install.err")
2222 installerrs = os.path.join(b"tests", b"install.err")
2214 compiler = ''
2223 compiler = ''
2215 if self.options.compiler:
2224 if self.options.compiler:
2216 compiler = '--compiler ' + self.options.compiler
2225 compiler = '--compiler ' + self.options.compiler
2217 if self.options.pure:
2226 if self.options.pure:
2218 pure = b"--pure"
2227 pure = b"--pure"
2219 else:
2228 else:
2220 pure = b""
2229 pure = b""
2221 py3 = ''
2230 py3 = ''
2222
2231
2223 # Run installer in hg root
2232 # Run installer in hg root
2224 script = os.path.realpath(sys.argv[0])
2233 script = os.path.realpath(sys.argv[0])
2225 exe = sys.executable
2234 exe = sys.executable
2226 if PYTHON3:
2235 if PYTHON3:
2227 py3 = b'--c2to3'
2236 py3 = b'--c2to3'
2228 compiler = _bytespath(compiler)
2237 compiler = _bytespath(compiler)
2229 script = _bytespath(script)
2238 script = _bytespath(script)
2230 exe = _bytespath(exe)
2239 exe = _bytespath(exe)
2231 hgroot = os.path.dirname(os.path.dirname(script))
2240 hgroot = os.path.dirname(os.path.dirname(script))
2232 self._hgroot = hgroot
2241 self._hgroot = hgroot
2233 os.chdir(hgroot)
2242 os.chdir(hgroot)
2234 nohome = b'--home=""'
2243 nohome = b'--home=""'
2235 if os.name == 'nt':
2244 if os.name == 'nt':
2236 # The --home="" trick works only on OS where os.sep == '/'
2245 # The --home="" trick works only on OS where os.sep == '/'
2237 # because of a distutils convert_path() fast-path. Avoid it at
2246 # because of a distutils convert_path() fast-path. Avoid it at
2238 # least on Windows for now, deal with .pydistutils.cfg bugs
2247 # least on Windows for now, deal with .pydistutils.cfg bugs
2239 # when they happen.
2248 # when they happen.
2240 nohome = b''
2249 nohome = b''
2241 cmd = (b'%(exe)s setup.py %(py3)s %(pure)s clean --all'
2250 cmd = (b'%(exe)s setup.py %(py3)s %(pure)s clean --all'
2242 b' build %(compiler)s --build-base="%(base)s"'
2251 b' build %(compiler)s --build-base="%(base)s"'
2243 b' install --force --prefix="%(prefix)s"'
2252 b' install --force --prefix="%(prefix)s"'
2244 b' --install-lib="%(libdir)s"'
2253 b' --install-lib="%(libdir)s"'
2245 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2254 b' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
2246 % {b'exe': exe, b'py3': py3, b'pure': pure,
2255 % {b'exe': exe, b'py3': py3, b'pure': pure,
2247 b'compiler': compiler,
2256 b'compiler': compiler,
2248 b'base': os.path.join(self._hgtmp, b"build"),
2257 b'base': os.path.join(self._hgtmp, b"build"),
2249 b'prefix': self._installdir, b'libdir': self._pythondir,
2258 b'prefix': self._installdir, b'libdir': self._pythondir,
2250 b'bindir': self._bindir,
2259 b'bindir': self._bindir,
2251 b'nohome': nohome, b'logfile': installerrs})
2260 b'nohome': nohome, b'logfile': installerrs})
2252
2261
2253 # setuptools requires install directories to exist.
2262 # setuptools requires install directories to exist.
2254 def makedirs(p):
2263 def makedirs(p):
2255 try:
2264 try:
2256 os.makedirs(p)
2265 os.makedirs(p)
2257 except OSError as e:
2266 except OSError as e:
2258 if e.errno != errno.EEXIST:
2267 if e.errno != errno.EEXIST:
2259 raise
2268 raise
2260 makedirs(self._pythondir)
2269 makedirs(self._pythondir)
2261 makedirs(self._bindir)
2270 makedirs(self._bindir)
2262
2271
2263 vlog("# Running", cmd)
2272 vlog("# Running", cmd)
2264 if os.system(cmd) == 0:
2273 if os.system(cmd) == 0:
2265 if not self.options.verbose:
2274 if not self.options.verbose:
2266 try:
2275 try:
2267 os.remove(installerrs)
2276 os.remove(installerrs)
2268 except OSError as e:
2277 except OSError as e:
2269 if e.errno != errno.ENOENT:
2278 if e.errno != errno.ENOENT:
2270 raise
2279 raise
2271 else:
2280 else:
2272 f = open(installerrs, 'rb')
2281 f = open(installerrs, 'rb')
2273 for line in f:
2282 for line in f:
2274 if PYTHON3:
2283 if PYTHON3:
2275 sys.stdout.buffer.write(line)
2284 sys.stdout.buffer.write(line)
2276 else:
2285 else:
2277 sys.stdout.write(line)
2286 sys.stdout.write(line)
2278 f.close()
2287 f.close()
2279 sys.exit(1)
2288 sys.exit(1)
2280 os.chdir(self._testdir)
2289 os.chdir(self._testdir)
2281
2290
2282 self._usecorrectpython()
2291 self._usecorrectpython()
2283
2292
2284 if self.options.py3k_warnings and not self.options.anycoverage:
2293 if self.options.py3k_warnings and not self.options.anycoverage:
2285 vlog("# Updating hg command to enable Py3k Warnings switch")
2294 vlog("# Updating hg command to enable Py3k Warnings switch")
2286 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2295 f = open(os.path.join(self._bindir, 'hg'), 'rb')
2287 lines = [line.rstrip() for line in f]
2296 lines = [line.rstrip() for line in f]
2288 lines[0] += ' -3'
2297 lines[0] += ' -3'
2289 f.close()
2298 f.close()
2290 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2299 f = open(os.path.join(self._bindir, 'hg'), 'wb')
2291 for line in lines:
2300 for line in lines:
2292 f.write(line + '\n')
2301 f.write(line + '\n')
2293 f.close()
2302 f.close()
2294
2303
2295 hgbat = os.path.join(self._bindir, b'hg.bat')
2304 hgbat = os.path.join(self._bindir, b'hg.bat')
2296 if os.path.isfile(hgbat):
2305 if os.path.isfile(hgbat):
2297 # hg.bat expects to be put in bin/scripts while run-tests.py
2306 # hg.bat expects to be put in bin/scripts while run-tests.py
2298 # installation layout put it in bin/ directly. Fix it
2307 # installation layout put it in bin/ directly. Fix it
2299 f = open(hgbat, 'rb')
2308 f = open(hgbat, 'rb')
2300 data = f.read()
2309 data = f.read()
2301 f.close()
2310 f.close()
2302 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2311 if b'"%~dp0..\python" "%~dp0hg" %*' in data:
2303 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2312 data = data.replace(b'"%~dp0..\python" "%~dp0hg" %*',
2304 b'"%~dp0python" "%~dp0hg" %*')
2313 b'"%~dp0python" "%~dp0hg" %*')
2305 f = open(hgbat, 'wb')
2314 f = open(hgbat, 'wb')
2306 f.write(data)
2315 f.write(data)
2307 f.close()
2316 f.close()
2308 else:
2317 else:
2309 print('WARNING: cannot fix hg.bat reference to python.exe')
2318 print('WARNING: cannot fix hg.bat reference to python.exe')
2310
2319
2311 if self.options.anycoverage:
2320 if self.options.anycoverage:
2312 custom = os.path.join(self._testdir, 'sitecustomize.py')
2321 custom = os.path.join(self._testdir, 'sitecustomize.py')
2313 target = os.path.join(self._pythondir, 'sitecustomize.py')
2322 target = os.path.join(self._pythondir, 'sitecustomize.py')
2314 vlog('# Installing coverage trigger to %s' % target)
2323 vlog('# Installing coverage trigger to %s' % target)
2315 shutil.copyfile(custom, target)
2324 shutil.copyfile(custom, target)
2316 rc = os.path.join(self._testdir, '.coveragerc')
2325 rc = os.path.join(self._testdir, '.coveragerc')
2317 vlog('# Installing coverage rc to %s' % rc)
2326 vlog('# Installing coverage rc to %s' % rc)
2318 os.environ['COVERAGE_PROCESS_START'] = rc
2327 os.environ['COVERAGE_PROCESS_START'] = rc
2319 covdir = os.path.join(self._installdir, '..', 'coverage')
2328 covdir = os.path.join(self._installdir, '..', 'coverage')
2320 try:
2329 try:
2321 os.mkdir(covdir)
2330 os.mkdir(covdir)
2322 except OSError as e:
2331 except OSError as e:
2323 if e.errno != errno.EEXIST:
2332 if e.errno != errno.EEXIST:
2324 raise
2333 raise
2325
2334
2326 os.environ['COVERAGE_DIR'] = covdir
2335 os.environ['COVERAGE_DIR'] = covdir
2327
2336
2328 def _checkhglib(self, verb):
2337 def _checkhglib(self, verb):
2329 """Ensure that the 'mercurial' package imported by python is
2338 """Ensure that the 'mercurial' package imported by python is
2330 the one we expect it to be. If not, print a warning to stderr."""
2339 the one we expect it to be. If not, print a warning to stderr."""
2331 if ((self._bindir == self._pythondir) and
2340 if ((self._bindir == self._pythondir) and
2332 (self._bindir != self._tmpbindir)):
2341 (self._bindir != self._tmpbindir)):
2333 # The pythondir has been inferred from --with-hg flag.
2342 # The pythondir has been inferred from --with-hg flag.
2334 # We cannot expect anything sensible here.
2343 # We cannot expect anything sensible here.
2335 return
2344 return
2336 expecthg = os.path.join(self._pythondir, b'mercurial')
2345 expecthg = os.path.join(self._pythondir, b'mercurial')
2337 actualhg = self._gethgpath()
2346 actualhg = self._gethgpath()
2338 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2347 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
2339 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2348 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
2340 ' (expected %s)\n'
2349 ' (expected %s)\n'
2341 % (verb, actualhg, expecthg))
2350 % (verb, actualhg, expecthg))
2342 def _gethgpath(self):
2351 def _gethgpath(self):
2343 """Return the path to the mercurial package that is actually found by
2352 """Return the path to the mercurial package that is actually found by
2344 the current Python interpreter."""
2353 the current Python interpreter."""
2345 if self._hgpath is not None:
2354 if self._hgpath is not None:
2346 return self._hgpath
2355 return self._hgpath
2347
2356
2348 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2357 cmd = b'%s -c "import mercurial; print (mercurial.__path__[0])"'
2349 cmd = cmd % PYTHON
2358 cmd = cmd % PYTHON
2350 if PYTHON3:
2359 if PYTHON3:
2351 cmd = _strpath(cmd)
2360 cmd = _strpath(cmd)
2352 pipe = os.popen(cmd)
2361 pipe = os.popen(cmd)
2353 try:
2362 try:
2354 self._hgpath = _bytespath(pipe.read().strip())
2363 self._hgpath = _bytespath(pipe.read().strip())
2355 finally:
2364 finally:
2356 pipe.close()
2365 pipe.close()
2357
2366
2358 return self._hgpath
2367 return self._hgpath
2359
2368
2360 def _installchg(self):
2369 def _installchg(self):
2361 """Install chg into the test environment"""
2370 """Install chg into the test environment"""
2362 vlog('# Performing temporary installation of CHG')
2371 vlog('# Performing temporary installation of CHG')
2363 assert os.path.dirname(self._bindir) == self._installdir
2372 assert os.path.dirname(self._bindir) == self._installdir
2364 assert self._hgroot, 'must be called after _installhg()'
2373 assert self._hgroot, 'must be called after _installhg()'
2365 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2374 cmd = (b'"%(make)s" clean install PREFIX="%(prefix)s"'
2366 % {b'make': 'make', # TODO: switch by option or environment?
2375 % {b'make': 'make', # TODO: switch by option or environment?
2367 b'prefix': self._installdir})
2376 b'prefix': self._installdir})
2368 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2377 cwd = os.path.join(self._hgroot, b'contrib', b'chg')
2369 vlog("# Running", cmd)
2378 vlog("# Running", cmd)
2370 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2379 proc = subprocess.Popen(cmd, shell=True, cwd=cwd,
2371 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2380 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
2372 stderr=subprocess.STDOUT)
2381 stderr=subprocess.STDOUT)
2373 out, _err = proc.communicate()
2382 out, _err = proc.communicate()
2374 if proc.returncode != 0:
2383 if proc.returncode != 0:
2375 if PYTHON3:
2384 if PYTHON3:
2376 sys.stdout.buffer.write(out)
2385 sys.stdout.buffer.write(out)
2377 else:
2386 else:
2378 sys.stdout.write(out)
2387 sys.stdout.write(out)
2379 sys.exit(1)
2388 sys.exit(1)
2380
2389
2381 def _killchgdaemons(self):
2390 def _killchgdaemons(self):
2382 """Kill all background chg command servers spawned by tests"""
2391 """Kill all background chg command servers spawned by tests"""
2383 for f in os.listdir(self._chgsockdir):
2392 for f in os.listdir(self._chgsockdir):
2384 if not f.endswith(b'.pid'):
2393 if not f.endswith(b'.pid'):
2385 continue
2394 continue
2386 killdaemons(os.path.join(self._chgsockdir, f))
2395 killdaemons(os.path.join(self._chgsockdir, f))
2387
2396
2388 def _outputcoverage(self):
2397 def _outputcoverage(self):
2389 """Produce code coverage output."""
2398 """Produce code coverage output."""
2390 from coverage import coverage
2399 from coverage import coverage
2391
2400
2392 vlog('# Producing coverage report')
2401 vlog('# Producing coverage report')
2393 # chdir is the easiest way to get short, relative paths in the
2402 # chdir is the easiest way to get short, relative paths in the
2394 # output.
2403 # output.
2395 os.chdir(self._hgroot)
2404 os.chdir(self._hgroot)
2396 covdir = os.path.join(self._installdir, '..', 'coverage')
2405 covdir = os.path.join(self._installdir, '..', 'coverage')
2397 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2406 cov = coverage(data_file=os.path.join(covdir, 'cov'))
2398
2407
2399 # Map install directory paths back to source directory.
2408 # Map install directory paths back to source directory.
2400 cov.config.paths['srcdir'] = ['.', self._pythondir]
2409 cov.config.paths['srcdir'] = ['.', self._pythondir]
2401
2410
2402 cov.combine()
2411 cov.combine()
2403
2412
2404 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2413 omit = [os.path.join(x, '*') for x in [self._bindir, self._testdir]]
2405 cov.report(ignore_errors=True, omit=omit)
2414 cov.report(ignore_errors=True, omit=omit)
2406
2415
2407 if self.options.htmlcov:
2416 if self.options.htmlcov:
2408 htmldir = os.path.join(self._testdir, 'htmlcov')
2417 htmldir = os.path.join(self._testdir, 'htmlcov')
2409 cov.html_report(directory=htmldir, omit=omit)
2418 cov.html_report(directory=htmldir, omit=omit)
2410 if self.options.annotate:
2419 if self.options.annotate:
2411 adir = os.path.join(self._testdir, 'annotated')
2420 adir = os.path.join(self._testdir, 'annotated')
2412 if not os.path.isdir(adir):
2421 if not os.path.isdir(adir):
2413 os.mkdir(adir)
2422 os.mkdir(adir)
2414 cov.annotate(directory=adir, omit=omit)
2423 cov.annotate(directory=adir, omit=omit)
2415
2424
2416 def _findprogram(self, program):
2425 def _findprogram(self, program):
2417 """Search PATH for a executable program"""
2426 """Search PATH for a executable program"""
2418 dpb = _bytespath(os.defpath)
2427 dpb = _bytespath(os.defpath)
2419 sepb = _bytespath(os.pathsep)
2428 sepb = _bytespath(os.pathsep)
2420 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2429 for p in osenvironb.get(b'PATH', dpb).split(sepb):
2421 name = os.path.join(p, program)
2430 name = os.path.join(p, program)
2422 if os.name == 'nt' or os.access(name, os.X_OK):
2431 if os.name == 'nt' or os.access(name, os.X_OK):
2423 return name
2432 return name
2424 return None
2433 return None
2425
2434
2426 def _checktools(self):
2435 def _checktools(self):
2427 """Ensure tools required to run tests are present."""
2436 """Ensure tools required to run tests are present."""
2428 for p in self.REQUIREDTOOLS:
2437 for p in self.REQUIREDTOOLS:
2429 if os.name == 'nt' and not p.endswith('.exe'):
2438 if os.name == 'nt' and not p.endswith('.exe'):
2430 p += '.exe'
2439 p += '.exe'
2431 found = self._findprogram(p)
2440 found = self._findprogram(p)
2432 if found:
2441 if found:
2433 vlog("# Found prerequisite", p, "at", found)
2442 vlog("# Found prerequisite", p, "at", found)
2434 else:
2443 else:
2435 print("WARNING: Did not find prerequisite tool: %s " % p)
2444 print("WARNING: Did not find prerequisite tool: %s " % p)
2436
2445
2437 if __name__ == '__main__':
2446 if __name__ == '__main__':
2438 runner = TestRunner()
2447 runner = TestRunner()
2439
2448
2440 try:
2449 try:
2441 import msvcrt
2450 import msvcrt
2442 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2451 msvcrt.setmode(sys.stdin.fileno(), os.O_BINARY)
2443 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2452 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
2444 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2453 msvcrt.setmode(sys.stderr.fileno(), os.O_BINARY)
2445 except ImportError:
2454 except ImportError:
2446 pass
2455 pass
2447
2456
2448 sys.exit(runner.run(sys.argv[1:]))
2457 sys.exit(runner.run(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now