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