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