##// END OF EJS Templates
run-tests: add #require to abort full test...
Matt Mackall -
r22045:769198c6 default
parent child Browse files
Show More
@@ -1,1890 +1,1898 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('#if'):
813 if l.startswith('#require'):
814 lsplit = l.split()
815 if len(lsplit) < 2 or lsplit[0] != '#require':
816 after.setdefault(pos, []).append(' !!! invalid #require\n')
817 if not self._hghave(lsplit[1:]):
818 script = ["exit 80\n"]
819 break
820 after.setdefault(pos, []).append(l)
821 elif l.startswith('#if'):
814 lsplit = l.split()
822 lsplit = l.split()
815 if len(lsplit) < 2 or lsplit[0] != '#if':
823 if len(lsplit) < 2 or lsplit[0] != '#if':
816 after.setdefault(pos, []).append(' !!! invalid #if\n')
824 after.setdefault(pos, []).append(' !!! invalid #if\n')
817 if skipping is not None:
825 if skipping is not None:
818 after.setdefault(pos, []).append(' !!! nested #if\n')
826 after.setdefault(pos, []).append(' !!! nested #if\n')
819 skipping = not self._hghave(lsplit[1:])
827 skipping = not self._hghave(lsplit[1:])
820 after.setdefault(pos, []).append(l)
828 after.setdefault(pos, []).append(l)
821 elif l.startswith('#else'):
829 elif l.startswith('#else'):
822 if skipping is None:
830 if skipping is None:
823 after.setdefault(pos, []).append(' !!! missing #if\n')
831 after.setdefault(pos, []).append(' !!! missing #if\n')
824 skipping = not skipping
832 skipping = not skipping
825 after.setdefault(pos, []).append(l)
833 after.setdefault(pos, []).append(l)
826 elif l.startswith('#endif'):
834 elif l.startswith('#endif'):
827 if skipping is None:
835 if skipping is None:
828 after.setdefault(pos, []).append(' !!! missing #if\n')
836 after.setdefault(pos, []).append(' !!! missing #if\n')
829 skipping = None
837 skipping = None
830 after.setdefault(pos, []).append(l)
838 after.setdefault(pos, []).append(l)
831 elif skipping:
839 elif skipping:
832 after.setdefault(pos, []).append(l)
840 after.setdefault(pos, []).append(l)
833 elif l.startswith(' >>> '): # python inlines
841 elif l.startswith(' >>> '): # python inlines
834 after.setdefault(pos, []).append(l)
842 after.setdefault(pos, []).append(l)
835 prepos = pos
843 prepos = pos
836 pos = n
844 pos = n
837 if not inpython:
845 if not inpython:
838 # We've just entered a Python block. Add the header.
846 # We've just entered a Python block. Add the header.
839 inpython = True
847 inpython = True
840 addsalt(prepos, False) # Make sure we report the exit code.
848 addsalt(prepos, False) # Make sure we report the exit code.
841 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
849 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
842 addsalt(n, True)
850 addsalt(n, True)
843 script.append(l[2:])
851 script.append(l[2:])
844 elif l.startswith(' ... '): # python inlines
852 elif l.startswith(' ... '): # python inlines
845 after.setdefault(prepos, []).append(l)
853 after.setdefault(prepos, []).append(l)
846 script.append(l[2:])
854 script.append(l[2:])
847 elif l.startswith(' $ '): # commands
855 elif l.startswith(' $ '): # commands
848 if inpython:
856 if inpython:
849 script.append('EOF\n')
857 script.append('EOF\n')
850 inpython = False
858 inpython = False
851 after.setdefault(pos, []).append(l)
859 after.setdefault(pos, []).append(l)
852 prepos = pos
860 prepos = pos
853 pos = n
861 pos = n
854 addsalt(n, False)
862 addsalt(n, False)
855 cmd = l[4:].split()
863 cmd = l[4:].split()
856 if len(cmd) == 2 and cmd[0] == 'cd':
864 if len(cmd) == 2 and cmd[0] == 'cd':
857 l = ' $ cd %s || exit 1\n' % cmd[1]
865 l = ' $ cd %s || exit 1\n' % cmd[1]
858 script.append(l[4:])
866 script.append(l[4:])
859 elif l.startswith(' > '): # continuations
867 elif l.startswith(' > '): # continuations
860 after.setdefault(prepos, []).append(l)
868 after.setdefault(prepos, []).append(l)
861 script.append(l[4:])
869 script.append(l[4:])
862 elif l.startswith(' '): # results
870 elif l.startswith(' '): # results
863 # Queue up a list of expected results.
871 # Queue up a list of expected results.
864 expected.setdefault(pos, []).append(l[2:])
872 expected.setdefault(pos, []).append(l[2:])
865 else:
873 else:
866 if inpython:
874 if inpython:
867 script.append('EOF\n')
875 script.append('EOF\n')
868 inpython = False
876 inpython = False
869 # Non-command/result. Queue up for merged output.
877 # Non-command/result. Queue up for merged output.
870 after.setdefault(pos, []).append(l)
878 after.setdefault(pos, []).append(l)
871
879
872 if inpython:
880 if inpython:
873 script.append('EOF\n')
881 script.append('EOF\n')
874 if skipping is not None:
882 if skipping is not None:
875 after.setdefault(pos, []).append(' !!! missing #endif\n')
883 after.setdefault(pos, []).append(' !!! missing #endif\n')
876 addsalt(n + 1, False)
884 addsalt(n + 1, False)
877
885
878 return salt, script, after, expected
886 return salt, script, after, expected
879
887
880 def _processoutput(self, exitcode, output, salt, after, expected):
888 def _processoutput(self, exitcode, output, salt, after, expected):
881 # Merge the script output back into a unified test.
889 # Merge the script output back into a unified test.
882 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
890 warnonly = 1 # 1: not yet; 2: yes; 3: for sure not
883 if exitcode != 0:
891 if exitcode != 0:
884 warnonly = 3
892 warnonly = 3
885
893
886 pos = -1
894 pos = -1
887 postout = []
895 postout = []
888 for l in output:
896 for l in output:
889 lout, lcmd = l, None
897 lout, lcmd = l, None
890 if salt in l:
898 if salt in l:
891 lout, lcmd = l.split(salt, 1)
899 lout, lcmd = l.split(salt, 1)
892
900
893 if lout:
901 if lout:
894 if not lout.endswith('\n'):
902 if not lout.endswith('\n'):
895 lout += ' (no-eol)\n'
903 lout += ' (no-eol)\n'
896
904
897 # Find the expected output at the current position.
905 # Find the expected output at the current position.
898 el = None
906 el = None
899 if expected.get(pos, None):
907 if expected.get(pos, None):
900 el = expected[pos].pop(0)
908 el = expected[pos].pop(0)
901
909
902 r = TTest.linematch(el, lout)
910 r = TTest.linematch(el, lout)
903 if isinstance(r, str):
911 if isinstance(r, str):
904 if r == '+glob':
912 if r == '+glob':
905 lout = el[:-1] + ' (glob)\n'
913 lout = el[:-1] + ' (glob)\n'
906 r = '' # Warn only this line.
914 r = '' # Warn only this line.
907 elif r == '-glob':
915 elif r == '-glob':
908 lout = ''.join(el.rsplit(' (glob)', 1))
916 lout = ''.join(el.rsplit(' (glob)', 1))
909 r = '' # Warn only this line.
917 r = '' # Warn only this line.
910 else:
918 else:
911 log('\ninfo, unknown linematch result: %r\n' % r)
919 log('\ninfo, unknown linematch result: %r\n' % r)
912 r = False
920 r = False
913 if r:
921 if r:
914 postout.append(' ' + el)
922 postout.append(' ' + el)
915 else:
923 else:
916 if self.NEEDESCAPE(lout):
924 if self.NEEDESCAPE(lout):
917 lout = TTest._stringescape('%s (esc)\n' %
925 lout = TTest._stringescape('%s (esc)\n' %
918 lout.rstrip('\n'))
926 lout.rstrip('\n'))
919 postout.append(' ' + lout) # Let diff deal with it.
927 postout.append(' ' + lout) # Let diff deal with it.
920 if r != '': # If line failed.
928 if r != '': # If line failed.
921 warnonly = 3 # for sure not
929 warnonly = 3 # for sure not
922 elif warnonly == 1: # Is "not yet" and line is warn only.
930 elif warnonly == 1: # Is "not yet" and line is warn only.
923 warnonly = 2 # Yes do warn.
931 warnonly = 2 # Yes do warn.
924
932
925 if lcmd:
933 if lcmd:
926 # Add on last return code.
934 # Add on last return code.
927 ret = int(lcmd.split()[1])
935 ret = int(lcmd.split()[1])
928 if ret != 0:
936 if ret != 0:
929 postout.append(' [%s]\n' % ret)
937 postout.append(' [%s]\n' % ret)
930 if pos in after:
938 if pos in after:
931 # Merge in non-active test bits.
939 # Merge in non-active test bits.
932 postout += after.pop(pos)
940 postout += after.pop(pos)
933 pos = int(lcmd.split()[0])
941 pos = int(lcmd.split()[0])
934
942
935 if pos in after:
943 if pos in after:
936 postout += after.pop(pos)
944 postout += after.pop(pos)
937
945
938 if warnonly == 2:
946 if warnonly == 2:
939 exitcode = False # Set exitcode to warned.
947 exitcode = False # Set exitcode to warned.
940
948
941 return exitcode, postout
949 return exitcode, postout
942
950
943 @staticmethod
951 @staticmethod
944 def rematch(el, l):
952 def rematch(el, l):
945 try:
953 try:
946 # 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
947 if os.name == 'nt':
955 if os.name == 'nt':
948 return re.match(el + r'\r?\n\Z', l)
956 return re.match(el + r'\r?\n\Z', l)
949 return re.match(el + r'\n\Z', l)
957 return re.match(el + r'\n\Z', l)
950 except re.error:
958 except re.error:
951 # el is an invalid regex
959 # el is an invalid regex
952 return False
960 return False
953
961
954 @staticmethod
962 @staticmethod
955 def globmatch(el, l):
963 def globmatch(el, l):
956 # The only supported special characters are * and ? plus / which also
964 # The only supported special characters are * and ? plus / which also
957 # matches \ on windows. Escaping of these characters is supported.
965 # matches \ on windows. Escaping of these characters is supported.
958 if el + '\n' == l:
966 if el + '\n' == l:
959 if os.altsep:
967 if os.altsep:
960 # matching on "/" is not needed for this line
968 # matching on "/" is not needed for this line
961 return '-glob'
969 return '-glob'
962 return True
970 return True
963 i, n = 0, len(el)
971 i, n = 0, len(el)
964 res = ''
972 res = ''
965 while i < n:
973 while i < n:
966 c = el[i]
974 c = el[i]
967 i += 1
975 i += 1
968 if c == '\\' and el[i] in '*?\\/':
976 if c == '\\' and el[i] in '*?\\/':
969 res += el[i - 1:i + 1]
977 res += el[i - 1:i + 1]
970 i += 1
978 i += 1
971 elif c == '*':
979 elif c == '*':
972 res += '.*'
980 res += '.*'
973 elif c == '?':
981 elif c == '?':
974 res += '.'
982 res += '.'
975 elif c == '/' and os.altsep:
983 elif c == '/' and os.altsep:
976 res += '[/\\\\]'
984 res += '[/\\\\]'
977 else:
985 else:
978 res += re.escape(c)
986 res += re.escape(c)
979 return TTest.rematch(res, l)
987 return TTest.rematch(res, l)
980
988
981 @staticmethod
989 @staticmethod
982 def linematch(el, l):
990 def linematch(el, l):
983 if el == l: # perfect match (fast)
991 if el == l: # perfect match (fast)
984 return True
992 return True
985 if el:
993 if el:
986 if el.endswith(" (esc)\n"):
994 if el.endswith(" (esc)\n"):
987 el = el[:-7].decode('string-escape') + '\n'
995 el = el[:-7].decode('string-escape') + '\n'
988 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:
989 return True
997 return True
990 if el.endswith(" (re)\n"):
998 if el.endswith(" (re)\n"):
991 return TTest.rematch(el[:-6], l)
999 return TTest.rematch(el[:-6], l)
992 if el.endswith(" (glob)\n"):
1000 if el.endswith(" (glob)\n"):
993 return TTest.globmatch(el[:-8], l)
1001 return TTest.globmatch(el[:-8], l)
994 if os.altsep and l.replace('\\', '/') == el:
1002 if os.altsep and l.replace('\\', '/') == el:
995 return '+glob'
1003 return '+glob'
996 return False
1004 return False
997
1005
998 @staticmethod
1006 @staticmethod
999 def parsehghaveoutput(lines):
1007 def parsehghaveoutput(lines):
1000 '''Parse hghave log lines.
1008 '''Parse hghave log lines.
1001
1009
1002 Return tuple of lists (missing, failed):
1010 Return tuple of lists (missing, failed):
1003 * the missing/unknown features
1011 * the missing/unknown features
1004 * the features for which existence check failed'''
1012 * the features for which existence check failed'''
1005 missing = []
1013 missing = []
1006 failed = []
1014 failed = []
1007 for line in lines:
1015 for line in lines:
1008 if line.startswith(TTest.SKIPPED_PREFIX):
1016 if line.startswith(TTest.SKIPPED_PREFIX):
1009 line = line.splitlines()[0]
1017 line = line.splitlines()[0]
1010 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1018 missing.append(line[len(TTest.SKIPPED_PREFIX):])
1011 elif line.startswith(TTest.FAILED_PREFIX):
1019 elif line.startswith(TTest.FAILED_PREFIX):
1012 line = line.splitlines()[0]
1020 line = line.splitlines()[0]
1013 failed.append(line[len(TTest.FAILED_PREFIX):])
1021 failed.append(line[len(TTest.FAILED_PREFIX):])
1014
1022
1015 return missing, failed
1023 return missing, failed
1016
1024
1017 @staticmethod
1025 @staticmethod
1018 def _escapef(m):
1026 def _escapef(m):
1019 return TTest.ESCAPEMAP[m.group(0)]
1027 return TTest.ESCAPEMAP[m.group(0)]
1020
1028
1021 @staticmethod
1029 @staticmethod
1022 def _stringescape(s):
1030 def _stringescape(s):
1023 return TTest.ESCAPESUB(TTest._escapef, s)
1031 return TTest.ESCAPESUB(TTest._escapef, s)
1024
1032
1025
1033
1026 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1034 wifexited = getattr(os, "WIFEXITED", lambda x: False)
1027 def run(cmd, wd, replacements, env, debug=False, timeout=None):
1035 def run(cmd, wd, replacements, env, debug=False, timeout=None):
1028 """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).
1029 Return a tuple (exitcode, output). output is None in debug mode."""
1037 Return a tuple (exitcode, output). output is None in debug mode."""
1030 if debug:
1038 if debug:
1031 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1039 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
1032 ret = proc.wait()
1040 ret = proc.wait()
1033 return (ret, None)
1041 return (ret, None)
1034
1042
1035 proc = Popen4(cmd, wd, timeout, env)
1043 proc = Popen4(cmd, wd, timeout, env)
1036 def cleanup():
1044 def cleanup():
1037 terminate(proc)
1045 terminate(proc)
1038 ret = proc.wait()
1046 ret = proc.wait()
1039 if ret == 0:
1047 if ret == 0:
1040 ret = signal.SIGTERM << 8
1048 ret = signal.SIGTERM << 8
1041 killdaemons(env['DAEMON_PIDS'])
1049 killdaemons(env['DAEMON_PIDS'])
1042 return ret
1050 return ret
1043
1051
1044 output = ''
1052 output = ''
1045 proc.tochild.close()
1053 proc.tochild.close()
1046
1054
1047 try:
1055 try:
1048 output = proc.fromchild.read()
1056 output = proc.fromchild.read()
1049 except KeyboardInterrupt:
1057 except KeyboardInterrupt:
1050 vlog('# Handling keyboard interrupt')
1058 vlog('# Handling keyboard interrupt')
1051 cleanup()
1059 cleanup()
1052 raise
1060 raise
1053
1061
1054 ret = proc.wait()
1062 ret = proc.wait()
1055 if wifexited(ret):
1063 if wifexited(ret):
1056 ret = os.WEXITSTATUS(ret)
1064 ret = os.WEXITSTATUS(ret)
1057
1065
1058 if proc.timeout:
1066 if proc.timeout:
1059 ret = 'timeout'
1067 ret = 'timeout'
1060
1068
1061 if ret:
1069 if ret:
1062 killdaemons(env['DAEMON_PIDS'])
1070 killdaemons(env['DAEMON_PIDS'])
1063
1071
1064 for s, r in replacements:
1072 for s, r in replacements:
1065 output = re.sub(s, r, output)
1073 output = re.sub(s, r, output)
1066 return ret, output.splitlines(True)
1074 return ret, output.splitlines(True)
1067
1075
1068 iolock = threading.Lock()
1076 iolock = threading.Lock()
1069
1077
1070 class SkipTest(Exception):
1078 class SkipTest(Exception):
1071 """Raised to indicate that a test is to be skipped."""
1079 """Raised to indicate that a test is to be skipped."""
1072
1080
1073 class IgnoreTest(Exception):
1081 class IgnoreTest(Exception):
1074 """Raised to indicate that a test is to be ignored."""
1082 """Raised to indicate that a test is to be ignored."""
1075
1083
1076 class WarnTest(Exception):
1084 class WarnTest(Exception):
1077 """Raised to indicate that a test warned."""
1085 """Raised to indicate that a test warned."""
1078
1086
1079 class TestResult(unittest._TextTestResult):
1087 class TestResult(unittest._TextTestResult):
1080 """Holds results when executing via unittest."""
1088 """Holds results when executing via unittest."""
1081 # Don't worry too much about accessing the non-public _TextTestResult.
1089 # Don't worry too much about accessing the non-public _TextTestResult.
1082 # It is relatively common in Python testing tools.
1090 # It is relatively common in Python testing tools.
1083 def __init__(self, options, *args, **kwargs):
1091 def __init__(self, options, *args, **kwargs):
1084 super(TestResult, self).__init__(*args, **kwargs)
1092 super(TestResult, self).__init__(*args, **kwargs)
1085
1093
1086 self._options = options
1094 self._options = options
1087
1095
1088 # 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
1089 # polyfill it.
1097 # polyfill it.
1090 self.skipped = []
1098 self.skipped = []
1091
1099
1092 # 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
1093 # unittest implementation. It is very similar to skipped. It may make
1101 # unittest implementation. It is very similar to skipped. It may make
1094 # sense to map it into skip some day.
1102 # sense to map it into skip some day.
1095 self.ignored = []
1103 self.ignored = []
1096
1104
1097 # 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
1098 # unittest implementation. It is very similar to failed. It may make
1106 # unittest implementation. It is very similar to failed. It may make
1099 # sense to map it into fail some day.
1107 # sense to map it into fail some day.
1100 self.warned = []
1108 self.warned = []
1101
1109
1102 self.times = []
1110 self.times = []
1103 self._started = {}
1111 self._started = {}
1104 self._stopped = {}
1112 self._stopped = {}
1105 # Data stored for the benefit of generating xunit reports.
1113 # Data stored for the benefit of generating xunit reports.
1106 self.successes = []
1114 self.successes = []
1107 self.faildata = {}
1115 self.faildata = {}
1108
1116
1109 def addFailure(self, test, reason):
1117 def addFailure(self, test, reason):
1110 self.failures.append((test, reason))
1118 self.failures.append((test, reason))
1111
1119
1112 if self._options.first:
1120 if self._options.first:
1113 self.stop()
1121 self.stop()
1114 else:
1122 else:
1115 iolock.acquire()
1123 iolock.acquire()
1116 if not self._options.nodiff:
1124 if not self._options.nodiff:
1117 self.stream.write('\nERROR: %s output changed\n' % test)
1125 self.stream.write('\nERROR: %s output changed\n' % test)
1118
1126
1119 self.stream.write('!')
1127 self.stream.write('!')
1120 iolock.release()
1128 iolock.release()
1121
1129
1122 def addSuccess(self, test):
1130 def addSuccess(self, test):
1123 super(TestResult, self).addSuccess(test)
1131 super(TestResult, self).addSuccess(test)
1124 self.successes.append(test)
1132 self.successes.append(test)
1125
1133
1126 def addError(self, test, err):
1134 def addError(self, test, err):
1127 super(TestResult, self).addError(test, err)
1135 super(TestResult, self).addError(test, err)
1128 if self._options.first:
1136 if self._options.first:
1129 self.stop()
1137 self.stop()
1130
1138
1131 # Polyfill.
1139 # Polyfill.
1132 def addSkip(self, test, reason):
1140 def addSkip(self, test, reason):
1133 self.skipped.append((test, reason))
1141 self.skipped.append((test, reason))
1134 if self.showAll:
1142 if self.showAll:
1135 self.stream.writeln('skipped %s' % reason)
1143 self.stream.writeln('skipped %s' % reason)
1136 else:
1144 else:
1137 self.stream.write('s')
1145 self.stream.write('s')
1138 self.stream.flush()
1146 self.stream.flush()
1139
1147
1140 def addIgnore(self, test, reason):
1148 def addIgnore(self, test, reason):
1141 self.ignored.append((test, reason))
1149 self.ignored.append((test, reason))
1142 if self.showAll:
1150 if self.showAll:
1143 self.stream.writeln('ignored %s' % reason)
1151 self.stream.writeln('ignored %s' % reason)
1144 else:
1152 else:
1145 if reason != 'not retesting':
1153 if reason != 'not retesting':
1146 self.stream.write('i')
1154 self.stream.write('i')
1147 else:
1155 else:
1148 self.testsRun += 1
1156 self.testsRun += 1
1149 self.stream.flush()
1157 self.stream.flush()
1150
1158
1151 def addWarn(self, test, reason):
1159 def addWarn(self, test, reason):
1152 self.warned.append((test, reason))
1160 self.warned.append((test, reason))
1153
1161
1154 if self._options.first:
1162 if self._options.first:
1155 self.stop()
1163 self.stop()
1156
1164
1157 if self.showAll:
1165 if self.showAll:
1158 self.stream.writeln('warned %s' % reason)
1166 self.stream.writeln('warned %s' % reason)
1159 else:
1167 else:
1160 self.stream.write('~')
1168 self.stream.write('~')
1161 self.stream.flush()
1169 self.stream.flush()
1162
1170
1163 def addOutputMismatch(self, test, ret, got, expected):
1171 def addOutputMismatch(self, test, ret, got, expected):
1164 """Record a mismatch in test output for a particular test."""
1172 """Record a mismatch in test output for a particular test."""
1165
1173
1166 accepted = False
1174 accepted = False
1167 failed = False
1175 failed = False
1168 lines = []
1176 lines = []
1169
1177
1170 iolock.acquire()
1178 iolock.acquire()
1171 if self._options.nodiff:
1179 if self._options.nodiff:
1172 pass
1180 pass
1173 elif self._options.view:
1181 elif self._options.view:
1174 os.system("%s %s %s" %
1182 os.system("%s %s %s" %
1175 (self._options.view, test.refpath, test.errpath))
1183 (self._options.view, test.refpath, test.errpath))
1176 else:
1184 else:
1177 failed, lines = getdiff(expected, got,
1185 failed, lines = getdiff(expected, got,
1178 test.refpath, test.errpath)
1186 test.refpath, test.errpath)
1179 if failed:
1187 if failed:
1180 self.addFailure(test, 'diff generation failed')
1188 self.addFailure(test, 'diff generation failed')
1181 else:
1189 else:
1182 self.stream.write('\n')
1190 self.stream.write('\n')
1183 for line in lines:
1191 for line in lines:
1184 self.stream.write(line)
1192 self.stream.write(line)
1185 self.stream.flush()
1193 self.stream.flush()
1186
1194
1187 # handle interactive prompt without releasing iolock
1195 # handle interactive prompt without releasing iolock
1188 if self._options.interactive:
1196 if self._options.interactive:
1189 self.stream.write('Accept this change? [n] ')
1197 self.stream.write('Accept this change? [n] ')
1190 answer = sys.stdin.readline().strip()
1198 answer = sys.stdin.readline().strip()
1191 if answer.lower() in ('y', 'yes'):
1199 if answer.lower() in ('y', 'yes'):
1192 if test.name.endswith('.t'):
1200 if test.name.endswith('.t'):
1193 rename(test.errpath, test.path)
1201 rename(test.errpath, test.path)
1194 else:
1202 else:
1195 rename(test.errpath, '%s.out' % test.path)
1203 rename(test.errpath, '%s.out' % test.path)
1196 accepted = True
1204 accepted = True
1197 if not accepted and not failed:
1205 if not accepted and not failed:
1198 self.faildata[test.name] = ''.join(lines)
1206 self.faildata[test.name] = ''.join(lines)
1199 iolock.release()
1207 iolock.release()
1200
1208
1201 return accepted
1209 return accepted
1202
1210
1203 def startTest(self, test):
1211 def startTest(self, test):
1204 super(TestResult, self).startTest(test)
1212 super(TestResult, self).startTest(test)
1205
1213
1206 # os.times module computes the user time and system time spent by
1214 # os.times module computes the user time and system time spent by
1207 # child's processes along with real elapsed time taken by a process.
1215 # child's processes along with real elapsed time taken by a process.
1208 # This module has one limitation. It can only work for Linux user
1216 # This module has one limitation. It can only work for Linux user
1209 # and not for Windows.
1217 # and not for Windows.
1210 self._started[test.name] = os.times()
1218 self._started[test.name] = os.times()
1211
1219
1212 def stopTest(self, test, interrupted=False):
1220 def stopTest(self, test, interrupted=False):
1213 super(TestResult, self).stopTest(test)
1221 super(TestResult, self).stopTest(test)
1214
1222
1215 self._stopped[test.name] = os.times()
1223 self._stopped[test.name] = os.times()
1216
1224
1217 starttime = self._started[test.name]
1225 starttime = self._started[test.name]
1218 endtime = self._stopped[test.name]
1226 endtime = self._stopped[test.name]
1219 self.times.append((test.name, endtime[2] - starttime[2],
1227 self.times.append((test.name, endtime[2] - starttime[2],
1220 endtime[3] - starttime[3], endtime[4] - starttime[4]))
1228 endtime[3] - starttime[3], endtime[4] - starttime[4]))
1221
1229
1222 del self._started[test.name]
1230 del self._started[test.name]
1223 del self._stopped[test.name]
1231 del self._stopped[test.name]
1224
1232
1225 if interrupted:
1233 if interrupted:
1226 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1234 self.stream.writeln('INTERRUPTED: %s (after %d seconds)' % (
1227 test.name, self.times[-1][3]))
1235 test.name, self.times[-1][3]))
1228
1236
1229 class TestSuite(unittest.TestSuite):
1237 class TestSuite(unittest.TestSuite):
1230 """Custom unitest TestSuite that knows how to execute Mercurial tests."""
1238 """Custom unitest TestSuite that knows how to execute Mercurial tests."""
1231
1239
1232 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1240 def __init__(self, testdir, jobs=1, whitelist=None, blacklist=None,
1233 retest=False, keywords=None, loop=False,
1241 retest=False, keywords=None, loop=False,
1234 *args, **kwargs):
1242 *args, **kwargs):
1235 """Create a new instance that can run tests with a configuration.
1243 """Create a new instance that can run tests with a configuration.
1236
1244
1237 testdir specifies the directory where tests are executed from. This
1245 testdir specifies the directory where tests are executed from. This
1238 is typically the ``tests`` directory from Mercurial's source
1246 is typically the ``tests`` directory from Mercurial's source
1239 repository.
1247 repository.
1240
1248
1241 jobs specifies the number of jobs to run concurrently. Each test
1249 jobs specifies the number of jobs to run concurrently. Each test
1242 executes on its own thread. Tests actually spawn new processes, so
1250 executes on its own thread. Tests actually spawn new processes, so
1243 state mutation should not be an issue.
1251 state mutation should not be an issue.
1244
1252
1245 whitelist and blacklist denote tests that have been whitelisted and
1253 whitelist and blacklist denote tests that have been whitelisted and
1246 blacklisted, respectively. These arguments don't belong in TestSuite.
1254 blacklisted, respectively. These arguments don't belong in TestSuite.
1247 Instead, whitelist and blacklist should be handled by the thing that
1255 Instead, whitelist and blacklist should be handled by the thing that
1248 populates the TestSuite with tests. They are present to preserve
1256 populates the TestSuite with tests. They are present to preserve
1249 backwards compatible behavior which reports skipped tests as part
1257 backwards compatible behavior which reports skipped tests as part
1250 of the results.
1258 of the results.
1251
1259
1252 retest denotes whether to retest failed tests. This arguably belongs
1260 retest denotes whether to retest failed tests. This arguably belongs
1253 outside of TestSuite.
1261 outside of TestSuite.
1254
1262
1255 keywords denotes key words that will be used to filter which tests
1263 keywords denotes key words that will be used to filter which tests
1256 to execute. This arguably belongs outside of TestSuite.
1264 to execute. This arguably belongs outside of TestSuite.
1257
1265
1258 loop denotes whether to loop over tests forever.
1266 loop denotes whether to loop over tests forever.
1259 """
1267 """
1260 super(TestSuite, self).__init__(*args, **kwargs)
1268 super(TestSuite, self).__init__(*args, **kwargs)
1261
1269
1262 self._jobs = jobs
1270 self._jobs = jobs
1263 self._whitelist = whitelist
1271 self._whitelist = whitelist
1264 self._blacklist = blacklist
1272 self._blacklist = blacklist
1265 self._retest = retest
1273 self._retest = retest
1266 self._keywords = keywords
1274 self._keywords = keywords
1267 self._loop = loop
1275 self._loop = loop
1268
1276
1269 def run(self, result):
1277 def run(self, result):
1270 # We have a number of filters that need to be applied. We do this
1278 # We have a number of filters that need to be applied. We do this
1271 # here instead of inside Test because it makes the running logic for
1279 # here instead of inside Test because it makes the running logic for
1272 # Test simpler.
1280 # Test simpler.
1273 tests = []
1281 tests = []
1274 for test in self._tests:
1282 for test in self._tests:
1275 if not os.path.exists(test.path):
1283 if not os.path.exists(test.path):
1276 result.addSkip(test, "Doesn't exist")
1284 result.addSkip(test, "Doesn't exist")
1277 continue
1285 continue
1278
1286
1279 if not (self._whitelist and test.name in self._whitelist):
1287 if not (self._whitelist and test.name in self._whitelist):
1280 if self._blacklist and test.name in self._blacklist:
1288 if self._blacklist and test.name in self._blacklist:
1281 result.addSkip(test, 'blacklisted')
1289 result.addSkip(test, 'blacklisted')
1282 continue
1290 continue
1283
1291
1284 if self._retest and not os.path.exists(test.errpath):
1292 if self._retest and not os.path.exists(test.errpath):
1285 result.addIgnore(test, 'not retesting')
1293 result.addIgnore(test, 'not retesting')
1286 continue
1294 continue
1287
1295
1288 if self._keywords:
1296 if self._keywords:
1289 f = open(test.path, 'rb')
1297 f = open(test.path, 'rb')
1290 t = f.read().lower() + test.name.lower()
1298 t = f.read().lower() + test.name.lower()
1291 f.close()
1299 f.close()
1292 ignored = False
1300 ignored = False
1293 for k in self._keywords.lower().split():
1301 for k in self._keywords.lower().split():
1294 if k not in t:
1302 if k not in t:
1295 result.addIgnore(test, "doesn't match keyword")
1303 result.addIgnore(test, "doesn't match keyword")
1296 ignored = True
1304 ignored = True
1297 break
1305 break
1298
1306
1299 if ignored:
1307 if ignored:
1300 continue
1308 continue
1301
1309
1302 tests.append(test)
1310 tests.append(test)
1303
1311
1304 runtests = list(tests)
1312 runtests = list(tests)
1305 done = queue.Queue()
1313 done = queue.Queue()
1306 running = 0
1314 running = 0
1307
1315
1308 def job(test, result):
1316 def job(test, result):
1309 try:
1317 try:
1310 test(result)
1318 test(result)
1311 done.put(None)
1319 done.put(None)
1312 except KeyboardInterrupt:
1320 except KeyboardInterrupt:
1313 pass
1321 pass
1314 except: # re-raises
1322 except: # re-raises
1315 done.put(('!', test, 'run-test raised an error, see traceback'))
1323 done.put(('!', test, 'run-test raised an error, see traceback'))
1316 raise
1324 raise
1317
1325
1318 try:
1326 try:
1319 while tests or running:
1327 while tests or running:
1320 if not done.empty() or running == self._jobs or not tests:
1328 if not done.empty() or running == self._jobs or not tests:
1321 try:
1329 try:
1322 done.get(True, 1)
1330 done.get(True, 1)
1323 if result and result.shouldStop:
1331 if result and result.shouldStop:
1324 break
1332 break
1325 except queue.Empty:
1333 except queue.Empty:
1326 continue
1334 continue
1327 running -= 1
1335 running -= 1
1328 if tests and not running == self._jobs:
1336 if tests and not running == self._jobs:
1329 test = tests.pop(0)
1337 test = tests.pop(0)
1330 if self._loop:
1338 if self._loop:
1331 tests.append(test)
1339 tests.append(test)
1332 t = threading.Thread(target=job, name=test.name,
1340 t = threading.Thread(target=job, name=test.name,
1333 args=(test, result))
1341 args=(test, result))
1334 t.start()
1342 t.start()
1335 running += 1
1343 running += 1
1336 except KeyboardInterrupt:
1344 except KeyboardInterrupt:
1337 for test in runtests:
1345 for test in runtests:
1338 test.abort()
1346 test.abort()
1339
1347
1340 return result
1348 return result
1341
1349
1342 class TextTestRunner(unittest.TextTestRunner):
1350 class TextTestRunner(unittest.TextTestRunner):
1343 """Custom unittest test runner that uses appropriate settings."""
1351 """Custom unittest test runner that uses appropriate settings."""
1344
1352
1345 def __init__(self, runner, *args, **kwargs):
1353 def __init__(self, runner, *args, **kwargs):
1346 super(TextTestRunner, self).__init__(*args, **kwargs)
1354 super(TextTestRunner, self).__init__(*args, **kwargs)
1347
1355
1348 self._runner = runner
1356 self._runner = runner
1349
1357
1350 def run(self, test):
1358 def run(self, test):
1351 result = TestResult(self._runner.options, self.stream,
1359 result = TestResult(self._runner.options, self.stream,
1352 self.descriptions, self.verbosity)
1360 self.descriptions, self.verbosity)
1353
1361
1354 test(result)
1362 test(result)
1355
1363
1356 failed = len(result.failures)
1364 failed = len(result.failures)
1357 warned = len(result.warned)
1365 warned = len(result.warned)
1358 skipped = len(result.skipped)
1366 skipped = len(result.skipped)
1359 ignored = len(result.ignored)
1367 ignored = len(result.ignored)
1360
1368
1361 self.stream.writeln('')
1369 self.stream.writeln('')
1362
1370
1363 if not self._runner.options.noskips:
1371 if not self._runner.options.noskips:
1364 for test, msg in result.skipped:
1372 for test, msg in result.skipped:
1365 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1373 self.stream.writeln('Skipped %s: %s' % (test.name, msg))
1366 for test, msg in result.warned:
1374 for test, msg in result.warned:
1367 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1375 self.stream.writeln('Warned %s: %s' % (test.name, msg))
1368 for test, msg in result.failures:
1376 for test, msg in result.failures:
1369 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1377 self.stream.writeln('Failed %s: %s' % (test.name, msg))
1370 for test, msg in result.errors:
1378 for test, msg in result.errors:
1371 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1379 self.stream.writeln('Errored %s: %s' % (test.name, msg))
1372
1380
1373 if self._runner.options.xunit:
1381 if self._runner.options.xunit:
1374 xuf = open(self._runner.options.xunit, 'wb')
1382 xuf = open(self._runner.options.xunit, 'wb')
1375 try:
1383 try:
1376 timesd = dict(
1384 timesd = dict(
1377 (test, real) for test, cuser, csys, real in result.times)
1385 (test, real) for test, cuser, csys, real in result.times)
1378 doc = minidom.Document()
1386 doc = minidom.Document()
1379 s = doc.createElement('testsuite')
1387 s = doc.createElement('testsuite')
1380 s.setAttribute('name', 'run-tests')
1388 s.setAttribute('name', 'run-tests')
1381 s.setAttribute('tests', str(result.testsRun))
1389 s.setAttribute('tests', str(result.testsRun))
1382 s.setAttribute('errors', "0") # TODO
1390 s.setAttribute('errors', "0") # TODO
1383 s.setAttribute('failures', str(failed))
1391 s.setAttribute('failures', str(failed))
1384 s.setAttribute('skipped', str(skipped + ignored))
1392 s.setAttribute('skipped', str(skipped + ignored))
1385 doc.appendChild(s)
1393 doc.appendChild(s)
1386 for tc in result.successes:
1394 for tc in result.successes:
1387 t = doc.createElement('testcase')
1395 t = doc.createElement('testcase')
1388 t.setAttribute('name', tc.name)
1396 t.setAttribute('name', tc.name)
1389 t.setAttribute('time', '%.3f' % timesd[tc.name])
1397 t.setAttribute('time', '%.3f' % timesd[tc.name])
1390 s.appendChild(t)
1398 s.appendChild(t)
1391 for tc, err in sorted(result.faildata.iteritems()):
1399 for tc, err in sorted(result.faildata.iteritems()):
1392 t = doc.createElement('testcase')
1400 t = doc.createElement('testcase')
1393 t.setAttribute('name', tc)
1401 t.setAttribute('name', tc)
1394 t.setAttribute('time', '%.3f' % timesd[tc])
1402 t.setAttribute('time', '%.3f' % timesd[tc])
1395 cd = doc.createCDATASection(cdatasafe(err))
1403 cd = doc.createCDATASection(cdatasafe(err))
1396 t.appendChild(cd)
1404 t.appendChild(cd)
1397 s.appendChild(t)
1405 s.appendChild(t)
1398 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1406 xuf.write(doc.toprettyxml(indent=' ', encoding='utf-8'))
1399 finally:
1407 finally:
1400 xuf.close()
1408 xuf.close()
1401
1409
1402 self._runner._checkhglib('Tested')
1410 self._runner._checkhglib('Tested')
1403
1411
1404 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1412 self.stream.writeln('# Ran %d tests, %d skipped, %d warned, %d failed.'
1405 % (result.testsRun,
1413 % (result.testsRun,
1406 skipped + ignored, warned, failed))
1414 skipped + ignored, warned, failed))
1407 if failed:
1415 if failed:
1408 self.stream.writeln('python hash seed: %s' %
1416 self.stream.writeln('python hash seed: %s' %
1409 os.environ['PYTHONHASHSEED'])
1417 os.environ['PYTHONHASHSEED'])
1410 if self._runner.options.time:
1418 if self._runner.options.time:
1411 self.printtimes(result.times)
1419 self.printtimes(result.times)
1412
1420
1413 return result
1421 return result
1414
1422
1415 def printtimes(self, times):
1423 def printtimes(self, times):
1416 self.stream.writeln('# Producing time report')
1424 self.stream.writeln('# Producing time report')
1417 times.sort(key=lambda t: (t[3]))
1425 times.sort(key=lambda t: (t[3]))
1418 cols = '%7.3f %7.3f %7.3f %s'
1426 cols = '%7.3f %7.3f %7.3f %s'
1419 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1427 self.stream.writeln('%-7s %-7s %-7s %s' % ('cuser', 'csys', 'real',
1420 'Test'))
1428 'Test'))
1421 for test, cuser, csys, real in times:
1429 for test, cuser, csys, real in times:
1422 self.stream.writeln(cols % (cuser, csys, real, test))
1430 self.stream.writeln(cols % (cuser, csys, real, test))
1423
1431
1424 class TestRunner(object):
1432 class TestRunner(object):
1425 """Holds context for executing tests.
1433 """Holds context for executing tests.
1426
1434
1427 Tests rely on a lot of state. This object holds it for them.
1435 Tests rely on a lot of state. This object holds it for them.
1428 """
1436 """
1429
1437
1430 # Programs required to run tests.
1438 # Programs required to run tests.
1431 REQUIREDTOOLS = [
1439 REQUIREDTOOLS = [
1432 os.path.basename(sys.executable),
1440 os.path.basename(sys.executable),
1433 'diff',
1441 'diff',
1434 'grep',
1442 'grep',
1435 'unzip',
1443 'unzip',
1436 'gunzip',
1444 'gunzip',
1437 'bunzip2',
1445 'bunzip2',
1438 'sed',
1446 'sed',
1439 ]
1447 ]
1440
1448
1441 # Maps file extensions to test class.
1449 # Maps file extensions to test class.
1442 TESTTYPES = [
1450 TESTTYPES = [
1443 ('.py', PythonTest),
1451 ('.py', PythonTest),
1444 ('.t', TTest),
1452 ('.t', TTest),
1445 ]
1453 ]
1446
1454
1447 def __init__(self):
1455 def __init__(self):
1448 self.options = None
1456 self.options = None
1449 self._testdir = None
1457 self._testdir = None
1450 self._hgtmp = None
1458 self._hgtmp = None
1451 self._installdir = None
1459 self._installdir = None
1452 self._bindir = None
1460 self._bindir = None
1453 self._tmpbinddir = None
1461 self._tmpbinddir = None
1454 self._pythondir = None
1462 self._pythondir = None
1455 self._coveragefile = None
1463 self._coveragefile = None
1456 self._createdfiles = []
1464 self._createdfiles = []
1457 self._hgpath = None
1465 self._hgpath = None
1458
1466
1459 def run(self, args, parser=None):
1467 def run(self, args, parser=None):
1460 """Run the test suite."""
1468 """Run the test suite."""
1461 oldmask = os.umask(022)
1469 oldmask = os.umask(022)
1462 try:
1470 try:
1463 parser = parser or getparser()
1471 parser = parser or getparser()
1464 options, args = parseargs(args, parser)
1472 options, args = parseargs(args, parser)
1465 self.options = options
1473 self.options = options
1466
1474
1467 self._checktools()
1475 self._checktools()
1468 tests = self.findtests(args)
1476 tests = self.findtests(args)
1469 return self._run(tests)
1477 return self._run(tests)
1470 finally:
1478 finally:
1471 os.umask(oldmask)
1479 os.umask(oldmask)
1472
1480
1473 def _run(self, tests):
1481 def _run(self, tests):
1474 if self.options.random:
1482 if self.options.random:
1475 random.shuffle(tests)
1483 random.shuffle(tests)
1476 else:
1484 else:
1477 # keywords for slow tests
1485 # keywords for slow tests
1478 slow = 'svn gendoc check-code-hg'.split()
1486 slow = 'svn gendoc check-code-hg'.split()
1479 def sortkey(f):
1487 def sortkey(f):
1480 # run largest tests first, as they tend to take the longest
1488 # run largest tests first, as they tend to take the longest
1481 try:
1489 try:
1482 val = -os.stat(f).st_size
1490 val = -os.stat(f).st_size
1483 except OSError, e:
1491 except OSError, e:
1484 if e.errno != errno.ENOENT:
1492 if e.errno != errno.ENOENT:
1485 raise
1493 raise
1486 return -1e9 # file does not exist, tell early
1494 return -1e9 # file does not exist, tell early
1487 for kw in slow:
1495 for kw in slow:
1488 if kw in f:
1496 if kw in f:
1489 val *= 10
1497 val *= 10
1490 return val
1498 return val
1491 tests.sort(key=sortkey)
1499 tests.sort(key=sortkey)
1492
1500
1493 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1501 self._testdir = os.environ['TESTDIR'] = os.getcwd()
1494
1502
1495 if 'PYTHONHASHSEED' not in os.environ:
1503 if 'PYTHONHASHSEED' not in os.environ:
1496 # use a random python hash seed all the time
1504 # use a random python hash seed all the time
1497 # we do the randomness ourself to know what seed is used
1505 # we do the randomness ourself to know what seed is used
1498 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1506 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1499
1507
1500 if self.options.tmpdir:
1508 if self.options.tmpdir:
1501 self.options.keep_tmpdir = True
1509 self.options.keep_tmpdir = True
1502 tmpdir = self.options.tmpdir
1510 tmpdir = self.options.tmpdir
1503 if os.path.exists(tmpdir):
1511 if os.path.exists(tmpdir):
1504 # Meaning of tmpdir has changed since 1.3: we used to create
1512 # Meaning of tmpdir has changed since 1.3: we used to create
1505 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1513 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1506 # tmpdir already exists.
1514 # tmpdir already exists.
1507 print "error: temp dir %r already exists" % tmpdir
1515 print "error: temp dir %r already exists" % tmpdir
1508 return 1
1516 return 1
1509
1517
1510 # Automatically removing tmpdir sounds convenient, but could
1518 # Automatically removing tmpdir sounds convenient, but could
1511 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1519 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1512 # or "--tmpdir=$HOME".
1520 # or "--tmpdir=$HOME".
1513 #vlog("# Removing temp dir", tmpdir)
1521 #vlog("# Removing temp dir", tmpdir)
1514 #shutil.rmtree(tmpdir)
1522 #shutil.rmtree(tmpdir)
1515 os.makedirs(tmpdir)
1523 os.makedirs(tmpdir)
1516 else:
1524 else:
1517 d = None
1525 d = None
1518 if os.name == 'nt':
1526 if os.name == 'nt':
1519 # without this, we get the default temp dir location, but
1527 # without this, we get the default temp dir location, but
1520 # in all lowercase, which causes troubles with paths (issue3490)
1528 # in all lowercase, which causes troubles with paths (issue3490)
1521 d = os.getenv('TMP')
1529 d = os.getenv('TMP')
1522 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1530 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1523 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1531 self._hgtmp = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1524
1532
1525 if self.options.with_hg:
1533 if self.options.with_hg:
1526 self._installdir = None
1534 self._installdir = None
1527 self._bindir = os.path.dirname(os.path.realpath(
1535 self._bindir = os.path.dirname(os.path.realpath(
1528 self.options.with_hg))
1536 self.options.with_hg))
1529 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1537 self._tmpbindir = os.path.join(self._hgtmp, 'install', 'bin')
1530 os.makedirs(self._tmpbindir)
1538 os.makedirs(self._tmpbindir)
1531
1539
1532 # This looks redundant with how Python initializes sys.path from
1540 # This looks redundant with how Python initializes sys.path from
1533 # the location of the script being executed. Needed because the
1541 # the location of the script being executed. Needed because the
1534 # "hg" specified by --with-hg is not the only Python script
1542 # "hg" specified by --with-hg is not the only Python script
1535 # executed in the test suite that needs to import 'mercurial'
1543 # executed in the test suite that needs to import 'mercurial'
1536 # ... which means it's not really redundant at all.
1544 # ... which means it's not really redundant at all.
1537 self._pythondir = self._bindir
1545 self._pythondir = self._bindir
1538 else:
1546 else:
1539 self._installdir = os.path.join(self._hgtmp, "install")
1547 self._installdir = os.path.join(self._hgtmp, "install")
1540 self._bindir = os.environ["BINDIR"] = \
1548 self._bindir = os.environ["BINDIR"] = \
1541 os.path.join(self._installdir, "bin")
1549 os.path.join(self._installdir, "bin")
1542 self._tmpbindir = self._bindir
1550 self._tmpbindir = self._bindir
1543 self._pythondir = os.path.join(self._installdir, "lib", "python")
1551 self._pythondir = os.path.join(self._installdir, "lib", "python")
1544
1552
1545 os.environ["BINDIR"] = self._bindir
1553 os.environ["BINDIR"] = self._bindir
1546 os.environ["PYTHON"] = PYTHON
1554 os.environ["PYTHON"] = PYTHON
1547
1555
1548 path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
1556 path = [self._bindir] + os.environ["PATH"].split(os.pathsep)
1549 if self._tmpbindir != self._bindir:
1557 if self._tmpbindir != self._bindir:
1550 path = [self._tmpbindir] + path
1558 path = [self._tmpbindir] + path
1551 os.environ["PATH"] = os.pathsep.join(path)
1559 os.environ["PATH"] = os.pathsep.join(path)
1552
1560
1553 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1561 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1554 # can run .../tests/run-tests.py test-foo where test-foo
1562 # can run .../tests/run-tests.py test-foo where test-foo
1555 # adds an extension to HGRC. Also include run-test.py directory to
1563 # adds an extension to HGRC. Also include run-test.py directory to
1556 # import modules like heredoctest.
1564 # import modules like heredoctest.
1557 pypath = [self._pythondir, self._testdir,
1565 pypath = [self._pythondir, self._testdir,
1558 os.path.abspath(os.path.dirname(__file__))]
1566 os.path.abspath(os.path.dirname(__file__))]
1559 # We have to augment PYTHONPATH, rather than simply replacing
1567 # We have to augment PYTHONPATH, rather than simply replacing
1560 # it, in case external libraries are only available via current
1568 # it, in case external libraries are only available via current
1561 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1569 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1562 # are in /opt/subversion.)
1570 # are in /opt/subversion.)
1563 oldpypath = os.environ.get(IMPL_PATH)
1571 oldpypath = os.environ.get(IMPL_PATH)
1564 if oldpypath:
1572 if oldpypath:
1565 pypath.append(oldpypath)
1573 pypath.append(oldpypath)
1566 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1574 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1567
1575
1568 self._coveragefile = os.path.join(self._testdir, '.coverage')
1576 self._coveragefile = os.path.join(self._testdir, '.coverage')
1569
1577
1570 vlog("# Using TESTDIR", self._testdir)
1578 vlog("# Using TESTDIR", self._testdir)
1571 vlog("# Using HGTMP", self._hgtmp)
1579 vlog("# Using HGTMP", self._hgtmp)
1572 vlog("# Using PATH", os.environ["PATH"])
1580 vlog("# Using PATH", os.environ["PATH"])
1573 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1581 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1574
1582
1575 try:
1583 try:
1576 return self._runtests(tests) or 0
1584 return self._runtests(tests) or 0
1577 finally:
1585 finally:
1578 time.sleep(.1)
1586 time.sleep(.1)
1579 self._cleanup()
1587 self._cleanup()
1580
1588
1581 def findtests(self, args):
1589 def findtests(self, args):
1582 """Finds possible test files from arguments.
1590 """Finds possible test files from arguments.
1583
1591
1584 If you wish to inject custom tests into the test harness, this would
1592 If you wish to inject custom tests into the test harness, this would
1585 be a good function to monkeypatch or override in a derived class.
1593 be a good function to monkeypatch or override in a derived class.
1586 """
1594 """
1587 if not args:
1595 if not args:
1588 if self.options.changed:
1596 if self.options.changed:
1589 proc = Popen4('hg st --rev "%s" -man0 .' %
1597 proc = Popen4('hg st --rev "%s" -man0 .' %
1590 self.options.changed, None, 0)
1598 self.options.changed, None, 0)
1591 stdout, stderr = proc.communicate()
1599 stdout, stderr = proc.communicate()
1592 args = stdout.strip('\0').split('\0')
1600 args = stdout.strip('\0').split('\0')
1593 else:
1601 else:
1594 args = os.listdir('.')
1602 args = os.listdir('.')
1595
1603
1596 return [t for t in args
1604 return [t for t in args
1597 if os.path.basename(t).startswith('test-')
1605 if os.path.basename(t).startswith('test-')
1598 and (t.endswith('.py') or t.endswith('.t'))]
1606 and (t.endswith('.py') or t.endswith('.t'))]
1599
1607
1600 def _runtests(self, tests):
1608 def _runtests(self, tests):
1601 try:
1609 try:
1602 if self._installdir:
1610 if self._installdir:
1603 self._installhg()
1611 self._installhg()
1604 self._checkhglib("Testing")
1612 self._checkhglib("Testing")
1605 else:
1613 else:
1606 self._usecorrectpython()
1614 self._usecorrectpython()
1607
1615
1608 if self.options.restart:
1616 if self.options.restart:
1609 orig = list(tests)
1617 orig = list(tests)
1610 while tests:
1618 while tests:
1611 if os.path.exists(tests[0] + ".err"):
1619 if os.path.exists(tests[0] + ".err"):
1612 break
1620 break
1613 tests.pop(0)
1621 tests.pop(0)
1614 if not tests:
1622 if not tests:
1615 print "running all tests"
1623 print "running all tests"
1616 tests = orig
1624 tests = orig
1617
1625
1618 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1626 tests = [self._gettest(t, i) for i, t in enumerate(tests)]
1619
1627
1620 failed = False
1628 failed = False
1621 warned = False
1629 warned = False
1622
1630
1623 suite = TestSuite(self._testdir,
1631 suite = TestSuite(self._testdir,
1624 jobs=self.options.jobs,
1632 jobs=self.options.jobs,
1625 whitelist=self.options.whitelisted,
1633 whitelist=self.options.whitelisted,
1626 blacklist=self.options.blacklist,
1634 blacklist=self.options.blacklist,
1627 retest=self.options.retest,
1635 retest=self.options.retest,
1628 keywords=self.options.keywords,
1636 keywords=self.options.keywords,
1629 loop=self.options.loop,
1637 loop=self.options.loop,
1630 tests=tests)
1638 tests=tests)
1631 verbosity = 1
1639 verbosity = 1
1632 if self.options.verbose:
1640 if self.options.verbose:
1633 verbosity = 2
1641 verbosity = 2
1634 runner = TextTestRunner(self, verbosity=verbosity)
1642 runner = TextTestRunner(self, verbosity=verbosity)
1635 result = runner.run(suite)
1643 result = runner.run(suite)
1636
1644
1637 if result.failures:
1645 if result.failures:
1638 failed = True
1646 failed = True
1639 if result.warned:
1647 if result.warned:
1640 warned = True
1648 warned = True
1641
1649
1642 if self.options.anycoverage:
1650 if self.options.anycoverage:
1643 self._outputcoverage()
1651 self._outputcoverage()
1644 except KeyboardInterrupt:
1652 except KeyboardInterrupt:
1645 failed = True
1653 failed = True
1646 print "\ninterrupted!"
1654 print "\ninterrupted!"
1647
1655
1648 if failed:
1656 if failed:
1649 return 1
1657 return 1
1650 if warned:
1658 if warned:
1651 return 80
1659 return 80
1652
1660
1653 def _gettest(self, test, count):
1661 def _gettest(self, test, count):
1654 """Obtain a Test by looking at its filename.
1662 """Obtain a Test by looking at its filename.
1655
1663
1656 Returns a Test instance. The Test may not be runnable if it doesn't
1664 Returns a Test instance. The Test may not be runnable if it doesn't
1657 map to a known type.
1665 map to a known type.
1658 """
1666 """
1659 lctest = test.lower()
1667 lctest = test.lower()
1660 testcls = Test
1668 testcls = Test
1661
1669
1662 for ext, cls in self.TESTTYPES:
1670 for ext, cls in self.TESTTYPES:
1663 if lctest.endswith(ext):
1671 if lctest.endswith(ext):
1664 testcls = cls
1672 testcls = cls
1665 break
1673 break
1666
1674
1667 refpath = os.path.join(self._testdir, test)
1675 refpath = os.path.join(self._testdir, test)
1668 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1676 tmpdir = os.path.join(self._hgtmp, 'child%d' % count)
1669
1677
1670 return testcls(refpath, tmpdir,
1678 return testcls(refpath, tmpdir,
1671 keeptmpdir=self.options.keep_tmpdir,
1679 keeptmpdir=self.options.keep_tmpdir,
1672 debug=self.options.debug,
1680 debug=self.options.debug,
1673 timeout=self.options.timeout,
1681 timeout=self.options.timeout,
1674 startport=self.options.port + count * 3,
1682 startport=self.options.port + count * 3,
1675 extraconfigopts=self.options.extra_config_opt,
1683 extraconfigopts=self.options.extra_config_opt,
1676 py3kwarnings=self.options.py3k_warnings,
1684 py3kwarnings=self.options.py3k_warnings,
1677 shell=self.options.shell)
1685 shell=self.options.shell)
1678
1686
1679 def _cleanup(self):
1687 def _cleanup(self):
1680 """Clean up state from this test invocation."""
1688 """Clean up state from this test invocation."""
1681
1689
1682 if self.options.keep_tmpdir:
1690 if self.options.keep_tmpdir:
1683 return
1691 return
1684
1692
1685 vlog("# Cleaning up HGTMP", self._hgtmp)
1693 vlog("# Cleaning up HGTMP", self._hgtmp)
1686 shutil.rmtree(self._hgtmp, True)
1694 shutil.rmtree(self._hgtmp, True)
1687 for f in self._createdfiles:
1695 for f in self._createdfiles:
1688 try:
1696 try:
1689 os.remove(f)
1697 os.remove(f)
1690 except OSError:
1698 except OSError:
1691 pass
1699 pass
1692
1700
1693 def _usecorrectpython(self):
1701 def _usecorrectpython(self):
1694 """Configure the environment to use the appropriate Python in tests."""
1702 """Configure the environment to use the appropriate Python in tests."""
1695 # Tests must use the same interpreter as us or bad things will happen.
1703 # Tests must use the same interpreter as us or bad things will happen.
1696 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1704 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
1697 if getattr(os, 'symlink', None):
1705 if getattr(os, 'symlink', None):
1698 vlog("# Making python executable in test path a symlink to '%s'" %
1706 vlog("# Making python executable in test path a symlink to '%s'" %
1699 sys.executable)
1707 sys.executable)
1700 mypython = os.path.join(self._tmpbindir, pyexename)
1708 mypython = os.path.join(self._tmpbindir, pyexename)
1701 try:
1709 try:
1702 if os.readlink(mypython) == sys.executable:
1710 if os.readlink(mypython) == sys.executable:
1703 return
1711 return
1704 os.unlink(mypython)
1712 os.unlink(mypython)
1705 except OSError, err:
1713 except OSError, err:
1706 if err.errno != errno.ENOENT:
1714 if err.errno != errno.ENOENT:
1707 raise
1715 raise
1708 if self._findprogram(pyexename) != sys.executable:
1716 if self._findprogram(pyexename) != sys.executable:
1709 try:
1717 try:
1710 os.symlink(sys.executable, mypython)
1718 os.symlink(sys.executable, mypython)
1711 self._createdfiles.append(mypython)
1719 self._createdfiles.append(mypython)
1712 except OSError, err:
1720 except OSError, err:
1713 # child processes may race, which is harmless
1721 # child processes may race, which is harmless
1714 if err.errno != errno.EEXIST:
1722 if err.errno != errno.EEXIST:
1715 raise
1723 raise
1716 else:
1724 else:
1717 exedir, exename = os.path.split(sys.executable)
1725 exedir, exename = os.path.split(sys.executable)
1718 vlog("# Modifying search path to find %s as %s in '%s'" %
1726 vlog("# Modifying search path to find %s as %s in '%s'" %
1719 (exename, pyexename, exedir))
1727 (exename, pyexename, exedir))
1720 path = os.environ['PATH'].split(os.pathsep)
1728 path = os.environ['PATH'].split(os.pathsep)
1721 while exedir in path:
1729 while exedir in path:
1722 path.remove(exedir)
1730 path.remove(exedir)
1723 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1731 os.environ['PATH'] = os.pathsep.join([exedir] + path)
1724 if not self._findprogram(pyexename):
1732 if not self._findprogram(pyexename):
1725 print "WARNING: Cannot find %s in search path" % pyexename
1733 print "WARNING: Cannot find %s in search path" % pyexename
1726
1734
1727 def _installhg(self):
1735 def _installhg(self):
1728 """Install hg into the test environment.
1736 """Install hg into the test environment.
1729
1737
1730 This will also configure hg with the appropriate testing settings.
1738 This will also configure hg with the appropriate testing settings.
1731 """
1739 """
1732 vlog("# Performing temporary installation of HG")
1740 vlog("# Performing temporary installation of HG")
1733 installerrs = os.path.join("tests", "install.err")
1741 installerrs = os.path.join("tests", "install.err")
1734 compiler = ''
1742 compiler = ''
1735 if self.options.compiler:
1743 if self.options.compiler:
1736 compiler = '--compiler ' + self.options.compiler
1744 compiler = '--compiler ' + self.options.compiler
1737 pure = self.options.pure and "--pure" or ""
1745 pure = self.options.pure and "--pure" or ""
1738 py3 = ''
1746 py3 = ''
1739 if sys.version_info[0] == 3:
1747 if sys.version_info[0] == 3:
1740 py3 = '--c2to3'
1748 py3 = '--c2to3'
1741
1749
1742 # Run installer in hg root
1750 # Run installer in hg root
1743 script = os.path.realpath(sys.argv[0])
1751 script = os.path.realpath(sys.argv[0])
1744 hgroot = os.path.dirname(os.path.dirname(script))
1752 hgroot = os.path.dirname(os.path.dirname(script))
1745 os.chdir(hgroot)
1753 os.chdir(hgroot)
1746 nohome = '--home=""'
1754 nohome = '--home=""'
1747 if os.name == 'nt':
1755 if os.name == 'nt':
1748 # The --home="" trick works only on OS where os.sep == '/'
1756 # The --home="" trick works only on OS where os.sep == '/'
1749 # because of a distutils convert_path() fast-path. Avoid it at
1757 # because of a distutils convert_path() fast-path. Avoid it at
1750 # least on Windows for now, deal with .pydistutils.cfg bugs
1758 # least on Windows for now, deal with .pydistutils.cfg bugs
1751 # when they happen.
1759 # when they happen.
1752 nohome = ''
1760 nohome = ''
1753 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1761 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
1754 ' build %(compiler)s --build-base="%(base)s"'
1762 ' build %(compiler)s --build-base="%(base)s"'
1755 ' install --force --prefix="%(prefix)s"'
1763 ' install --force --prefix="%(prefix)s"'
1756 ' --install-lib="%(libdir)s"'
1764 ' --install-lib="%(libdir)s"'
1757 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1765 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
1758 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1766 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
1759 'compiler': compiler,
1767 'compiler': compiler,
1760 'base': os.path.join(self._hgtmp, "build"),
1768 'base': os.path.join(self._hgtmp, "build"),
1761 'prefix': self._installdir, 'libdir': self._pythondir,
1769 'prefix': self._installdir, 'libdir': self._pythondir,
1762 'bindir': self._bindir,
1770 'bindir': self._bindir,
1763 'nohome': nohome, 'logfile': installerrs})
1771 'nohome': nohome, 'logfile': installerrs})
1764 vlog("# Running", cmd)
1772 vlog("# Running", cmd)
1765 if os.system(cmd) == 0:
1773 if os.system(cmd) == 0:
1766 if not self.options.verbose:
1774 if not self.options.verbose:
1767 os.remove(installerrs)
1775 os.remove(installerrs)
1768 else:
1776 else:
1769 f = open(installerrs, 'rb')
1777 f = open(installerrs, 'rb')
1770 for line in f:
1778 for line in f:
1771 print line,
1779 print line,
1772 f.close()
1780 f.close()
1773 sys.exit(1)
1781 sys.exit(1)
1774 os.chdir(self._testdir)
1782 os.chdir(self._testdir)
1775
1783
1776 self._usecorrectpython()
1784 self._usecorrectpython()
1777
1785
1778 if self.options.py3k_warnings and not self.options.anycoverage:
1786 if self.options.py3k_warnings and not self.options.anycoverage:
1779 vlog("# Updating hg command to enable Py3k Warnings switch")
1787 vlog("# Updating hg command to enable Py3k Warnings switch")
1780 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1788 f = open(os.path.join(self._bindir, 'hg'), 'rb')
1781 lines = [line.rstrip() for line in f]
1789 lines = [line.rstrip() for line in f]
1782 lines[0] += ' -3'
1790 lines[0] += ' -3'
1783 f.close()
1791 f.close()
1784 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1792 f = open(os.path.join(self._bindir, 'hg'), 'wb')
1785 for line in lines:
1793 for line in lines:
1786 f.write(line + '\n')
1794 f.write(line + '\n')
1787 f.close()
1795 f.close()
1788
1796
1789 hgbat = os.path.join(self._bindir, 'hg.bat')
1797 hgbat = os.path.join(self._bindir, 'hg.bat')
1790 if os.path.isfile(hgbat):
1798 if os.path.isfile(hgbat):
1791 # hg.bat expects to be put in bin/scripts while run-tests.py
1799 # hg.bat expects to be put in bin/scripts while run-tests.py
1792 # installation layout put it in bin/ directly. Fix it
1800 # installation layout put it in bin/ directly. Fix it
1793 f = open(hgbat, 'rb')
1801 f = open(hgbat, 'rb')
1794 data = f.read()
1802 data = f.read()
1795 f.close()
1803 f.close()
1796 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1804 if '"%~dp0..\python" "%~dp0hg" %*' in data:
1797 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1805 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
1798 '"%~dp0python" "%~dp0hg" %*')
1806 '"%~dp0python" "%~dp0hg" %*')
1799 f = open(hgbat, 'wb')
1807 f = open(hgbat, 'wb')
1800 f.write(data)
1808 f.write(data)
1801 f.close()
1809 f.close()
1802 else:
1810 else:
1803 print 'WARNING: cannot fix hg.bat reference to python.exe'
1811 print 'WARNING: cannot fix hg.bat reference to python.exe'
1804
1812
1805 if self.options.anycoverage:
1813 if self.options.anycoverage:
1806 custom = os.path.join(self._testdir, 'sitecustomize.py')
1814 custom = os.path.join(self._testdir, 'sitecustomize.py')
1807 target = os.path.join(self._pythondir, 'sitecustomize.py')
1815 target = os.path.join(self._pythondir, 'sitecustomize.py')
1808 vlog('# Installing coverage trigger to %s' % target)
1816 vlog('# Installing coverage trigger to %s' % target)
1809 shutil.copyfile(custom, target)
1817 shutil.copyfile(custom, target)
1810 rc = os.path.join(self._testdir, '.coveragerc')
1818 rc = os.path.join(self._testdir, '.coveragerc')
1811 vlog('# Installing coverage rc to %s' % rc)
1819 vlog('# Installing coverage rc to %s' % rc)
1812 os.environ['COVERAGE_PROCESS_START'] = rc
1820 os.environ['COVERAGE_PROCESS_START'] = rc
1813 fn = os.path.join(self._installdir, '..', '.coverage')
1821 fn = os.path.join(self._installdir, '..', '.coverage')
1814 os.environ['COVERAGE_FILE'] = fn
1822 os.environ['COVERAGE_FILE'] = fn
1815
1823
1816 def _checkhglib(self, verb):
1824 def _checkhglib(self, verb):
1817 """Ensure that the 'mercurial' package imported by python is
1825 """Ensure that the 'mercurial' package imported by python is
1818 the one we expect it to be. If not, print a warning to stderr."""
1826 the one we expect it to be. If not, print a warning to stderr."""
1819 if ((self._bindir == self._pythondir) and
1827 if ((self._bindir == self._pythondir) and
1820 (self._bindir != self._tmpbindir)):
1828 (self._bindir != self._tmpbindir)):
1821 # The pythondir has been infered from --with-hg flag.
1829 # The pythondir has been infered from --with-hg flag.
1822 # We cannot expect anything sensible here
1830 # We cannot expect anything sensible here
1823 return
1831 return
1824 expecthg = os.path.join(self._pythondir, 'mercurial')
1832 expecthg = os.path.join(self._pythondir, 'mercurial')
1825 actualhg = self._gethgpath()
1833 actualhg = self._gethgpath()
1826 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1834 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1827 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1835 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1828 ' (expected %s)\n'
1836 ' (expected %s)\n'
1829 % (verb, actualhg, expecthg))
1837 % (verb, actualhg, expecthg))
1830 def _gethgpath(self):
1838 def _gethgpath(self):
1831 """Return the path to the mercurial package that is actually found by
1839 """Return the path to the mercurial package that is actually found by
1832 the current Python interpreter."""
1840 the current Python interpreter."""
1833 if self._hgpath is not None:
1841 if self._hgpath is not None:
1834 return self._hgpath
1842 return self._hgpath
1835
1843
1836 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1844 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1837 pipe = os.popen(cmd % PYTHON)
1845 pipe = os.popen(cmd % PYTHON)
1838 try:
1846 try:
1839 self._hgpath = pipe.read().strip()
1847 self._hgpath = pipe.read().strip()
1840 finally:
1848 finally:
1841 pipe.close()
1849 pipe.close()
1842
1850
1843 return self._hgpath
1851 return self._hgpath
1844
1852
1845 def _outputcoverage(self):
1853 def _outputcoverage(self):
1846 """Produce code coverage output."""
1854 """Produce code coverage output."""
1847 vlog('# Producing coverage report')
1855 vlog('# Producing coverage report')
1848 os.chdir(self._pythondir)
1856 os.chdir(self._pythondir)
1849
1857
1850 def covrun(*args):
1858 def covrun(*args):
1851 cmd = 'coverage %s' % ' '.join(args)
1859 cmd = 'coverage %s' % ' '.join(args)
1852 vlog('# Running: %s' % cmd)
1860 vlog('# Running: %s' % cmd)
1853 os.system(cmd)
1861 os.system(cmd)
1854
1862
1855 covrun('-c')
1863 covrun('-c')
1856 omit = ','.join(os.path.join(x, '*') for x in
1864 omit = ','.join(os.path.join(x, '*') for x in
1857 [self._bindir, self._testdir])
1865 [self._bindir, self._testdir])
1858 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1866 covrun('-i', '-r', '"--omit=%s"' % omit) # report
1859 if self.options.htmlcov:
1867 if self.options.htmlcov:
1860 htmldir = os.path.join(self._testdir, 'htmlcov')
1868 htmldir = os.path.join(self._testdir, 'htmlcov')
1861 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1869 covrun('-i', '-b', '"--directory=%s"' % htmldir,
1862 '"--omit=%s"' % omit)
1870 '"--omit=%s"' % omit)
1863 if self.options.annotate:
1871 if self.options.annotate:
1864 adir = os.path.join(self._testdir, 'annotated')
1872 adir = os.path.join(self._testdir, 'annotated')
1865 if not os.path.isdir(adir):
1873 if not os.path.isdir(adir):
1866 os.mkdir(adir)
1874 os.mkdir(adir)
1867 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1875 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
1868
1876
1869 def _findprogram(self, program):
1877 def _findprogram(self, program):
1870 """Search PATH for a executable program"""
1878 """Search PATH for a executable program"""
1871 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1879 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
1872 name = os.path.join(p, program)
1880 name = os.path.join(p, program)
1873 if os.name == 'nt' or os.access(name, os.X_OK):
1881 if os.name == 'nt' or os.access(name, os.X_OK):
1874 return name
1882 return name
1875 return None
1883 return None
1876
1884
1877 def _checktools(self):
1885 def _checktools(self):
1878 """Ensure tools required to run tests are present."""
1886 """Ensure tools required to run tests are present."""
1879 for p in self.REQUIREDTOOLS:
1887 for p in self.REQUIREDTOOLS:
1880 if os.name == 'nt' and not p.endswith('.exe'):
1888 if os.name == 'nt' and not p.endswith('.exe'):
1881 p += '.exe'
1889 p += '.exe'
1882 found = self._findprogram(p)
1890 found = self._findprogram(p)
1883 if found:
1891 if found:
1884 vlog("# Found prerequisite", p, "at", found)
1892 vlog("# Found prerequisite", p, "at", found)
1885 else:
1893 else:
1886 print "WARNING: Did not find prerequisite tool: %s " % p
1894 print "WARNING: Did not find prerequisite tool: %s " % p
1887
1895
1888 if __name__ == '__main__':
1896 if __name__ == '__main__':
1889 runner = TestRunner()
1897 runner = TestRunner()
1890 sys.exit(runner.run(sys.argv[1:]))
1898 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 i.
118 # Ran 1 tests, 1 skipped, 0 warned, 0 failed.
118 # Ran 1 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 i
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 1 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 > $ exit 80
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 i.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 1 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 i.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 1 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="1">
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