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