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