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