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