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