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