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