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