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