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