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