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