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