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