##// END OF EJS Templates
run-tests: don't show 'i' for tests that don't match a keyword
Matt Mackall -
r22107:3b5cf39f default
parent child Browse files
Show More
@@ -1,1912 +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.RLock()
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 iolock.acquire()
1132 super(TestResult, self).addSuccess(test)
1132 super(TestResult, self).addSuccess(test)
1133 iolock.release()
1133 iolock.release()
1134 self.successes.append(test)
1134 self.successes.append(test)
1135
1135
1136 def addError(self, test, err):
1136 def addError(self, test, err):
1137 super(TestResult, self).addError(test, err)
1137 super(TestResult, self).addError(test, err)
1138 if self._options.first:
1138 if self._options.first:
1139 self.stop()
1139 self.stop()
1140
1140
1141 # Polyfill.
1141 # Polyfill.
1142 def addSkip(self, test, reason):
1142 def addSkip(self, test, reason):
1143 self.skipped.append((test, reason))
1143 self.skipped.append((test, reason))
1144 iolock.acquire()
1144 iolock.acquire()
1145 if self.showAll:
1145 if self.showAll:
1146 self.stream.writeln('skipped %s' % reason)
1146 self.stream.writeln('skipped %s' % reason)
1147 else:
1147 else:
1148 self.stream.write('s')
1148 self.stream.write('s')
1149 self.stream.flush()
1149 self.stream.flush()
1150 iolock.release()
1150 iolock.release()
1151
1151
1152 def addIgnore(self, test, reason):
1152 def addIgnore(self, test, reason):
1153 self.ignored.append((test, reason))
1153 self.ignored.append((test, reason))
1154 iolock.acquire()
1154 iolock.acquire()
1155 if self.showAll:
1155 if self.showAll:
1156 self.stream.writeln('ignored %s' % reason)
1156 self.stream.writeln('ignored %s' % reason)
1157 else:
1157 else:
1158 if reason != 'not retesting':
1158 if reason != 'not retesting' and reason != "doesn't match keyword":
1159 self.stream.write('i')
1159 self.stream.write('i')
1160 else:
1160 else:
1161 self.testsRun += 1
1161 self.testsRun += 1
1162 self.stream.flush()
1162 self.stream.flush()
1163 iolock.release()
1163 iolock.release()
1164
1164
1165 def addWarn(self, test, reason):
1165 def addWarn(self, test, reason):
1166 self.warned.append((test, reason))
1166 self.warned.append((test, reason))
1167
1167
1168 if self._options.first:
1168 if self._options.first:
1169 self.stop()
1169 self.stop()
1170
1170
1171 iolock.acquire()
1171 iolock.acquire()
1172 if self.showAll:
1172 if self.showAll:
1173 self.stream.writeln('warned %s' % reason)
1173 self.stream.writeln('warned %s' % reason)
1174 else:
1174 else:
1175 self.stream.write('~')
1175 self.stream.write('~')
1176 self.stream.flush()
1176 self.stream.flush()
1177 iolock.release()
1177 iolock.release()
1178
1178
1179 def addOutputMismatch(self, test, ret, got, expected):
1179 def addOutputMismatch(self, test, ret, got, expected):
1180 """Record a mismatch in test output for a particular test."""
1180 """Record a mismatch in test output for a particular test."""
1181
1181
1182 accepted = False
1182 accepted = False
1183 failed = False
1183 failed = False
1184 lines = []
1184 lines = []
1185
1185
1186 iolock.acquire()
1186 iolock.acquire()
1187 if self._options.nodiff:
1187 if self._options.nodiff:
1188 pass
1188 pass
1189 elif self._options.view:
1189 elif self._options.view:
1190 os.system("%s %s %s" %
1190 os.system("%s %s %s" %
1191 (self._options.view, test.refpath, test.errpath))
1191 (self._options.view, test.refpath, test.errpath))
1192 else:
1192 else:
1193 failed, lines = getdiff(expected, got,
1193 failed, lines = getdiff(expected, got,
1194 test.refpath, test.errpath)
1194 test.refpath, test.errpath)
1195 if failed:
1195 if failed:
1196 self.addFailure(test, 'diff generation failed')
1196 self.addFailure(test, 'diff generation failed')
1197 else:
1197 else:
1198 self.stream.write('\n')
1198 self.stream.write('\n')
1199 for line in lines:
1199 for line in lines:
1200 self.stream.write(line)
1200 self.stream.write(line)
1201 self.stream.flush()
1201 self.stream.flush()
1202
1202
1203 # handle interactive prompt without releasing iolock
1203 # handle interactive prompt without releasing iolock
1204 if self._options.interactive:
1204 if self._options.interactive:
1205 self.stream.write('Accept this change? [n] ')
1205 self.stream.write('Accept this change? [n] ')
1206 answer = sys.stdin.readline().strip()
1206 answer = sys.stdin.readline().strip()
1207 if answer.lower() in ('y', 'yes'):
1207 if answer.lower() in ('y', 'yes'):
1208 if test.name.endswith('.t'):
1208 if test.name.endswith('.t'):
1209 rename(test.errpath, test.path)
1209 rename(test.errpath, test.path)
1210 else:
1210 else:
1211 rename(test.errpath, '%s.out' % test.path)
1211 rename(test.errpath, '%s.out' % test.path)
1212 accepted = True
1212 accepted = True
1213 if not accepted and not failed:
1213 if not accepted and not failed:
1214 self.faildata[test.name] = ''.join(lines)
1214 self.faildata[test.name] = ''.join(lines)
1215 iolock.release()
1215 iolock.release()
1216
1216
1217 return accepted
1217 return accepted
1218
1218
1219 def startTest(self, test):
1219 def startTest(self, test):
1220 super(TestResult, self).startTest(test)
1220 super(TestResult, self).startTest(test)
1221
1221
1222 # 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
1223 # 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.
1224 # 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
1225 # and not for Windows.
1225 # and not for Windows.
1226 self._started[test.name] = os.times()
1226 self._started[test.name] = os.times()
1227
1227
1228 def stopTest(self, test, interrupted=False):
1228 def stopTest(self, test, interrupted=False):
1229 super(TestResult, self).stopTest(test)
1229 super(TestResult, self).stopTest(test)
1230
1230
1231 self._stopped[test.name] = os.times()
1231 self._stopped[test.name] = os.times()
1232
1232
1233 starttime = self._started[test.name]
1233 starttime = self._started[test.name]
1234 endtime = self._stopped[test.name]
1234 endtime = self._stopped[test.name]
1235 self.times.append((test.name, endtime[2] - starttime[2],
1235 self.times.append((test.name, endtime[2] - starttime[2],
1236 endtime[3] - starttime[3], endtime[4] - starttime[4]))
1236 endtime[3] - starttime[3], endtime[4] - starttime[4]))
1237
1237
1238 del self._started[test.name]
1238 del self._started[test.name]
1239 del self._stopped[test.name]
1239 del self._stopped[test.name]
1240
1240
1241 if interrupted:
1241 if interrupted:
1242 iolock.acquire()
1242 iolock.acquire()
1243 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1243 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1244 test.name, self.times[-1][3]))
1244 test.name, self.times[-1][3]))
1245 iolock.release()
1245 iolock.release()
1246
1246
1247 class TestSuite(unittest.TestSuite):
1247 class TestSuite(unittest.TestSuite):
1248 """Custom unitest TestSuite that knows how to execute Mercurial tests."""
1248 """Custom unitest TestSuite that knows how to execute Mercurial tests."""
1249
1249
1250 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1250 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1251 retest=False, keywords=None, loop=False,
1251 retest=False, keywords=None, loop=False,
1252 *args, **kwargs):
1252 *args, **kwargs):
1253 """Create a new instance that can run tests with a configuration.
1253 """Create a new instance that can run tests with a configuration.
1254
1254
1255 testdir specifies the directory where tests are executed from. This
1255 testdir specifies the directory where tests are executed from. This
1256 is typically the ``tests`` directory from Mercurial's source
1256 is typically the ``tests`` directory from Mercurial's source
1257 repository.
1257 repository.
1258
1258
1259 jobs specifies the number of jobs to run concurrently. Each test
1259 jobs specifies the number of jobs to run concurrently. Each test
1260 executes on its own thread. Tests actually spawn new processes, so
1260 executes on its own thread. Tests actually spawn new processes, so
1261 state mutation should not be an issue.
1261 state mutation should not be an issue.
1262
1262
1263 whitelist and blacklist denote tests that have been whitelisted and
1263 whitelist and blacklist denote tests that have been whitelisted and
1264 blacklisted, respectively. These arguments don't belong in TestSuite.
1264 blacklisted, respectively. These arguments don't belong in TestSuite.
1265 Instead, whitelist and blacklist should be handled by the thing that
1265 Instead, whitelist and blacklist should be handled by the thing that
1266 populates the TestSuite with tests. They are present to preserve
1266 populates the TestSuite with tests. They are present to preserve
1267 backwards compatible behavior which reports skipped tests as part
1267 backwards compatible behavior which reports skipped tests as part
1268 of the results.
1268 of the results.
1269
1269
1270 retest denotes whether to retest failed tests. This arguably belongs
1270 retest denotes whether to retest failed tests. This arguably belongs
1271 outside of TestSuite.
1271 outside of TestSuite.
1272
1272
1273 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
1274 to execute. This arguably belongs outside of TestSuite.
1274 to execute. This arguably belongs outside of TestSuite.
1275
1275
1276 loop denotes whether to loop over tests forever.
1276 loop denotes whether to loop over tests forever.
1277 """
1277 """
1278 super(TestSuite, self).__init__(*args, **kwargs)
1278 super(TestSuite, self).__init__(*args, **kwargs)
1279
1279
1280 self._jobs = jobs
1280 self._jobs = jobs
1281 self._whitelist = whitelist
1281 self._whitelist = whitelist
1282 self._blacklist = blacklist
1282 self._blacklist = blacklist
1283 self._retest = retest
1283 self._retest = retest
1284 self._keywords = keywords
1284 self._keywords = keywords
1285 self._loop = loop
1285 self._loop = loop
1286
1286
1287 def run(self, result):
1287 def run(self, result):
1288 # 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
1289 # 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
1290 # Test simpler.
1290 # Test simpler.
1291 tests = []
1291 tests = []
1292 for test in self._tests:
1292 for test in self._tests:
1293 if not os.path.exists(test.path):
1293 if not os.path.exists(test.path):
1294 result.addSkip(test, "Doesn't exist")
1294 result.addSkip(test, "Doesn't exist")
1295 continue
1295 continue
1296
1296
1297 if not (self._whitelist and test.name in self._whitelist):
1297 if not (self._whitelist and test.name in self._whitelist):
1298 if self._blacklist and test.name in self._blacklist:
1298 if self._blacklist and test.name in self._blacklist:
1299 result.addSkip(test, 'blacklisted')
1299 result.addSkip(test, 'blacklisted')
1300 continue
1300 continue
1301
1301
1302 if self._retest and not os.path.exists(test.errpath):
1302 if self._retest and not os.path.exists(test.errpath):
1303 result.addIgnore(test, 'not retesting')
1303 result.addIgnore(test, 'not retesting')
1304 continue
1304 continue
1305
1305
1306 if self._keywords:
1306 if self._keywords:
1307 f = open(test.path, 'rb')
1307 f = open(test.path, 'rb')
1308 t = f.read().lower() + test.name.lower()
1308 t = f.read().lower() + test.name.lower()
1309 f.close()
1309 f.close()
1310 ignored = False
1310 ignored = False
1311 for k in self._keywords.lower().split():
1311 for k in self._keywords.lower().split():
1312 if k not in t:
1312 if k not in t:
1313 result.addIgnore(test, "doesn't match keyword")
1313 result.addIgnore(test, "doesn't match keyword")
1314 ignored = True
1314 ignored = True
1315 break
1315 break
1316
1316
1317 if ignored:
1317 if ignored:
1318 continue
1318 continue
1319
1319
1320 tests.append(test)
1320 tests.append(test)
1321
1321
1322 runtests = list(tests)
1322 runtests = list(tests)
1323 done = queue.Queue()
1323 done = queue.Queue()
1324 running = 0
1324 running = 0
1325
1325
1326 def job(test, result):
1326 def job(test, result):
1327 try:
1327 try:
1328 test(result)
1328 test(result)
1329 done.put(None)
1329 done.put(None)
1330 except KeyboardInterrupt:
1330 except KeyboardInterrupt:
1331 pass
1331 pass
1332 except: # re-raises
1332 except: # re-raises
1333 done.put(('!', test, 'run-test raised an error, see traceback'))
1333 done.put(('!', test, 'run-test raised an error, see traceback'))
1334 raise
1334 raise
1335
1335
1336 try:
1336 try:
1337 while tests or running:
1337 while tests or running:
1338 if not done.empty() or running == self._jobs or not tests:
1338 if not done.empty() or running == self._jobs or not tests:
1339 try:
1339 try:
1340 done.get(True, 1)
1340 done.get(True, 1)
1341 if result and result.shouldStop:
1341 if result and result.shouldStop:
1342 break
1342 break
1343 except queue.Empty:
1343 except queue.Empty:
1344 continue
1344 continue
1345 running -= 1
1345 running -= 1
1346 if tests and not running == self._jobs:
1346 if tests and not running == self._jobs:
1347 test = tests.pop(0)
1347 test = tests.pop(0)
1348 if self._loop:
1348 if self._loop:
1349 tests.append(test)
1349 tests.append(test)
1350 t = threading.Thread(target=job, name=test.name,
1350 t = threading.Thread(target=job, name=test.name,
1351 args=(test, result))
1351 args=(test, result))
1352 t.start()
1352 t.start()
1353 running += 1
1353 running += 1
1354 except KeyboardInterrupt:
1354 except KeyboardInterrupt:
1355 for test in runtests:
1355 for test in runtests:
1356 test.abort()
1356 test.abort()
1357
1357
1358 return result
1358 return result
1359
1359
1360 class TextTestRunner(unittest.TextTestRunner):
1360 class TextTestRunner(unittest.TextTestRunner):
1361 """Custom unittest test runner that uses appropriate settings."""
1361 """Custom unittest test runner that uses appropriate settings."""
1362
1362
1363 def __init__(self, runner, *args, **kwargs):
1363 def __init__(self, runner, *args, **kwargs):
1364 super(TextTestRunner, self).__init__(*args, **kwargs)
1364 super(TextTestRunner, self).__init__(*args, **kwargs)
1365
1365
1366 self._runner = runner
1366 self._runner = runner
1367
1367
1368 def run(self, test):
1368 def run(self, test):
1369 result = TestResult(self._runner.options, self.stream,
1369 result = TestResult(self._runner.options, self.stream,
1370 self.descriptions, self.verbosity)
1370 self.descriptions, self.verbosity)
1371
1371
1372 test(result)
1372 test(result)
1373
1373
1374 failed = len(result.failures)
1374 failed = len(result.failures)
1375 warned = len(result.warned)
1375 warned = len(result.warned)
1376 skipped = len(result.skipped)
1376 skipped = len(result.skipped)
1377 ignored = len(result.ignored)
1377 ignored = len(result.ignored)
1378
1378
1379 iolock.acquire()
1379 iolock.acquire()
1380 self.stream.writeln('')
1380 self.stream.writeln('')
1381
1381
1382 if not self._runner.options.noskips:
1382 if not self._runner.options.noskips:
1383 for test, msg in result.skipped:
1383 for test, msg in result.skipped:
1384 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1384 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1385 for test, msg in result.warned:
1385 for test, msg in result.warned:
1386 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1386 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1387 for test, msg in result.failures:
1387 for test, msg in result.failures:
1388 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1388 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1389 for test, msg in result.errors:
1389 for test, msg in result.errors:
1390 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1390 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1391
1391
1392 if self._runner.options.xunit:
1392 if self._runner.options.xunit:
1393 xuf = open(self._runner.options.xunit, 'wb')
1393 xuf = open(self._runner.options.xunit, 'wb')
1394 try:
1394 try:
1395 timesd = dict(
1395 timesd = dict(
1396 (test, real) for test, cuser, csys, real in result.times)
1396 (test, real) for test, cuser, csys, real in result.times)
1397 doc = minidom.Document()
1397 doc = minidom.Document()
1398 s = doc.createElement('testsuite')
1398 s = doc.createElement('testsuite')
1399 s.setAttribute('name', 'run-tests')
1399 s.setAttribute('name', 'run-tests')
1400 s.setAttribute('tests', str(result.testsRun))
1400 s.setAttribute('tests', str(result.testsRun))
1401 s.setAttribute('errors', "0") # TODO
1401 s.setAttribute('errors', "0") # TODO
1402 s.setAttribute('failures', str(failed))
1402 s.setAttribute('failures', str(failed))
1403 s.setAttribute('skipped', str(skipped + ignored))
1403 s.setAttribute('skipped', str(skipped + ignored))
1404 doc.appendChild(s)
1404 doc.appendChild(s)
1405 for tc in result.successes:
1405 for tc in result.successes:
1406 t = doc.createElement('testcase')
1406 t = doc.createElement('testcase')
1407 t.setAttribute('name', tc.name)
1407 t.setAttribute('name', tc.name)
1408 t.setAttribute('time', '%.3f' % timesd[tc.name])
1408 t.setAttribute('time', '%.3f' % timesd[tc.name])
1409 s.appendChild(t)
1409 s.appendChild(t)
1410 for tc, err in sorted(result.faildata.iteritems()):
1410 for tc, err in sorted(result.faildata.iteritems()):
1411 t = doc.createElement('testcase')
1411 t = doc.createElement('testcase')
1412 t.setAttribute('name', tc)
1412 t.setAttribute('name', tc)
1413 t.setAttribute('time', '%.3f' % timesd[tc])
1413 t.setAttribute('time', '%.3f' % timesd[tc])
1414 cd = doc.createCDATASection(cdatasafe(err))
1414 cd = doc.createCDATASection(cdatasafe(err))
1415 t.appendChild(cd)
1415 t.appendChild(cd)
1416 s.appendChild(t)
1416 s.appendChild(t)
1417 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1417 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1418 finally:
1418 finally:
1419 xuf.close()
1419 xuf.close()
1420
1420
1421 self._runner._checkhglib('Tested')
1421 self._runner._checkhglib('Tested')
1422
1422
1423 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.'
1424 % (result.testsRun,
1424 % (result.testsRun,
1425 skipped + ignored, warned, failed))
1425 skipped + ignored, warned, failed))
1426 if failed:
1426 if failed:
1427 self.stream.writeln('python hash seed: %s' %
1427 self.stream.writeln('python hash seed: %s' %
1428 os.environ['PYTHONHASHSEED'])
1428 os.environ['PYTHONHASHSEED'])
1429 if self._runner.options.time:
1429 if self._runner.options.time:
1430 self.printtimes(result.times)
1430 self.printtimes(result.times)
1431
1431
1432 iolock.release()
1432 iolock.release()
1433
1433
1434 return result
1434 return result
1435
1435
1436 def printtimes(self, times):
1436 def printtimes(self, times):
1437 # iolock held by run
1437 # iolock held by run
1438 self.stream.writeln('# Producing time report')
1438 self.stream.writeln('# Producing time report')
1439 times.sort(key=lambda t: (t[3]))
1439 times.sort(key=lambda t: (t[3]))
1440 cols = '%7.3f %7.3f %7.3f %s'
1440 cols = '%7.3f %7.3f %7.3f %s'
1441 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1441 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1442 'Test'))
1442 'Test'))
1443 for test, cuser, csys, real in times:
1443 for test, cuser, csys, real in times:
1444 self.stream.writeln(cols % (cuser, csys, real, test))
1444 self.stream.writeln(cols % (cuser, csys, real, test))
1445
1445
1446 class TestRunner(object):
1446 class TestRunner(object):
1447 """Holds context for executing tests.
1447 """Holds context for executing tests.
1448
1448
1449 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.
1450 """
1450 """
1451
1451
1452 # Programs required to run tests.
1452 # Programs required to run tests.
1453 REQUIREDTOOLS = [
1453 REQUIREDTOOLS = [
1454 os.path.basename(sys.executable),
1454 os.path.basename(sys.executable),
1455 'diff',
1455 'diff',
1456 'grep',
1456 'grep',
1457 'unzip',
1457 'unzip',
1458 'gunzip',
1458 'gunzip',
1459 'bunzip2',
1459 'bunzip2',
1460 'sed',
1460 'sed',
1461 ]
1461 ]
1462
1462
1463 # Maps file extensions to test class.
1463 # Maps file extensions to test class.
1464 TESTTYPES = [
1464 TESTTYPES = [
1465 ('.py', PythonTest),
1465 ('.py', PythonTest),
1466 ('.t', TTest),
1466 ('.t', TTest),
1467 ]
1467 ]
1468
1468
1469 def __init__(self):
1469 def __init__(self):
1470 self.options = None
1470 self.options = None
1471 self._testdir = None
1471 self._testdir = None
1472 self._hgtmp = None
1472 self._hgtmp = None
1473 self._installdir = None
1473 self._installdir = None
1474 self._bindir = None
1474 self._bindir = None
1475 self._tmpbinddir = None
1475 self._tmpbinddir = None
1476 self._pythondir = None
1476 self._pythondir = None
1477 self._coveragefile = None
1477 self._coveragefile = None
1478 self._createdfiles = []
1478 self._createdfiles = []
1479 self._hgpath = None
1479 self._hgpath = None
1480
1480
1481 def run(self, args, parser=None):
1481 def run(self, args, parser=None):
1482 """Run the test suite."""
1482 """Run the test suite."""
1483 oldmask = os.umask(022)
1483 oldmask = os.umask(022)
1484 try:
1484 try:
1485 parser = parser or getparser()
1485 parser = parser or getparser()
1486 options, args = parseargs(args, parser)
1486 options, args = parseargs(args, parser)
1487 self.options = options
1487 self.options = options
1488
1488
1489 self._checktools()
1489 self._checktools()
1490 tests = self.findtests(args)
1490 tests = self.findtests(args)
1491 return self._run(tests)
1491 return self._run(tests)
1492 finally:
1492 finally:
1493 os.umask(oldmask)
1493 os.umask(oldmask)
1494
1494
1495 def _run(self, tests):
1495 def _run(self, tests):
1496 if self.options.random:
1496 if self.options.random:
1497 random.shuffle(tests)
1497 random.shuffle(tests)
1498 else:
1498 else:
1499 # keywords for slow tests
1499 # keywords for slow tests
1500 slow = 'svn gendoc check-code-hg'.split()
1500 slow = 'svn gendoc check-code-hg'.split()
1501 def sortkey(f):
1501 def sortkey(f):
1502 # run largest tests first, as they tend to take the longest
1502 # run largest tests first, as they tend to take the longest
1503 try:
1503 try:
1504 val = -os.stat(f).st_size
1504 val = -os.stat(f).st_size
1505 except OSError, e:
1505 except OSError, e:
1506 if e.errno != errno.ENOENT:
1506 if e.errno != errno.ENOENT:
1507 raise
1507 raise
1508 return -1e9 # file does not exist, tell early
1508 return -1e9 # file does not exist, tell early
1509 for kw in slow:
1509 for kw in slow:
1510 if kw in f:
1510 if kw in f:
1511 val *= 10
1511 val *= 10
1512 return val
1512 return val
1513 tests.sort(key=sortkey)
1513 tests.sort(key=sortkey)
1514
1514
1515 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1515 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1516
1516
1517 if 'PYTHONHASHSEED' not in os.environ:
1517 if 'PYTHONHASHSEED' not in os.environ:
1518 # use a random python hash seed all the time
1518 # use a random python hash seed all the time
1519 # we do the randomness ourself to know what seed is used
1519 # we do the randomness ourself to know what seed is used
1520 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1520 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1521
1521
1522 if self.options.tmpdir:
1522 if self.options.tmpdir:
1523 self.options.keep_tmpdir = True
1523 self.options.keep_tmpdir = True
1524 tmpdir = self.options.tmpdir
1524 tmpdir = self.options.tmpdir
1525 if os.path.exists(tmpdir):
1525 if os.path.exists(tmpdir):
1526 # 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
1527 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1527 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1528 # tmpdir already exists.
1528 # tmpdir already exists.
1529 print "error: temp dir %r already exists" % tmpdir
1529 print "error: temp dir %r already exists" % tmpdir
1530 return 1
1530 return 1
1531
1531
1532 # Automatically removing tmpdir sounds convenient, but could
1532 # Automatically removing tmpdir sounds convenient, but could
1533 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1533 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1534 # or "--tmpdir=$HOME".
1534 # or "--tmpdir=$HOME".
1535 #vlog("# Removing temp dir", tmpdir)
1535 #vlog("# Removing temp dir", tmpdir)
1536 #shutil.rmtree(tmpdir)
1536 #shutil.rmtree(tmpdir)
1537 os.makedirs(tmpdir)
1537 os.makedirs(tmpdir)
1538 else:
1538 else:
1539 d = None
1539 d = None
1540 if os.name == 'nt':
1540 if os.name == 'nt':
1541 # without this, we get the default temp dir location, but
1541 # without this, we get the default temp dir location, but
1542 # in all lowercase, which causes troubles with paths (issue3490)
1542 # in all lowercase, which causes troubles with paths (issue3490)
1543 d = os.getenv('TMP')
1543 d = os.getenv('TMP')
1544 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1544 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1545 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1545 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1546
1546
1547 if self.options.with_hg:
1547 if self.options.with_hg:
1548 self._installdir = None
1548 self._installdir = None
1549 self._bindir = os.path.dirname(os.path.realpath(
1549 self._bindir = os.path.dirname(os.path.realpath(
1550 self.options.with_hg))
1550 self.options.with_hg))
1551 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1551 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1552 os.makedirs(self._tmpbindir)
1552 os.makedirs(self._tmpbindir)
1553
1553
1554 # This looks redundant with how Python initializes sys.path from
1554 # This looks redundant with how Python initializes sys.path from
1555 # the location of the script being executed. Needed because the
1555 # the location of the script being executed. Needed because the
1556 # "hg" specified by --with-hg is not the only Python script
1556 # "hg" specified by --with-hg is not the only Python script
1557 # executed in the test suite that needs to import 'mercurial'
1557 # executed in the test suite that needs to import 'mercurial'
1558 # ... which means it's not really redundant at all.
1558 # ... which means it's not really redundant at all.
1559 self._pythondir = self._bindir
1559 self._pythondir = self._bindir
1560 else:
1560 else:
1561 self._installdir = os.path.join(self._hgtmp, "install")
1561 self._installdir = os.path.join(self._hgtmp, "install")
1562 self._bindir = os.environ["BINDIR"] = \
1562 self._bindir = os.environ["BINDIR"] = \
1563 os.path.join(self._installdir, "bin")
1563 os.path.join(self._installdir, "bin")
1564 self._tmpbindir = self._bindir
1564 self._tmpbindir = self._bindir
1565 self._pythondir = os.path.join(self._installdir, "lib", "python")
1565 self._pythondir = os.path.join(self._installdir, "lib", "python")
1566
1566
1567 os.environ["BINDIR"] = self._bindir
1567 os.environ["BINDIR"] = self._bindir
1568 os.environ["PYTHON"] = PYTHON
1568 os.environ["PYTHON"] = PYTHON
1569
1569
1570 path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
1570 path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
1571 if self._tmpbindir != self._bindir:
1571 if self._tmpbindir != self._bindir:
1572 path = [self._tmpbindir] + path
1572 path = [self._tmpbindir] + path
1573 os.environ["PATH"] = os.pathsep.join(path)
1573 os.environ["PATH"] = os.pathsep.join(path)
1574
1574
1575 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1575 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1576 # can run .../tests/run-tests.py test-foo where test-foo
1576 # can run .../tests/run-tests.py test-foo where test-foo
1577 # 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
1578 # import modules like heredoctest.
1578 # import modules like heredoctest.
1579 pypath = [self._pythondir, self._testdir,
1579 pypath = [self._pythondir, self._testdir,
1580 os.path.abspath(os.path.dirname(__file__))]
1580 os.path.abspath(os.path.dirname(__file__))]
1581 # We have to augment PYTHONPATH, rather than simply replacing
1581 # We have to augment PYTHONPATH, rather than simply replacing
1582 # it, in case external libraries are only available via current
1582 # it, in case external libraries are only available via current
1583 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1583 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1584 # are in /opt/subversion.)
1584 # are in /opt/subversion.)
1585 oldpypath = os.environ.get(IMPL_PATH)
1585 oldpypath = os.environ.get(IMPL_PATH)
1586 if oldpypath:
1586 if oldpypath:
1587 pypath.append(oldpypath)
1587 pypath.append(oldpypath)
1588 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1588 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1589
1589
1590 self._coveragefile = os.path.join(self._testdir, '.coverage')
1590 self._coveragefile = os.path.join(self._testdir, '.coverage')
1591
1591
1592 vlog("# Using TESTDIR", self._testdir)
1592 vlog("# Using TESTDIR", self._testdir)
1593 vlog("# Using HGTMP", self._hgtmp)
1593 vlog("# Using HGTMP", self._hgtmp)
1594 vlog("# Using PATH", os.environ["PATH"])
1594 vlog("# Using PATH", os.environ["PATH"])
1595 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1595 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1596
1596
1597 try:
1597 try:
1598 return self._runtests(tests) or 0
1598 return self._runtests(tests) or 0
1599 finally:
1599 finally:
1600 time.sleep(.1)
1600 time.sleep(.1)
1601 self._cleanup()
1601 self._cleanup()
1602
1602
1603 def findtests(self, args):
1603 def findtests(self, args):
1604 """Finds possible test files from arguments.
1604 """Finds possible test files from arguments.
1605
1605
1606 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
1607 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.
1608 """
1608 """
1609 if not args:
1609 if not args:
1610 if self.options.changed:
1610 if self.options.changed:
1611 proc = Popen4('hg st --rev "%s" -man0 .' %
1611 proc = Popen4('hg st --rev "%s" -man0 .' %
1612 self.options.changed, None, 0)
1612 self.options.changed, None, 0)
1613 stdout, stderr = proc.communicate()
1613 stdout, stderr = proc.communicate()
1614 args = stdout.strip('\0').split('\0')
1614 args = stdout.strip('\0').split('\0')
1615 else:
1615 else:
1616 args = os.listdir('.')
1616 args = os.listdir('.')
1617
1617
1618 return [t for t in args
1618 return [t for t in args
1619 if os.path.basename(t).startswith('test-')
1619 if os.path.basename(t).startswith('test-')
1620 and (t.endswith('.py') or t.endswith('.t'))]
1620 and (t.endswith('.py') or t.endswith('.t'))]
1621
1621
1622 def _runtests(self, tests):
1622 def _runtests(self, tests):
1623 try:
1623 try:
1624 if self._installdir:
1624 if self._installdir:
1625 self._installhg()
1625 self._installhg()
1626 self._checkhglib("Testing")
1626 self._checkhglib("Testing")
1627 else:
1627 else:
1628 self._usecorrectpython()
1628 self._usecorrectpython()
1629
1629
1630 if self.options.restart:
1630 if self.options.restart:
1631 orig = list(tests)
1631 orig = list(tests)
1632 while tests:
1632 while tests:
1633 if os.path.exists(tests[0] + ".err"):
1633 if os.path.exists(tests[0] + ".err"):
1634 break
1634 break
1635 tests.pop(0)
1635 tests.pop(0)
1636 if not tests:
1636 if not tests:
1637 print "running all tests"
1637 print "running all tests"
1638 tests = orig
1638 tests = orig
1639
1639
1640 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1640 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1641
1641
1642 failed = False
1642 failed = False
1643 warned = False
1643 warned = False
1644
1644
1645 suite = TestSuite(self._testdir,
1645 suite = TestSuite(self._testdir,
1646 jobs=self.options.jobs,
1646 jobs=self.options.jobs,
1647 whitelist=self.options.whitelisted,
1647 whitelist=self.options.whitelisted,
1648 blacklist=self.options.blacklist,
1648 blacklist=self.options.blacklist,
1649 retest=self.options.retest,
1649 retest=self.options.retest,
1650 keywords=self.options.keywords,
1650 keywords=self.options.keywords,
1651 loop=self.options.loop,
1651 loop=self.options.loop,
1652 tests=tests)
1652 tests=tests)
1653 verbosity = 1
1653 verbosity = 1
1654 if self.options.verbose:
1654 if self.options.verbose:
1655 verbosity = 2
1655 verbosity = 2
1656 runner = TextTestRunner(self, verbosity=verbosity)
1656 runner = TextTestRunner(self, verbosity=verbosity)
1657 result = runner.run(suite)
1657 result = runner.run(suite)
1658
1658
1659 if result.failures:
1659 if result.failures:
1660 failed = True
1660 failed = True
1661 if result.warned:
1661 if result.warned:
1662 warned = True
1662 warned = True
1663
1663
1664 if self.options.anycoverage:
1664 if self.options.anycoverage:
1665 self._outputcoverage()
1665 self._outputcoverage()
1666 except KeyboardInterrupt:
1666 except KeyboardInterrupt:
1667 failed = True
1667 failed = True
1668 print "\ninterrupted!"
1668 print "\ninterrupted!"
1669
1669
1670 if failed:
1670 if failed:
1671 return 1
1671 return 1
1672 if warned:
1672 if warned:
1673 return 80
1673 return 80
1674
1674
1675 def _gettest(self, test, count):
1675 def _gettest(self, test, count):
1676 """Obtain a Test by looking at its filename.
1676 """Obtain a Test by looking at its filename.
1677
1677
1678 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
1679 map to a known type.
1679 map to a known type.
1680 """
1680 """
1681 lctest = test.lower()
1681 lctest = test.lower()
1682 testcls = Test
1682 testcls = Test
1683
1683
1684 for ext, cls in self.TESTTYPES:
1684 for ext, cls in self.TESTTYPES:
1685 if lctest.endswith(ext):
1685 if lctest.endswith(ext):
1686 testcls = cls
1686 testcls = cls
1687 break
1687 break
1688
1688
1689 refpath = os.path.join(self._testdir, test)
1689 refpath = os.path.join(self._testdir, test)
1690 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1690 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1691
1691
1692 return testcls(refpath, tmpdir,
1692 return testcls(refpath, tmpdir,
1693 keeptmpdir=self.options.keep_tmpdir,
1693 keeptmpdir=self.options.keep_tmpdir,
1694 debug=self.options.debug,
1694 debug=self.options.debug,
1695 timeout=self.options.timeout,
1695 timeout=self.options.timeout,
1696 startport=self.options.port + count * 3,
1696 startport=self.options.port + count * 3,
1697 extraconfigopts=self.options.extra_config_opt,
1697 extraconfigopts=self.options.extra_config_opt,
1698 py3kwarnings=self.options.py3k_warnings,
1698 py3kwarnings=self.options.py3k_warnings,
1699 shell=self.options.shell)
1699 shell=self.options.shell)
1700
1700
1701 def _cleanup(self):
1701 def _cleanup(self):
1702 """Clean up state from this test invocation."""
1702 """Clean up state from this test invocation."""
1703
1703
1704 if self.options.keep_tmpdir:
1704 if self.options.keep_tmpdir:
1705 return
1705 return
1706
1706
1707 vlog("# Cleaning up HGTMP", self._hgtmp)
1707 vlog("# Cleaning up HGTMP", self._hgtmp)
1708 shutil.rmtree(self._hgtmp, True)
1708 shutil.rmtree(self._hgtmp, True)
1709 for f in self._createdfiles:
1709 for f in self._createdfiles:
1710 try:
1710 try:
1711 os.remove(f)
1711 os.remove(f)
1712 except OSError:
1712 except OSError:
1713 pass
1713 pass
1714
1714
1715 def _usecorrectpython(self):
1715 def _usecorrectpython(self):
1716 """Configure the environment to use the appropriate Python in tests."""
1716 """Configure the environment to use the appropriate Python in tests."""
1717 # 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.
1718 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1718 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1719 if getattr(os, 'symlink', None):
1719 if getattr(os, 'symlink', None):
1720 vlog("# Making python executable in test path a symlink to '%s'" %
1720 vlog("# Making python executable in test path a symlink to '%s'" %
1721 sys.executable)
1721 sys.executable)
1722 mypython = os.path.join(self._tmpbindir, pyexename)
1722 mypython = os.path.join(self._tmpbindir, pyexename)
1723 try:
1723 try:
1724 if os.readlink(mypython) == sys.executable:
1724 if os.readlink(mypython) == sys.executable:
1725 return
1725 return
1726 os.unlink(mypython)
1726 os.unlink(mypython)
1727 except OSError, err:
1727 except OSError, err:
1728 if err.errno != errno.ENOENT:
1728 if err.errno != errno.ENOENT:
1729 raise
1729 raise
1730 if self._findprogram(pyexename) != sys.executable:
1730 if self._findprogram(pyexename) != sys.executable:
1731 try:
1731 try:
1732 os.symlink(sys.executable, mypython)
1732 os.symlink(sys.executable, mypython)
1733 self._createdfiles.append(mypython)
1733 self._createdfiles.append(mypython)
1734 except OSError, err:
1734 except OSError, err:
1735 # child processes may race, which is harmless
1735 # child processes may race, which is harmless
1736 if err.errno != errno.EEXIST:
1736 if err.errno != errno.EEXIST:
1737 raise
1737 raise
1738 else:
1738 else:
1739 exedir, exename = os.path.split(sys.executable)
1739 exedir, exename = os.path.split(sys.executable)
1740 vlog("# Modifying search path to find %s as %s in '%s'" %
1740 vlog("# Modifying search path to find %s as %s in '%s'" %
1741 (exename, pyexename, exedir))
1741 (exename, pyexename, exedir))
1742 path = os.environ['PATH'].split(os.pathsep)
1742 path = os.environ['PATH'].split(os.pathsep)
1743 while exedir in path:
1743 while exedir in path:
1744 path.remove(exedir)
1744 path.remove(exedir)
1745 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1745 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1746 if not self._findprogram(pyexename):
1746 if not self._findprogram(pyexename):
1747 print "WARNING: Cannot find %s in search path" % pyexename
1747 print "WARNING: Cannot find %s in search path" % pyexename
1748
1748
1749 def _installhg(self):
1749 def _installhg(self):
1750 """Install hg into the test environment.
1750 """Install hg into the test environment.
1751
1751
1752 This will also configure hg with the appropriate testing settings.
1752 This will also configure hg with the appropriate testing settings.
1753 """
1753 """
1754 vlog("# Performing temporary installation of HG")
1754 vlog("# Performing temporary installation of HG")
1755 installerrs = os.path.join("tests", "install.err")
1755 installerrs = os.path.join("tests", "install.err")
1756 compiler = ''
1756 compiler = ''
1757 if self.options.compiler:
1757 if self.options.compiler:
1758 compiler = '--compiler ' + self.options.compiler
1758 compiler = '--compiler ' + self.options.compiler
1759 pure = self.options.pure and "--pure" or ""
1759 pure = self.options.pure and "--pure" or ""
1760 py3 = ''
1760 py3 = ''
1761 if sys.version_info[0] == 3:
1761 if sys.version_info[0] == 3:
1762 py3 = '--c2to3'
1762 py3 = '--c2to3'
1763
1763
1764 # Run installer in hg root
1764 # Run installer in hg root
1765 script = os.path.realpath(sys.argv[0])
1765 script = os.path.realpath(sys.argv[0])
1766 hgroot = os.path.dirname(os.path.dirname(script))
1766 hgroot = os.path.dirname(os.path.dirname(script))
1767 os.chdir(hgroot)
1767 os.chdir(hgroot)
1768 nohome = '--home=""'
1768 nohome = '--home=""'
1769 if os.name == 'nt':
1769 if os.name == 'nt':
1770 # The --home="" trick works only on OS where os.sep == '/'
1770 # The --home="" trick works only on OS where os.sep == '/'
1771 # because of a distutils convert_path() fast-path. Avoid it at
1771 # because of a distutils convert_path() fast-path. Avoid it at
1772 # least on Windows for now, deal with .pydistutils.cfg bugs
1772 # least on Windows for now, deal with .pydistutils.cfg bugs
1773 # when they happen.
1773 # when they happen.
1774 nohome = ''
1774 nohome = ''
1775 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1775 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1776 ' build %(compiler)s --build-base="%(base)s"'
1776 ' build %(compiler)s --build-base="%(base)s"'
1777 ' install --force --prefix="%(prefix)s"'
1777 ' install --force --prefix="%(prefix)s"'
1778 ' --install-lib="%(libdir)s"'
1778 ' --install-lib="%(libdir)s"'
1779 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1779 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1780 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1780 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1781 'compiler': compiler,
1781 'compiler': compiler,
1782 'base': os.path.join(self._hgtmp, "build"),
1782 'base': os.path.join(self._hgtmp, "build"),
1783 'prefix': self._installdir, 'libdir': self._pythondir,
1783 'prefix': self._installdir, 'libdir': self._pythondir,
1784 'bindir': self._bindir,
1784 'bindir': self._bindir,
1785 'nohome': nohome, 'logfile': installerrs})
1785 'nohome': nohome, 'logfile': installerrs})
1786 vlog("# Running", cmd)
1786 vlog("# Running", cmd)
1787 if os.system(cmd) == 0:
1787 if os.system(cmd) == 0:
1788 if not self.options.verbose:
1788 if not self.options.verbose:
1789 os.remove(installerrs)
1789 os.remove(installerrs)
1790 else:
1790 else:
1791 f = open(installerrs, 'rb')
1791 f = open(installerrs, 'rb')
1792 for line in f:
1792 for line in f:
1793 print line,
1793 print line,
1794 f.close()
1794 f.close()
1795 sys.exit(1)
1795 sys.exit(1)
1796 os.chdir(self._testdir)
1796 os.chdir(self._testdir)
1797
1797
1798 self._usecorrectpython()
1798 self._usecorrectpython()
1799
1799
1800 if self.options.py3k_warnings and not self.options.anycoverage:
1800 if self.options.py3k_warnings and not self.options.anycoverage:
1801 vlog("# Updating hg command to enable Py3k Warnings switch")
1801 vlog("# Updating hg command to enable Py3k Warnings switch")
1802 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1802 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1803 lines = [line.rstrip() for line in f]
1803 lines = [line.rstrip() for line in f]
1804 lines[0] += ' -3'
1804 lines[0] += ' -3'
1805 f.close()
1805 f.close()
1806 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1806 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1807 for line in lines:
1807 for line in lines:
1808 f.write(line + '\n')
1808 f.write(line + '\n')
1809 f.close()
1809 f.close()
1810
1810
1811 hgbat = os.path.join(self._bindir, 'hg.bat')
1811 hgbat = os.path.join(self._bindir, 'hg.bat')
1812 if os.path.isfile(hgbat):
1812 if os.path.isfile(hgbat):
1813 # 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
1814 # installation layout put it in bin/ directly. Fix it
1814 # installation layout put it in bin/ directly. Fix it
1815 f = open(hgbat, 'rb')
1815 f = open(hgbat, 'rb')
1816 data = f.read()
1816 data = f.read()
1817 f.close()
1817 f.close()
1818 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1818 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1819 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1819 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1820 '"%~dp0python" "%~dp0hg" %*')
1820 '"%~dp0python" "%~dp0hg" %*')
1821 f = open(hgbat, 'wb')
1821 f = open(hgbat, 'wb')
1822 f.write(data)
1822 f.write(data)
1823 f.close()
1823 f.close()
1824 else:
1824 else:
1825 print 'WARNING: cannot fix hg.bat reference to python.exe'
1825 print 'WARNING: cannot fix hg.bat reference to python.exe'
1826
1826
1827 if self.options.anycoverage:
1827 if self.options.anycoverage:
1828 custom = os.path.join(self._testdir, 'sitecustomize.py')
1828 custom = os.path.join(self._testdir, 'sitecustomize.py')
1829 target = os.path.join(self._pythondir, 'sitecustomize.py')
1829 target = os.path.join(self._pythondir, 'sitecustomize.py')
1830 vlog('# Installing coverage trigger to %s' % target)
1830 vlog('# Installing coverage trigger to %s' % target)
1831 shutil.copyfile(custom, target)
1831 shutil.copyfile(custom, target)
1832 rc = os.path.join(self._testdir, '.coveragerc')
1832 rc = os.path.join(self._testdir, '.coveragerc')
1833 vlog('# Installing coverage rc to %s' % rc)
1833 vlog('# Installing coverage rc to %s' % rc)
1834 os.environ['COVERAGE_PROCESS_START'] = rc
1834 os.environ['COVERAGE_PROCESS_START'] = rc
1835 fn = os.path.join(self._installdir, '..', '.coverage')
1835 fn = os.path.join(self._installdir, '..', '.coverage')
1836 os.environ['COVERAGE_FILE'] = fn
1836 os.environ['COVERAGE_FILE'] = fn
1837
1837
1838 def _checkhglib(self, verb):
1838 def _checkhglib(self, verb):
1839 """Ensure that the 'mercurial' package imported by python is
1839 """Ensure that the 'mercurial' package imported by python is
1840 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."""
1841 if ((self._bindir == self._pythondir) and
1841 if ((self._bindir == self._pythondir) and
1842 (self._bindir != self._tmpbindir)):
1842 (self._bindir != self._tmpbindir)):
1843 # The pythondir has been infered from --with-hg flag.
1843 # The pythondir has been infered from --with-hg flag.
1844 # We cannot expect anything sensible here
1844 # We cannot expect anything sensible here
1845 return
1845 return
1846 expecthg = os.path.join(self._pythondir, 'mercurial')
1846 expecthg = os.path.join(self._pythondir, 'mercurial')
1847 actualhg = self._gethgpath()
1847 actualhg = self._gethgpath()
1848 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1848 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1849 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1849 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1850 ' (expected %s)\n'
1850 ' (expected %s)\n'
1851 % (verb, actualhg, expecthg))
1851 % (verb, actualhg, expecthg))
1852 def _gethgpath(self):
1852 def _gethgpath(self):
1853 """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
1854 the current Python interpreter."""
1854 the current Python interpreter."""
1855 if self._hgpath is not None:
1855 if self._hgpath is not None:
1856 return self._hgpath
1856 return self._hgpath
1857
1857
1858 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1858 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1859 pipe = os.popen(cmd % PYTHON)
1859 pipe = os.popen(cmd % PYTHON)
1860 try:
1860 try:
1861 self._hgpath = pipe.read().strip()
1861 self._hgpath = pipe.read().strip()
1862 finally:
1862 finally:
1863 pipe.close()
1863 pipe.close()
1864
1864
1865 return self._hgpath
1865 return self._hgpath
1866
1866
1867 def _outputcoverage(self):
1867 def _outputcoverage(self):
1868 """Produce code coverage output."""
1868 """Produce code coverage output."""
1869 vlog('# Producing coverage report')
1869 vlog('# Producing coverage report')
1870 os.chdir(self._pythondir)
1870 os.chdir(self._pythondir)
1871
1871
1872 def covrun(*args):
1872 def covrun(*args):
1873 cmd = 'coverage %s' % ' '.join(args)
1873 cmd = 'coverage %s' % ' '.join(args)
1874 vlog('# Running: %s' % cmd)
1874 vlog('# Running: %s' % cmd)
1875 os.system(cmd)
1875 os.system(cmd)
1876
1876
1877 covrun('-c')
1877 covrun('-c')
1878 omit = ','.join(os.path.join(x, '*') for x in
1878 omit = ','.join(os.path.join(x, '*') for x in
1879 [self._bindir, self._testdir])
1879 [self._bindir, self._testdir])
1880 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1880 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1881 if self.options.htmlcov:
1881 if self.options.htmlcov:
1882 htmldir = os.path.join(self._testdir, 'htmlcov')
1882 htmldir = os.path.join(self._testdir, 'htmlcov')
1883 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1883 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1884 '"--omit=%s"' % omit)
1884 '"--omit=%s"' % omit)
1885 if self.options.annotate:
1885 if self.options.annotate:
1886 adir = os.path.join(self._testdir, 'annotated')
1886 adir = os.path.join(self._testdir, 'annotated')
1887 if not os.path.isdir(adir):
1887 if not os.path.isdir(adir):
1888 os.mkdir(adir)
1888 os.mkdir(adir)
1889 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1889 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1890
1890
1891 def _findprogram(self, program):
1891 def _findprogram(self, program):
1892 """Search PATH for a executable program"""
1892 """Search PATH for a executable program"""
1893 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):
1894 name = os.path.join(p, program)
1894 name = os.path.join(p, program)
1895 if os.name == 'nt' or os.access(name, os.X_OK):
1895 if os.name == 'nt' or os.access(name, os.X_OK):
1896 return name
1896 return name
1897 return None
1897 return None
1898
1898
1899 def _checktools(self):
1899 def _checktools(self):
1900 """Ensure tools required to run tests are present."""
1900 """Ensure tools required to run tests are present."""
1901 for p in self.REQUIREDTOOLS:
1901 for p in self.REQUIREDTOOLS:
1902 if os.name == 'nt' and not p.endswith('.exe'):
1902 if os.name == 'nt' and not p.endswith('.exe'):
1903 p += '.exe'
1903 p += '.exe'
1904 found = self._findprogram(p)
1904 found = self._findprogram(p)
1905 if found:
1905 if found:
1906 vlog("# Found prerequisite", p, "at", found)
1906 vlog("# Found prerequisite", p, "at", found)
1907 else:
1907 else:
1908 print "WARNING: Did not find prerequisite tool: %s " % p
1908 print "WARNING: Did not find prerequisite tool: %s " % p
1909
1909
1910 if __name__ == '__main__':
1910 if __name__ == '__main__':
1911 runner = TestRunner()
1911 runner = TestRunner()
1912 sys.exit(runner.run(sys.argv[1:]))
1912 sys.exit(runner.run(sys.argv[1:]))
@@ -1,347 +1,347 b''
1 This file tests the behavior of run-tests.py itself.
1 This file tests the behavior of run-tests.py itself.
2
2
3 Smoke test
3 Smoke test
4 ============
4 ============
5
5
6 $ $TESTDIR/run-tests.py
6 $ $TESTDIR/run-tests.py
7
7
8 # Ran 0 tests, 0 skipped, 0 warned, 0 failed.
8 # Ran 0 tests, 0 skipped, 0 warned, 0 failed.
9
9
10 a succesful test
10 a succesful test
11 =======================
11 =======================
12
12
13 $ cat > test-success.t << EOF
13 $ cat > test-success.t << EOF
14 > $ echo babar
14 > $ echo babar
15 > babar
15 > babar
16 > $ echo xyzzy
16 > $ echo xyzzy
17 > xyzzy
17 > xyzzy
18 > EOF
18 > EOF
19
19
20 $ $TESTDIR/run-tests.py --with-hg=`which hg`
20 $ $TESTDIR/run-tests.py --with-hg=`which hg`
21 .
21 .
22 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
22 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
23
23
24 failing test
24 failing test
25 ==================
25 ==================
26
26
27 $ cat > test-failure.t << EOF
27 $ cat > test-failure.t << EOF
28 > $ echo babar
28 > $ echo babar
29 > rataxes
29 > rataxes
30 > This is a noop statement so that
30 > This is a noop statement so that
31 > this test is still more bytes than success.
31 > this test is still more bytes than success.
32 > EOF
32 > EOF
33
33
34 $ $TESTDIR/run-tests.py --with-hg=`which hg`
34 $ $TESTDIR/run-tests.py --with-hg=`which hg`
35
35
36 --- $TESTTMP/test-failure.t
36 --- $TESTTMP/test-failure.t
37 +++ $TESTTMP/test-failure.t.err
37 +++ $TESTTMP/test-failure.t.err
38 @@ -1,4 +1,4 @@
38 @@ -1,4 +1,4 @@
39 $ echo babar
39 $ echo babar
40 - rataxes
40 - rataxes
41 + babar
41 + babar
42 This is a noop statement so that
42 This is a noop statement so that
43 this test is still more bytes than success.
43 this test is still more bytes than success.
44
44
45 ERROR: test-failure.t output changed
45 ERROR: test-failure.t output changed
46 !.
46 !.
47 Failed test-failure.t: output changed
47 Failed test-failure.t: output changed
48 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
48 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
49 python hash seed: * (glob)
49 python hash seed: * (glob)
50 [1]
50 [1]
51 test --xunit support
51 test --xunit support
52 $ $TESTDIR/run-tests.py --with-hg=`which hg` --xunit=xunit.xml
52 $ $TESTDIR/run-tests.py --with-hg=`which hg` --xunit=xunit.xml
53
53
54 --- $TESTTMP/test-failure.t
54 --- $TESTTMP/test-failure.t
55 +++ $TESTTMP/test-failure.t.err
55 +++ $TESTTMP/test-failure.t.err
56 @@ -1,4 +1,4 @@
56 @@ -1,4 +1,4 @@
57 $ echo babar
57 $ echo babar
58 - rataxes
58 - rataxes
59 + babar
59 + babar
60 This is a noop statement so that
60 This is a noop statement so that
61 this test is still more bytes than success.
61 this test is still more bytes than success.
62
62
63 ERROR: test-failure.t output changed
63 ERROR: test-failure.t output changed
64 !.
64 !.
65 Failed test-failure.t: output changed
65 Failed test-failure.t: output changed
66 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
66 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
67 python hash seed: * (glob)
67 python hash seed: * (glob)
68 [1]
68 [1]
69 $ cat xunit.xml
69 $ cat xunit.xml
70 <?xml version="1.0" encoding="utf-8"?>
70 <?xml version="1.0" encoding="utf-8"?>
71 <testsuite errors="0" failures="1" name="run-tests" skipped="0" tests="2">
71 <testsuite errors="0" failures="1" name="run-tests" skipped="0" tests="2">
72 <testcase name="test-success.t" time="*"/> (glob)
72 <testcase name="test-success.t" time="*"/> (glob)
73 <testcase name="test-failure.t" time="*"> (glob)
73 <testcase name="test-failure.t" time="*"> (glob)
74 <![CDATA[--- $TESTTMP/test-failure.t
74 <![CDATA[--- $TESTTMP/test-failure.t
75 +++ $TESTTMP/test-failure.t.err
75 +++ $TESTTMP/test-failure.t.err
76 @@ -1,4 +1,4 @@
76 @@ -1,4 +1,4 @@
77 $ echo babar
77 $ echo babar
78 - rataxes
78 - rataxes
79 + babar
79 + babar
80 This is a noop statement so that
80 This is a noop statement so that
81 this test is still more bytes than success.
81 this test is still more bytes than success.
82 ]]> </testcase>
82 ]]> </testcase>
83 </testsuite>
83 </testsuite>
84
84
85 test for --retest
85 test for --retest
86 ====================
86 ====================
87
87
88 $ $TESTDIR/run-tests.py --with-hg=`which hg` --retest
88 $ $TESTDIR/run-tests.py --with-hg=`which hg` --retest
89
89
90 --- $TESTTMP/test-failure.t
90 --- $TESTTMP/test-failure.t
91 +++ $TESTTMP/test-failure.t.err
91 +++ $TESTTMP/test-failure.t.err
92 @@ -1,4 +1,4 @@
92 @@ -1,4 +1,4 @@
93 $ echo babar
93 $ echo babar
94 - rataxes
94 - rataxes
95 + babar
95 + babar
96 This is a noop statement so that
96 This is a noop statement so that
97 this test is still more bytes than success.
97 this test is still more bytes than success.
98
98
99 ERROR: test-failure.t output changed
99 ERROR: test-failure.t output changed
100 !
100 !
101 Failed test-failure.t: output changed
101 Failed test-failure.t: output changed
102 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
102 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
103 python hash seed: * (glob)
103 python hash seed: * (glob)
104 [1]
104 [1]
105
105
106 Selecting Tests To Run
106 Selecting Tests To Run
107 ======================
107 ======================
108
108
109 successful
109 successful
110
110
111 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t
111 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t
112 .
112 .
113 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
113 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
114
114
115 success w/ keyword
115 success w/ keyword
116 $ $TESTDIR/run-tests.py --with-hg=`which hg` -k xyzzy
116 $ $TESTDIR/run-tests.py --with-hg=`which hg` -k xyzzy
117 i.
117 .
118 # Ran 1 tests, 1 skipped, 0 warned, 0 failed.
118 # Ran 2 tests, 1 skipped, 0 warned, 0 failed.
119
119
120 failed
120 failed
121
121
122 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-failure.t
122 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-failure.t
123
123
124 --- $TESTTMP/test-failure.t
124 --- $TESTTMP/test-failure.t
125 +++ $TESTTMP/test-failure.t.err
125 +++ $TESTTMP/test-failure.t.err
126 @@ -1,4 +1,4 @@
126 @@ -1,4 +1,4 @@
127 $ echo babar
127 $ echo babar
128 - rataxes
128 - rataxes
129 + babar
129 + babar
130 This is a noop statement so that
130 This is a noop statement so that
131 this test is still more bytes than success.
131 this test is still more bytes than success.
132
132
133 ERROR: test-failure.t output changed
133 ERROR: test-failure.t output changed
134 !
134 !
135 Failed test-failure.t: output changed
135 Failed test-failure.t: output changed
136 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
136 # Ran 1 tests, 0 skipped, 0 warned, 1 failed.
137 python hash seed: * (glob)
137 python hash seed: * (glob)
138 [1]
138 [1]
139
139
140 failure w/ keyword
140 failure w/ keyword
141 $ $TESTDIR/run-tests.py --with-hg=`which hg` -k rataxes
141 $ $TESTDIR/run-tests.py --with-hg=`which hg` -k rataxes
142 i
142
143 --- $TESTTMP/test-failure.t
143 --- $TESTTMP/test-failure.t
144 +++ $TESTTMP/test-failure.t.err
144 +++ $TESTTMP/test-failure.t.err
145 @@ -1,4 +1,4 @@
145 @@ -1,4 +1,4 @@
146 $ echo babar
146 $ echo babar
147 - rataxes
147 - rataxes
148 + babar
148 + babar
149 This is a noop statement so that
149 This is a noop statement so that
150 this test is still more bytes than success.
150 this test is still more bytes than success.
151
151
152 ERROR: test-failure.t output changed
152 ERROR: test-failure.t output changed
153 !
153 !
154 Failed test-failure.t: output changed
154 Failed test-failure.t: output changed
155 # Ran 1 tests, 1 skipped, 0 warned, 1 failed.
155 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
156 python hash seed: * (glob)
156 python hash seed: * (glob)
157 [1]
157 [1]
158
158
159 Running In Debug Mode
159 Running In Debug Mode
160 ======================
160 ======================
161
161
162 $ $TESTDIR/run-tests.py --with-hg=`which hg` --debug
162 $ $TESTDIR/run-tests.py --with-hg=`which hg` --debug
163 + echo SALT* 0 0 (glob)
163 + echo SALT* 0 0 (glob)
164 SALT* 0 0 (glob)
164 SALT* 0 0 (glob)
165 + echo babar
165 + echo babar
166 babar
166 babar
167 + echo SALT* 4 0 (glob)
167 + echo SALT* 4 0 (glob)
168 SALT* 4 0 (glob)
168 SALT* 4 0 (glob)
169 .+ echo SALT* 0 0 (glob)
169 .+ echo SALT* 0 0 (glob)
170 SALT* 0 0 (glob)
170 SALT* 0 0 (glob)
171 + echo babar
171 + echo babar
172 babar
172 babar
173 + echo SALT* 2 0 (glob)
173 + echo SALT* 2 0 (glob)
174 SALT* 2 0 (glob)
174 SALT* 2 0 (glob)
175 + echo xyzzy
175 + echo xyzzy
176 xyzzy
176 xyzzy
177 + echo SALT* 4 0 (glob)
177 + echo SALT* 4 0 (glob)
178 SALT* 4 0 (glob)
178 SALT* 4 0 (glob)
179 .
179 .
180 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
180 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
181
181
182 Parallel runs
182 Parallel runs
183 ==============
183 ==============
184
184
185 (duplicate the failing test to get predictable output)
185 (duplicate the failing test to get predictable output)
186 $ cp test-failure.t test-failure-copy.t
186 $ cp test-failure.t test-failure-copy.t
187
187
188 $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t
188 $ $TESTDIR/run-tests.py --with-hg=`which hg` --jobs 2 test-failure*.t
189
189
190 --- $TESTTMP/test-failure*.t (glob)
190 --- $TESTTMP/test-failure*.t (glob)
191 +++ $TESTTMP/test-failure*.t.err (glob)
191 +++ $TESTTMP/test-failure*.t.err (glob)
192 @@ -1,4 +1,4 @@
192 @@ -1,4 +1,4 @@
193 $ echo babar
193 $ echo babar
194 - rataxes
194 - rataxes
195 + babar
195 + babar
196 This is a noop statement so that
196 This is a noop statement so that
197 this test is still more bytes than success.
197 this test is still more bytes than success.
198
198
199 ERROR: test-failure*.t output changed (glob)
199 ERROR: test-failure*.t output changed (glob)
200 !
200 !
201 --- $TESTTMP/test-failure*.t (glob)
201 --- $TESTTMP/test-failure*.t (glob)
202 +++ $TESTTMP/test-failure*.t.err (glob)
202 +++ $TESTTMP/test-failure*.t.err (glob)
203 @@ -1,4 +1,4 @@
203 @@ -1,4 +1,4 @@
204 $ echo babar
204 $ echo babar
205 - rataxes
205 - rataxes
206 + babar
206 + babar
207 This is a noop statement so that
207 This is a noop statement so that
208 this test is still more bytes than success.
208 this test is still more bytes than success.
209
209
210 ERROR: test-failure*.t output changed (glob)
210 ERROR: test-failure*.t output changed (glob)
211 !
211 !
212 Failed test-failure*.t: output changed (glob)
212 Failed test-failure*.t: output changed (glob)
213 Failed test-failure*.t: output changed (glob)
213 Failed test-failure*.t: output changed (glob)
214 # Ran 2 tests, 0 skipped, 0 warned, 2 failed.
214 # Ran 2 tests, 0 skipped, 0 warned, 2 failed.
215 python hash seed: * (glob)
215 python hash seed: * (glob)
216 [1]
216 [1]
217
217
218 (delete the duplicated test file)
218 (delete the duplicated test file)
219 $ rm test-failure-copy.t
219 $ rm test-failure-copy.t
220
220
221
221
222 Interactive run
222 Interactive run
223 ===============
223 ===============
224
224
225 (backup the failing test)
225 (backup the failing test)
226 $ cp test-failure.t backup
226 $ cp test-failure.t backup
227
227
228 Refuse the fix
228 Refuse the fix
229
229
230 $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i
230 $ echo 'n' | $TESTDIR/run-tests.py --with-hg=`which hg` -i
231
231
232 --- $TESTTMP/test-failure.t
232 --- $TESTTMP/test-failure.t
233 +++ $TESTTMP/test-failure.t.err
233 +++ $TESTTMP/test-failure.t.err
234 @@ -1,4 +1,4 @@
234 @@ -1,4 +1,4 @@
235 $ echo babar
235 $ echo babar
236 - rataxes
236 - rataxes
237 + babar
237 + babar
238 This is a noop statement so that
238 This is a noop statement so that
239 this test is still more bytes than success.
239 this test is still more bytes than success.
240 Accept this change? [n]
240 Accept this change? [n]
241 ERROR: test-failure.t output changed
241 ERROR: test-failure.t output changed
242 !.
242 !.
243 Failed test-failure.t: output changed
243 Failed test-failure.t: output changed
244 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
244 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
245 python hash seed: * (glob)
245 python hash seed: * (glob)
246 [1]
246 [1]
247
247
248 $ cat test-failure.t
248 $ cat test-failure.t
249 $ echo babar
249 $ echo babar
250 rataxes
250 rataxes
251 This is a noop statement so that
251 This is a noop statement so that
252 this test is still more bytes than success.
252 this test is still more bytes than success.
253
253
254 Accept the fix
254 Accept the fix
255
255
256 $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` -i
256 $ echo 'y' | $TESTDIR/run-tests.py --with-hg=`which hg` -i
257
257
258 --- $TESTTMP/test-failure.t
258 --- $TESTTMP/test-failure.t
259 +++ $TESTTMP/test-failure.t.err
259 +++ $TESTTMP/test-failure.t.err
260 @@ -1,4 +1,4 @@
260 @@ -1,4 +1,4 @@
261 $ echo babar
261 $ echo babar
262 - rataxes
262 - rataxes
263 + babar
263 + babar
264 This is a noop statement so that
264 This is a noop statement so that
265 this test is still more bytes than success.
265 this test is still more bytes than success.
266 Accept this change? [n] ..
266 Accept this change? [n] ..
267 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
267 # Ran 2 tests, 0 skipped, 0 warned, 0 failed.
268
268
269 $ cat test-failure.t
269 $ cat test-failure.t
270 $ echo babar
270 $ echo babar
271 babar
271 babar
272 This is a noop statement so that
272 This is a noop statement so that
273 this test is still more bytes than success.
273 this test is still more bytes than success.
274
274
275 (reinstall)
275 (reinstall)
276 $ mv backup test-failure.t
276 $ mv backup test-failure.t
277
277
278 No Diff
278 No Diff
279 ===============
279 ===============
280
280
281 $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
281 $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
282 !.
282 !.
283 Failed test-failure.t: output changed
283 Failed test-failure.t: output changed
284 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
284 # Ran 2 tests, 0 skipped, 0 warned, 1 failed.
285 python hash seed: * (glob)
285 python hash seed: * (glob)
286 [1]
286 [1]
287
287
288 test for --time
288 test for --time
289 ==================
289 ==================
290
290
291 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time
291 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time
292 .
292 .
293 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
293 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
294 # Producing time report
294 # Producing time report
295 cuser csys real Test
295 cuser csys real Test
296 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
296 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
297
297
298 test for --time with --job enabled
298 test for --time with --job enabled
299 ====================================
299 ====================================
300
300
301 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2
301 $ $TESTDIR/run-tests.py --with-hg=`which hg` test-success.t --time --jobs 2
302 .
302 .
303 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
303 # Ran 1 tests, 0 skipped, 0 warned, 0 failed.
304 # Producing time report
304 # Producing time report
305 cuser csys real Test
305 cuser csys real Test
306 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
306 \s*[\d\.]{5} \s*[\d\.]{5} \s*[\d\.]{5} test-success.t (re)
307
307
308 Skips
308 Skips
309 ================
309 ================
310 $ cat > test-skip.t <<EOF
310 $ cat > test-skip.t <<EOF
311 > $ echo xyzzy
311 > $ echo xyzzy
312 > #require false
312 > #require false
313 > EOF
313 > EOF
314 $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
314 $ $TESTDIR/run-tests.py --with-hg=`which hg` --nodiff
315 !.s
315 !.s
316 Skipped test-skip.t: irrelevant
316 Skipped test-skip.t: irrelevant
317 Failed test-failure.t: output changed
317 Failed test-failure.t: output changed
318 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
318 # Ran 2 tests, 1 skipped, 0 warned, 1 failed.
319 python hash seed: * (glob)
319 python hash seed: * (glob)
320 [1]
320 [1]
321
321
322 $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy
322 $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy
323 i.s
323 .s
324 Skipped test-skip.t: irrelevant
324 Skipped test-skip.t: irrelevant
325 # Ran 1 tests, 2 skipped, 0 warned, 0 failed.
325 # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
326
326
327 Skips with xml
327 Skips with xml
328 $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy \
328 $ $TESTDIR/run-tests.py --with-hg=`which hg` --keyword xyzzy \
329 > --xunit=xunit.xml
329 > --xunit=xunit.xml
330 i.s
330 .s
331 Skipped test-skip.t: irrelevant
331 Skipped test-skip.t: irrelevant
332 # Ran 1 tests, 2 skipped, 0 warned, 0 failed.
332 # Ran 2 tests, 2 skipped, 0 warned, 0 failed.
333 $ cat xunit.xml
333 $ cat xunit.xml
334 <?xml version="1.0" encoding="utf-8"?>
334 <?xml version="1.0" encoding="utf-8"?>
335 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="1">
335 <testsuite errors="0" failures="0" name="run-tests" skipped="2" tests="2">
336 <testcase name="test-success.t" time="*"/> (glob)
336 <testcase name="test-success.t" time="*"/> (glob)
337 </testsuite>
337 </testsuite>
338
338
339 Missing skips or blacklisted skips don't count as executed:
339 Missing skips or blacklisted skips don't count as executed:
340 $ echo test-failure.t > blacklist
340 $ echo test-failure.t > blacklist
341 $ $TESTDIR/run-tests.py --with-hg=`which hg` --blacklist=blacklist \
341 $ $TESTDIR/run-tests.py --with-hg=`which hg` --blacklist=blacklist \
342 > test-failure.t test-bogus.t
342 > test-failure.t test-bogus.t
343 ss
343 ss
344 Skipped test-bogus.t: Doesn't exist
344 Skipped test-bogus.t: Doesn't exist
345 Skipped test-failure.t: blacklisted
345 Skipped test-failure.t: blacklisted
346 # Ran 0 tests, 2 skipped, 0 warned, 0 failed.
346 # Ran 0 tests, 2 skipped, 0 warned, 0 failed.
347
347
General Comments 0
You need to be logged in to leave comments. Login now