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