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