##// END OF EJS Templates
run-tests: move killdaemons into Test.run()...
Gregory Szorc -
r21302:9e5d8eaa default
parent child Browse files
Show More
@@ -1,1358 +1,1359 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
60
61 processlock = threading.Lock()
61 processlock = threading.Lock()
62
62
63 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
63 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
64 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
64 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
65 # zombies but it's pretty harmless even if we do.
65 # zombies but it's pretty harmless even if we do.
66 if sys.version_info < (2, 5):
66 if sys.version_info < (2, 5):
67 subprocess._cleanup = lambda: None
67 subprocess._cleanup = lambda: None
68
68
69 closefds = os.name == 'posix'
69 closefds = os.name == 'posix'
70 def Popen4(cmd, wd, timeout, env=None):
70 def Popen4(cmd, wd, timeout, env=None):
71 processlock.acquire()
71 processlock.acquire()
72 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
72 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
73 close_fds=closefds,
73 close_fds=closefds,
74 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
74 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
75 stderr=subprocess.STDOUT)
75 stderr=subprocess.STDOUT)
76 processlock.release()
76 processlock.release()
77
77
78 p.fromchild = p.stdout
78 p.fromchild = p.stdout
79 p.tochild = p.stdin
79 p.tochild = p.stdin
80 p.childerr = p.stderr
80 p.childerr = p.stderr
81
81
82 p.timeout = False
82 p.timeout = False
83 if timeout:
83 if timeout:
84 def t():
84 def t():
85 start = time.time()
85 start = time.time()
86 while time.time() - start < timeout and p.returncode is None:
86 while time.time() - start < timeout and p.returncode is None:
87 time.sleep(.1)
87 time.sleep(.1)
88 p.timeout = True
88 p.timeout = True
89 if p.returncode is None:
89 if p.returncode is None:
90 terminate(p)
90 terminate(p)
91 threading.Thread(target=t).start()
91 threading.Thread(target=t).start()
92
92
93 return p
93 return p
94
94
95 # reserved exit code to skip test (used by hghave)
95 # reserved exit code to skip test (used by hghave)
96 SKIPPED_STATUS = 80
96 SKIPPED_STATUS = 80
97 SKIPPED_PREFIX = 'skipped: '
97 SKIPPED_PREFIX = 'skipped: '
98 FAILED_PREFIX = 'hghave check failed: '
98 FAILED_PREFIX = 'hghave check failed: '
99 PYTHON = sys.executable.replace('\\', '/')
99 PYTHON = sys.executable.replace('\\', '/')
100 IMPL_PATH = 'PYTHONPATH'
100 IMPL_PATH = 'PYTHONPATH'
101 if 'java' in sys.platform:
101 if 'java' in sys.platform:
102 IMPL_PATH = 'JYTHONPATH'
102 IMPL_PATH = 'JYTHONPATH'
103
103
104 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
104 TESTDIR = HGTMP = INST = BINDIR = TMPBINDIR = PYTHONDIR = None
105
105
106 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
106 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
107 "gunzip", "bunzip2", "sed"]
107 "gunzip", "bunzip2", "sed"]
108 createdfiles = []
108 createdfiles = []
109
109
110 defaults = {
110 defaults = {
111 'jobs': ('HGTEST_JOBS', 1),
111 'jobs': ('HGTEST_JOBS', 1),
112 'timeout': ('HGTEST_TIMEOUT', 180),
112 'timeout': ('HGTEST_TIMEOUT', 180),
113 'port': ('HGTEST_PORT', 20059),
113 'port': ('HGTEST_PORT', 20059),
114 'shell': ('HGTEST_SHELL', 'sh'),
114 'shell': ('HGTEST_SHELL', 'sh'),
115 }
115 }
116
116
117 def parselistfiles(files, listtype, warn=True):
117 def parselistfiles(files, listtype, warn=True):
118 entries = dict()
118 entries = dict()
119 for filename in files:
119 for filename in files:
120 try:
120 try:
121 path = os.path.expanduser(os.path.expandvars(filename))
121 path = os.path.expanduser(os.path.expandvars(filename))
122 f = open(path, "r")
122 f = open(path, "r")
123 except IOError, err:
123 except IOError, err:
124 if err.errno != errno.ENOENT:
124 if err.errno != errno.ENOENT:
125 raise
125 raise
126 if warn:
126 if warn:
127 print "warning: no such %s file: %s" % (listtype, filename)
127 print "warning: no such %s file: %s" % (listtype, filename)
128 continue
128 continue
129
129
130 for line in f.readlines():
130 for line in f.readlines():
131 line = line.split('#', 1)[0].strip()
131 line = line.split('#', 1)[0].strip()
132 if line:
132 if line:
133 entries[line] = filename
133 entries[line] = filename
134
134
135 f.close()
135 f.close()
136 return entries
136 return entries
137
137
138 def getparser():
138 def getparser():
139 parser = optparse.OptionParser("%prog [options] [tests]")
139 parser = optparse.OptionParser("%prog [options] [tests]")
140
140
141 # keep these sorted
141 # keep these sorted
142 parser.add_option("--blacklist", action="append",
142 parser.add_option("--blacklist", action="append",
143 help="skip tests listed in the specified blacklist file")
143 help="skip tests listed in the specified blacklist file")
144 parser.add_option("--whitelist", action="append",
144 parser.add_option("--whitelist", action="append",
145 help="always run tests listed in the specified whitelist file")
145 help="always run tests listed in the specified whitelist file")
146 parser.add_option("--changed", type="string",
146 parser.add_option("--changed", type="string",
147 help="run tests that are changed in parent rev or working directory")
147 help="run tests that are changed in parent rev or working directory")
148 parser.add_option("-C", "--annotate", action="store_true",
148 parser.add_option("-C", "--annotate", action="store_true",
149 help="output files annotated with coverage")
149 help="output files annotated with coverage")
150 parser.add_option("-c", "--cover", action="store_true",
150 parser.add_option("-c", "--cover", action="store_true",
151 help="print a test coverage report")
151 help="print a test coverage report")
152 parser.add_option("-d", "--debug", action="store_true",
152 parser.add_option("-d", "--debug", action="store_true",
153 help="debug mode: write output of test scripts to console"
153 help="debug mode: write output of test scripts to console"
154 " rather than capturing and diffing it (disables timeout)")
154 " rather than capturing and diffing it (disables timeout)")
155 parser.add_option("-f", "--first", action="store_true",
155 parser.add_option("-f", "--first", action="store_true",
156 help="exit on the first test failure")
156 help="exit on the first test failure")
157 parser.add_option("-H", "--htmlcov", action="store_true",
157 parser.add_option("-H", "--htmlcov", action="store_true",
158 help="create an HTML report of the coverage of the files")
158 help="create an HTML report of the coverage of the files")
159 parser.add_option("-i", "--interactive", action="store_true",
159 parser.add_option("-i", "--interactive", action="store_true",
160 help="prompt to accept changed output")
160 help="prompt to accept changed output")
161 parser.add_option("-j", "--jobs", type="int",
161 parser.add_option("-j", "--jobs", type="int",
162 help="number of jobs to run in parallel"
162 help="number of jobs to run in parallel"
163 " (default: $%s or %d)" % defaults['jobs'])
163 " (default: $%s or %d)" % defaults['jobs'])
164 parser.add_option("--keep-tmpdir", action="store_true",
164 parser.add_option("--keep-tmpdir", action="store_true",
165 help="keep temporary directory after running tests")
165 help="keep temporary directory after running tests")
166 parser.add_option("-k", "--keywords",
166 parser.add_option("-k", "--keywords",
167 help="run tests matching keywords")
167 help="run tests matching keywords")
168 parser.add_option("-l", "--local", action="store_true",
168 parser.add_option("-l", "--local", action="store_true",
169 help="shortcut for --with-hg=<testdir>/../hg")
169 help="shortcut for --with-hg=<testdir>/../hg")
170 parser.add_option("--loop", action="store_true",
170 parser.add_option("--loop", action="store_true",
171 help="loop tests repeatedly")
171 help="loop tests repeatedly")
172 parser.add_option("-n", "--nodiff", action="store_true",
172 parser.add_option("-n", "--nodiff", action="store_true",
173 help="skip showing test changes")
173 help="skip showing test changes")
174 parser.add_option("-p", "--port", type="int",
174 parser.add_option("-p", "--port", type="int",
175 help="port on which servers should listen"
175 help="port on which servers should listen"
176 " (default: $%s or %d)" % defaults['port'])
176 " (default: $%s or %d)" % defaults['port'])
177 parser.add_option("--compiler", type="string",
177 parser.add_option("--compiler", type="string",
178 help="compiler to build with")
178 help="compiler to build with")
179 parser.add_option("--pure", action="store_true",
179 parser.add_option("--pure", action="store_true",
180 help="use pure Python code instead of C extensions")
180 help="use pure Python code instead of C extensions")
181 parser.add_option("-R", "--restart", action="store_true",
181 parser.add_option("-R", "--restart", action="store_true",
182 help="restart at last error")
182 help="restart at last error")
183 parser.add_option("-r", "--retest", action="store_true",
183 parser.add_option("-r", "--retest", action="store_true",
184 help="retest failed tests")
184 help="retest failed tests")
185 parser.add_option("-S", "--noskips", action="store_true",
185 parser.add_option("-S", "--noskips", action="store_true",
186 help="don't report skip tests verbosely")
186 help="don't report skip tests verbosely")
187 parser.add_option("--shell", type="string",
187 parser.add_option("--shell", type="string",
188 help="shell to use (default: $%s or %s)" % defaults['shell'])
188 help="shell to use (default: $%s or %s)" % defaults['shell'])
189 parser.add_option("-t", "--timeout", type="int",
189 parser.add_option("-t", "--timeout", type="int",
190 help="kill errant tests after TIMEOUT seconds"
190 help="kill errant tests after TIMEOUT seconds"
191 " (default: $%s or %d)" % defaults['timeout'])
191 " (default: $%s or %d)" % defaults['timeout'])
192 parser.add_option("--time", action="store_true",
192 parser.add_option("--time", action="store_true",
193 help="time how long each test takes")
193 help="time how long each test takes")
194 parser.add_option("--tmpdir", type="string",
194 parser.add_option("--tmpdir", type="string",
195 help="run tests in the given temporary directory"
195 help="run tests in the given temporary directory"
196 " (implies --keep-tmpdir)")
196 " (implies --keep-tmpdir)")
197 parser.add_option("-v", "--verbose", action="store_true",
197 parser.add_option("-v", "--verbose", action="store_true",
198 help="output verbose messages")
198 help="output verbose messages")
199 parser.add_option("--view", type="string",
199 parser.add_option("--view", type="string",
200 help="external diff viewer")
200 help="external diff viewer")
201 parser.add_option("--with-hg", type="string",
201 parser.add_option("--with-hg", type="string",
202 metavar="HG",
202 metavar="HG",
203 help="test using specified hg script rather than a "
203 help="test using specified hg script rather than a "
204 "temporary installation")
204 "temporary installation")
205 parser.add_option("-3", "--py3k-warnings", action="store_true",
205 parser.add_option("-3", "--py3k-warnings", action="store_true",
206 help="enable Py3k warnings on Python 2.6+")
206 help="enable Py3k warnings on Python 2.6+")
207 parser.add_option('--extra-config-opt', action="append",
207 parser.add_option('--extra-config-opt', action="append",
208 help='set the given config opt in the test hgrc')
208 help='set the given config opt in the test hgrc')
209 parser.add_option('--random', action="store_true",
209 parser.add_option('--random', action="store_true",
210 help='run tests in random order')
210 help='run tests in random order')
211
211
212 for option, (envvar, default) in defaults.items():
212 for option, (envvar, default) in defaults.items():
213 defaults[option] = type(default)(os.environ.get(envvar, default))
213 defaults[option] = type(default)(os.environ.get(envvar, default))
214 parser.set_defaults(**defaults)
214 parser.set_defaults(**defaults)
215
215
216 return parser
216 return parser
217
217
218 def parseargs(args, parser):
218 def parseargs(args, parser):
219 (options, args) = parser.parse_args(args)
219 (options, args) = parser.parse_args(args)
220
220
221 # jython is always pure
221 # jython is always pure
222 if 'java' in sys.platform or '__pypy__' in sys.modules:
222 if 'java' in sys.platform or '__pypy__' in sys.modules:
223 options.pure = True
223 options.pure = True
224
224
225 if options.with_hg:
225 if options.with_hg:
226 options.with_hg = os.path.expanduser(options.with_hg)
226 options.with_hg = os.path.expanduser(options.with_hg)
227 if not (os.path.isfile(options.with_hg) and
227 if not (os.path.isfile(options.with_hg) and
228 os.access(options.with_hg, os.X_OK)):
228 os.access(options.with_hg, os.X_OK)):
229 parser.error('--with-hg must specify an executable hg script')
229 parser.error('--with-hg must specify an executable hg script')
230 if not os.path.basename(options.with_hg) == 'hg':
230 if not os.path.basename(options.with_hg) == 'hg':
231 sys.stderr.write('warning: --with-hg should specify an hg script\n')
231 sys.stderr.write('warning: --with-hg should specify an hg script\n')
232 if options.local:
232 if options.local:
233 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
233 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
234 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
234 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
235 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
235 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
236 parser.error('--local specified, but %r not found or not executable'
236 parser.error('--local specified, but %r not found or not executable'
237 % hgbin)
237 % hgbin)
238 options.with_hg = hgbin
238 options.with_hg = hgbin
239
239
240 options.anycoverage = options.cover or options.annotate or options.htmlcov
240 options.anycoverage = options.cover or options.annotate or options.htmlcov
241 if options.anycoverage:
241 if options.anycoverage:
242 try:
242 try:
243 import coverage
243 import coverage
244 covver = version.StrictVersion(coverage.__version__).version
244 covver = version.StrictVersion(coverage.__version__).version
245 if covver < (3, 3):
245 if covver < (3, 3):
246 parser.error('coverage options require coverage 3.3 or later')
246 parser.error('coverage options require coverage 3.3 or later')
247 except ImportError:
247 except ImportError:
248 parser.error('coverage options now require the coverage package')
248 parser.error('coverage options now require the coverage package')
249
249
250 if options.anycoverage and options.local:
250 if options.anycoverage and options.local:
251 # this needs some path mangling somewhere, I guess
251 # this needs some path mangling somewhere, I guess
252 parser.error("sorry, coverage options do not work when --local "
252 parser.error("sorry, coverage options do not work when --local "
253 "is specified")
253 "is specified")
254
254
255 global verbose
255 global verbose
256 if options.verbose:
256 if options.verbose:
257 verbose = ''
257 verbose = ''
258
258
259 if options.tmpdir:
259 if options.tmpdir:
260 options.tmpdir = os.path.expanduser(options.tmpdir)
260 options.tmpdir = os.path.expanduser(options.tmpdir)
261
261
262 if options.jobs < 1:
262 if options.jobs < 1:
263 parser.error('--jobs must be positive')
263 parser.error('--jobs must be positive')
264 if options.interactive and options.debug:
264 if options.interactive and options.debug:
265 parser.error("-i/--interactive and -d/--debug are incompatible")
265 parser.error("-i/--interactive and -d/--debug are incompatible")
266 if options.debug:
266 if options.debug:
267 if options.timeout != defaults['timeout']:
267 if options.timeout != defaults['timeout']:
268 sys.stderr.write(
268 sys.stderr.write(
269 'warning: --timeout option ignored with --debug\n')
269 'warning: --timeout option ignored with --debug\n')
270 options.timeout = 0
270 options.timeout = 0
271 if options.py3k_warnings:
271 if options.py3k_warnings:
272 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
272 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
273 parser.error('--py3k-warnings can only be used on Python 2.6+')
273 parser.error('--py3k-warnings can only be used on Python 2.6+')
274 if options.blacklist:
274 if options.blacklist:
275 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
275 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
276 if options.whitelist:
276 if options.whitelist:
277 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
277 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
278 else:
278 else:
279 options.whitelisted = {}
279 options.whitelisted = {}
280
280
281 return (options, args)
281 return (options, args)
282
282
283 def rename(src, dst):
283 def rename(src, dst):
284 """Like os.rename(), trade atomicity and opened files friendliness
284 """Like os.rename(), trade atomicity and opened files friendliness
285 for existing destination support.
285 for existing destination support.
286 """
286 """
287 shutil.copy(src, dst)
287 shutil.copy(src, dst)
288 os.remove(src)
288 os.remove(src)
289
289
290 def parsehghaveoutput(lines):
290 def parsehghaveoutput(lines):
291 '''Parse hghave log lines.
291 '''Parse hghave log lines.
292 Return tuple of lists (missing, failed):
292 Return tuple of lists (missing, failed):
293 * the missing/unknown features
293 * the missing/unknown features
294 * the features for which existence check failed'''
294 * the features for which existence check failed'''
295 missing = []
295 missing = []
296 failed = []
296 failed = []
297 for line in lines:
297 for line in lines:
298 if line.startswith(SKIPPED_PREFIX):
298 if line.startswith(SKIPPED_PREFIX):
299 line = line.splitlines()[0]
299 line = line.splitlines()[0]
300 missing.append(line[len(SKIPPED_PREFIX):])
300 missing.append(line[len(SKIPPED_PREFIX):])
301 elif line.startswith(FAILED_PREFIX):
301 elif line.startswith(FAILED_PREFIX):
302 line = line.splitlines()[0]
302 line = line.splitlines()[0]
303 failed.append(line[len(FAILED_PREFIX):])
303 failed.append(line[len(FAILED_PREFIX):])
304
304
305 return missing, failed
305 return missing, failed
306
306
307 def showdiff(expected, output, ref, err):
307 def showdiff(expected, output, ref, err):
308 print
308 print
309 servefail = False
309 servefail = False
310 for line in difflib.unified_diff(expected, output, ref, err):
310 for line in difflib.unified_diff(expected, output, ref, err):
311 sys.stdout.write(line)
311 sys.stdout.write(line)
312 if not servefail and line.startswith(
312 if not servefail and line.startswith(
313 '+ abort: child process failed to start'):
313 '+ abort: child process failed to start'):
314 servefail = True
314 servefail = True
315 return {'servefail': servefail}
315 return {'servefail': servefail}
316
316
317
317
318 verbose = False
318 verbose = False
319 def vlog(*msg):
319 def vlog(*msg):
320 if verbose is not False:
320 if verbose is not False:
321 iolock.acquire()
321 iolock.acquire()
322 if verbose:
322 if verbose:
323 print verbose,
323 print verbose,
324 for m in msg:
324 for m in msg:
325 print m,
325 print m,
326 print
326 print
327 sys.stdout.flush()
327 sys.stdout.flush()
328 iolock.release()
328 iolock.release()
329
329
330 def log(*msg):
330 def log(*msg):
331 iolock.acquire()
331 iolock.acquire()
332 if verbose:
332 if verbose:
333 print verbose,
333 print verbose,
334 for m in msg:
334 for m in msg:
335 print m,
335 print m,
336 print
336 print
337 sys.stdout.flush()
337 sys.stdout.flush()
338 iolock.release()
338 iolock.release()
339
339
340 def findprogram(program):
340 def findprogram(program):
341 """Search PATH for a executable program"""
341 """Search PATH for a executable program"""
342 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
342 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
343 name = os.path.join(p, program)
343 name = os.path.join(p, program)
344 if os.name == 'nt' or os.access(name, os.X_OK):
344 if os.name == 'nt' or os.access(name, os.X_OK):
345 return name
345 return name
346 return None
346 return None
347
347
348 def createhgrc(path, options):
348 def createhgrc(path, options):
349 # create a fresh hgrc
349 # create a fresh hgrc
350 hgrc = open(path, 'w')
350 hgrc = open(path, 'w')
351 hgrc.write('[ui]\n')
351 hgrc.write('[ui]\n')
352 hgrc.write('slash = True\n')
352 hgrc.write('slash = True\n')
353 hgrc.write('interactive = False\n')
353 hgrc.write('interactive = False\n')
354 hgrc.write('[defaults]\n')
354 hgrc.write('[defaults]\n')
355 hgrc.write('backout = -d "0 0"\n')
355 hgrc.write('backout = -d "0 0"\n')
356 hgrc.write('commit = -d "0 0"\n')
356 hgrc.write('commit = -d "0 0"\n')
357 hgrc.write('shelve = --date "0 0"\n')
357 hgrc.write('shelve = --date "0 0"\n')
358 hgrc.write('tag = -d "0 0"\n')
358 hgrc.write('tag = -d "0 0"\n')
359 if options.extra_config_opt:
359 if options.extra_config_opt:
360 for opt in options.extra_config_opt:
360 for opt in options.extra_config_opt:
361 section, key = opt.split('.', 1)
361 section, key = opt.split('.', 1)
362 assert '=' in key, ('extra config opt %s must '
362 assert '=' in key, ('extra config opt %s must '
363 'have an = for assignment' % opt)
363 'have an = for assignment' % opt)
364 hgrc.write('[%s]\n%s\n' % (section, key))
364 hgrc.write('[%s]\n%s\n' % (section, key))
365 hgrc.close()
365 hgrc.close()
366
366
367 def checktools():
367 def checktools():
368 # Before we go any further, check for pre-requisite tools
368 # Before we go any further, check for pre-requisite tools
369 # stuff from coreutils (cat, rm, etc) are not tested
369 # stuff from coreutils (cat, rm, etc) are not tested
370 for p in requiredtools:
370 for p in requiredtools:
371 if os.name == 'nt' and not p.endswith('.exe'):
371 if os.name == 'nt' and not p.endswith('.exe'):
372 p += '.exe'
372 p += '.exe'
373 found = findprogram(p)
373 found = findprogram(p)
374 if found:
374 if found:
375 vlog("# Found prerequisite", p, "at", found)
375 vlog("# Found prerequisite", p, "at", found)
376 else:
376 else:
377 print "WARNING: Did not find prerequisite tool: "+p
377 print "WARNING: Did not find prerequisite tool: "+p
378
378
379 def terminate(proc):
379 def terminate(proc):
380 """Terminate subprocess (with fallback for Python versions < 2.6)"""
380 """Terminate subprocess (with fallback for Python versions < 2.6)"""
381 vlog('# Terminating process %d' % proc.pid)
381 vlog('# Terminating process %d' % proc.pid)
382 try:
382 try:
383 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
383 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
384 except OSError:
384 except OSError:
385 pass
385 pass
386
386
387 def killdaemons(pidfile):
387 def killdaemons(pidfile):
388 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
388 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
389 logfn=vlog)
389 logfn=vlog)
390
390
391 def cleanup(options):
391 def cleanup(options):
392 if not options.keep_tmpdir:
392 if not options.keep_tmpdir:
393 vlog("# Cleaning up HGTMP", HGTMP)
393 vlog("# Cleaning up HGTMP", HGTMP)
394 shutil.rmtree(HGTMP, True)
394 shutil.rmtree(HGTMP, True)
395 for f in createdfiles:
395 for f in createdfiles:
396 try:
396 try:
397 os.remove(f)
397 os.remove(f)
398 except OSError:
398 except OSError:
399 pass
399 pass
400
400
401 def usecorrectpython():
401 def usecorrectpython():
402 # some tests run python interpreter. they must use same
402 # some tests run python interpreter. they must use same
403 # interpreter we use or bad things will happen.
403 # interpreter we use or bad things will happen.
404 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
404 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
405 if getattr(os, 'symlink', None):
405 if getattr(os, 'symlink', None):
406 vlog("# Making python executable in test path a symlink to '%s'" %
406 vlog("# Making python executable in test path a symlink to '%s'" %
407 sys.executable)
407 sys.executable)
408 mypython = os.path.join(TMPBINDIR, pyexename)
408 mypython = os.path.join(TMPBINDIR, pyexename)
409 try:
409 try:
410 if os.readlink(mypython) == sys.executable:
410 if os.readlink(mypython) == sys.executable:
411 return
411 return
412 os.unlink(mypython)
412 os.unlink(mypython)
413 except OSError, err:
413 except OSError, err:
414 if err.errno != errno.ENOENT:
414 if err.errno != errno.ENOENT:
415 raise
415 raise
416 if findprogram(pyexename) != sys.executable:
416 if findprogram(pyexename) != sys.executable:
417 try:
417 try:
418 os.symlink(sys.executable, mypython)
418 os.symlink(sys.executable, mypython)
419 createdfiles.append(mypython)
419 createdfiles.append(mypython)
420 except OSError, err:
420 except OSError, err:
421 # child processes may race, which is harmless
421 # child processes may race, which is harmless
422 if err.errno != errno.EEXIST:
422 if err.errno != errno.EEXIST:
423 raise
423 raise
424 else:
424 else:
425 exedir, exename = os.path.split(sys.executable)
425 exedir, exename = os.path.split(sys.executable)
426 vlog("# Modifying search path to find %s as %s in '%s'" %
426 vlog("# Modifying search path to find %s as %s in '%s'" %
427 (exename, pyexename, exedir))
427 (exename, pyexename, exedir))
428 path = os.environ['PATH'].split(os.pathsep)
428 path = os.environ['PATH'].split(os.pathsep)
429 while exedir in path:
429 while exedir in path:
430 path.remove(exedir)
430 path.remove(exedir)
431 os.environ['PATH'] = os.pathsep.join([exedir] + path)
431 os.environ['PATH'] = os.pathsep.join([exedir] + path)
432 if not findprogram(pyexename):
432 if not findprogram(pyexename):
433 print "WARNING: Cannot find %s in search path" % pyexename
433 print "WARNING: Cannot find %s in search path" % pyexename
434
434
435 def installhg(options):
435 def installhg(options):
436 vlog("# Performing temporary installation of HG")
436 vlog("# Performing temporary installation of HG")
437 installerrs = os.path.join("tests", "install.err")
437 installerrs = os.path.join("tests", "install.err")
438 compiler = ''
438 compiler = ''
439 if options.compiler:
439 if options.compiler:
440 compiler = '--compiler ' + options.compiler
440 compiler = '--compiler ' + options.compiler
441 pure = options.pure and "--pure" or ""
441 pure = options.pure and "--pure" or ""
442 py3 = ''
442 py3 = ''
443 if sys.version_info[0] == 3:
443 if sys.version_info[0] == 3:
444 py3 = '--c2to3'
444 py3 = '--c2to3'
445
445
446 # Run installer in hg root
446 # Run installer in hg root
447 script = os.path.realpath(sys.argv[0])
447 script = os.path.realpath(sys.argv[0])
448 hgroot = os.path.dirname(os.path.dirname(script))
448 hgroot = os.path.dirname(os.path.dirname(script))
449 os.chdir(hgroot)
449 os.chdir(hgroot)
450 nohome = '--home=""'
450 nohome = '--home=""'
451 if os.name == 'nt':
451 if os.name == 'nt':
452 # The --home="" trick works only on OS where os.sep == '/'
452 # The --home="" trick works only on OS where os.sep == '/'
453 # because of a distutils convert_path() fast-path. Avoid it at
453 # because of a distutils convert_path() fast-path. Avoid it at
454 # least on Windows for now, deal with .pydistutils.cfg bugs
454 # least on Windows for now, deal with .pydistutils.cfg bugs
455 # when they happen.
455 # when they happen.
456 nohome = ''
456 nohome = ''
457 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
457 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
458 ' build %(compiler)s --build-base="%(base)s"'
458 ' build %(compiler)s --build-base="%(base)s"'
459 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
459 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
460 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
460 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
461 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
461 % {'exe': sys.executable, 'py3': py3, 'pure': pure,
462 'compiler': compiler, 'base': os.path.join(HGTMP, "build"),
462 'compiler': compiler, 'base': os.path.join(HGTMP, "build"),
463 'prefix': INST, 'libdir': PYTHONDIR, 'bindir': BINDIR,
463 'prefix': INST, 'libdir': PYTHONDIR, 'bindir': BINDIR,
464 'nohome': nohome, 'logfile': installerrs})
464 'nohome': nohome, 'logfile': installerrs})
465 vlog("# Running", cmd)
465 vlog("# Running", cmd)
466 if os.system(cmd) == 0:
466 if os.system(cmd) == 0:
467 if not options.verbose:
467 if not options.verbose:
468 os.remove(installerrs)
468 os.remove(installerrs)
469 else:
469 else:
470 f = open(installerrs)
470 f = open(installerrs)
471 for line in f:
471 for line in f:
472 print line,
472 print line,
473 f.close()
473 f.close()
474 sys.exit(1)
474 sys.exit(1)
475 os.chdir(TESTDIR)
475 os.chdir(TESTDIR)
476
476
477 usecorrectpython()
477 usecorrectpython()
478
478
479 if options.py3k_warnings and not options.anycoverage:
479 if options.py3k_warnings and not options.anycoverage:
480 vlog("# Updating hg command to enable Py3k Warnings switch")
480 vlog("# Updating hg command to enable Py3k Warnings switch")
481 f = open(os.path.join(BINDIR, 'hg'), 'r')
481 f = open(os.path.join(BINDIR, 'hg'), 'r')
482 lines = [line.rstrip() for line in f]
482 lines = [line.rstrip() for line in f]
483 lines[0] += ' -3'
483 lines[0] += ' -3'
484 f.close()
484 f.close()
485 f = open(os.path.join(BINDIR, 'hg'), 'w')
485 f = open(os.path.join(BINDIR, 'hg'), 'w')
486 for line in lines:
486 for line in lines:
487 f.write(line + '\n')
487 f.write(line + '\n')
488 f.close()
488 f.close()
489
489
490 hgbat = os.path.join(BINDIR, 'hg.bat')
490 hgbat = os.path.join(BINDIR, 'hg.bat')
491 if os.path.isfile(hgbat):
491 if os.path.isfile(hgbat):
492 # hg.bat expects to be put in bin/scripts while run-tests.py
492 # hg.bat expects to be put in bin/scripts while run-tests.py
493 # installation layout put it in bin/ directly. Fix it
493 # installation layout put it in bin/ directly. Fix it
494 f = open(hgbat, 'rb')
494 f = open(hgbat, 'rb')
495 data = f.read()
495 data = f.read()
496 f.close()
496 f.close()
497 if '"%~dp0..\python" "%~dp0hg" %*' in data:
497 if '"%~dp0..\python" "%~dp0hg" %*' in data:
498 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
498 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
499 '"%~dp0python" "%~dp0hg" %*')
499 '"%~dp0python" "%~dp0hg" %*')
500 f = open(hgbat, 'wb')
500 f = open(hgbat, 'wb')
501 f.write(data)
501 f.write(data)
502 f.close()
502 f.close()
503 else:
503 else:
504 print 'WARNING: cannot fix hg.bat reference to python.exe'
504 print 'WARNING: cannot fix hg.bat reference to python.exe'
505
505
506 if options.anycoverage:
506 if options.anycoverage:
507 custom = os.path.join(TESTDIR, 'sitecustomize.py')
507 custom = os.path.join(TESTDIR, 'sitecustomize.py')
508 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
508 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
509 vlog('# Installing coverage trigger to %s' % target)
509 vlog('# Installing coverage trigger to %s' % target)
510 shutil.copyfile(custom, target)
510 shutil.copyfile(custom, target)
511 rc = os.path.join(TESTDIR, '.coveragerc')
511 rc = os.path.join(TESTDIR, '.coveragerc')
512 vlog('# Installing coverage rc to %s' % rc)
512 vlog('# Installing coverage rc to %s' % rc)
513 os.environ['COVERAGE_PROCESS_START'] = rc
513 os.environ['COVERAGE_PROCESS_START'] = rc
514 fn = os.path.join(INST, '..', '.coverage')
514 fn = os.path.join(INST, '..', '.coverage')
515 os.environ['COVERAGE_FILE'] = fn
515 os.environ['COVERAGE_FILE'] = fn
516
516
517 def outputtimes(options):
517 def outputtimes(options):
518 vlog('# Producing time report')
518 vlog('# Producing time report')
519 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
519 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
520 cols = '%7.3f %s'
520 cols = '%7.3f %s'
521 print '\n%-7s %s' % ('Time', 'Test')
521 print '\n%-7s %s' % ('Time', 'Test')
522 for test, timetaken in times:
522 for test, timetaken in times:
523 print cols % (timetaken, test)
523 print cols % (timetaken, test)
524
524
525 def outputcoverage(options):
525 def outputcoverage(options):
526
526
527 vlog('# Producing coverage report')
527 vlog('# Producing coverage report')
528 os.chdir(PYTHONDIR)
528 os.chdir(PYTHONDIR)
529
529
530 def covrun(*args):
530 def covrun(*args):
531 cmd = 'coverage %s' % ' '.join(args)
531 cmd = 'coverage %s' % ' '.join(args)
532 vlog('# Running: %s' % cmd)
532 vlog('# Running: %s' % cmd)
533 os.system(cmd)
533 os.system(cmd)
534
534
535 covrun('-c')
535 covrun('-c')
536 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
536 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
537 covrun('-i', '-r', '"--omit=%s"' % omit) # report
537 covrun('-i', '-r', '"--omit=%s"' % omit) # report
538 if options.htmlcov:
538 if options.htmlcov:
539 htmldir = os.path.join(TESTDIR, 'htmlcov')
539 htmldir = os.path.join(TESTDIR, 'htmlcov')
540 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
540 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
541 if options.annotate:
541 if options.annotate:
542 adir = os.path.join(TESTDIR, 'annotated')
542 adir = os.path.join(TESTDIR, 'annotated')
543 if not os.path.isdir(adir):
543 if not os.path.isdir(adir):
544 os.mkdir(adir)
544 os.mkdir(adir)
545 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
545 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
546
546
547 class Test(object):
547 class Test(object):
548 """Encapsulates a single, runnable test."""
548 """Encapsulates a single, runnable test."""
549
549
550 def __init__(self, path, options, threadtmp, count):
550 def __init__(self, path, options, threadtmp, count):
551 self._path = path
551 self._path = path
552 self._options = options
552 self._options = options
553 self._threadtmp = threadtmp
553 self._threadtmp = threadtmp
554
554
555 self.testtmp = os.path.join(threadtmp, os.path.basename(path))
555 self.testtmp = os.path.join(threadtmp, os.path.basename(path))
556 os.mkdir(self.testtmp)
556 os.mkdir(self.testtmp)
557
557
558 self._setreplacements(count)
558 self._setreplacements(count)
559
559
560 def run(self, env):
560 def run(self, env):
561 createhgrc(env['HGRCPATH'], self._options)
561 createhgrc(env['HGRCPATH'], self._options)
562
562
563 try:
563 return self._run(self._replacements, env)
564 return self._run(self._replacements, env)
565 finally:
566 killdaemons(env['DAEMON_PIDS'])
564
567
565 def _run(self, replacements, env):
568 def _run(self, replacements, env):
566 raise NotImplemented('Subclasses must implement Test.run()')
569 raise NotImplemented('Subclasses must implement Test.run()')
567
570
568 def _setreplacements(self, count):
571 def _setreplacements(self, count):
569 port = self._options.port + count * 3
572 port = self._options.port + count * 3
570 r = [
573 r = [
571 (r':%s\b' % port, ':$HGPORT'),
574 (r':%s\b' % port, ':$HGPORT'),
572 (r':%s\b' % (port + 1), ':$HGPORT1'),
575 (r':%s\b' % (port + 1), ':$HGPORT1'),
573 (r':%s\b' % (port + 2), ':$HGPORT2'),
576 (r':%s\b' % (port + 2), ':$HGPORT2'),
574 ]
577 ]
575
578
576 if os.name == 'nt':
579 if os.name == 'nt':
577 r.append(
580 r.append(
578 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
581 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
579 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
582 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
580 for c in self.testtmp), '$TESTTMP'))
583 for c in self.testtmp), '$TESTTMP'))
581 else:
584 else:
582 r.append((re.escape(self.testtmp), '$TESTTMP'))
585 r.append((re.escape(self.testtmp), '$TESTTMP'))
583
586
584 self._replacements = r
587 self._replacements = r
585 self._port = port
588 self._port = port
586
589
587 def getenv(self):
590 def getenv(self):
588 env = os.environ.copy()
591 env = os.environ.copy()
589 env['TESTTMP'] = self.testtmp
592 env['TESTTMP'] = self.testtmp
590 env['HOME'] = self.testtmp
593 env['HOME'] = self.testtmp
591 env["HGPORT"] = str(self._port)
594 env["HGPORT"] = str(self._port)
592 env["HGPORT1"] = str(self._port + 1)
595 env["HGPORT1"] = str(self._port + 1)
593 env["HGPORT2"] = str(self._port + 2)
596 env["HGPORT2"] = str(self._port + 2)
594 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
597 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
595 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
598 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
596 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
599 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
597 env["HGMERGE"] = "internal:merge"
600 env["HGMERGE"] = "internal:merge"
598 env["HGUSER"] = "test"
601 env["HGUSER"] = "test"
599 env["HGENCODING"] = "ascii"
602 env["HGENCODING"] = "ascii"
600 env["HGENCODINGMODE"] = "strict"
603 env["HGENCODINGMODE"] = "strict"
601
604
602 # Reset some environment variables to well-known values so that
605 # Reset some environment variables to well-known values so that
603 # the tests produce repeatable output.
606 # the tests produce repeatable output.
604 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
607 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
605 env['TZ'] = 'GMT'
608 env['TZ'] = 'GMT'
606 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
609 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
607 env['COLUMNS'] = '80'
610 env['COLUMNS'] = '80'
608 env['TERM'] = 'xterm'
611 env['TERM'] = 'xterm'
609
612
610 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
613 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
611 'NO_PROXY').split():
614 'NO_PROXY').split():
612 if k in env:
615 if k in env:
613 del env[k]
616 del env[k]
614
617
615 # unset env related to hooks
618 # unset env related to hooks
616 for k in env.keys():
619 for k in env.keys():
617 if k.startswith('HG_'):
620 if k.startswith('HG_'):
618 del env[k]
621 del env[k]
619
622
620 return env
623 return env
621
624
622 def pytest(test, wd, options, replacements, env):
625 def pytest(test, wd, options, replacements, env):
623 py3kswitch = options.py3k_warnings and ' -3' or ''
626 py3kswitch = options.py3k_warnings and ' -3' or ''
624 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
627 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
625 vlog("# Running", cmd)
628 vlog("# Running", cmd)
626 if os.name == 'nt':
629 if os.name == 'nt':
627 replacements.append((r'\r\n', '\n'))
630 replacements.append((r'\r\n', '\n'))
628 return run(cmd, wd, options, replacements, env)
631 return run(cmd, wd, options, replacements, env)
629
632
630 class PythonTest(Test):
633 class PythonTest(Test):
631 """A Python-based test."""
634 """A Python-based test."""
632 def _run(self, replacements, env):
635 def _run(self, replacements, env):
633 return pytest(self._path, self.testtmp, self._options, replacements,
636 return pytest(self._path, self.testtmp, self._options, replacements,
634 env)
637 env)
635
638
636 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
639 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
637 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
640 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
638 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
641 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
639 escapemap.update({'\\': '\\\\', '\r': r'\r'})
642 escapemap.update({'\\': '\\\\', '\r': r'\r'})
640 def escapef(m):
643 def escapef(m):
641 return escapemap[m.group(0)]
644 return escapemap[m.group(0)]
642 def stringescape(s):
645 def stringescape(s):
643 return escapesub(escapef, s)
646 return escapesub(escapef, s)
644
647
645 def rematch(el, l):
648 def rematch(el, l):
646 try:
649 try:
647 # use \Z to ensure that the regex matches to the end of the string
650 # use \Z to ensure that the regex matches to the end of the string
648 if os.name == 'nt':
651 if os.name == 'nt':
649 return re.match(el + r'\r?\n\Z', l)
652 return re.match(el + r'\r?\n\Z', l)
650 return re.match(el + r'\n\Z', l)
653 return re.match(el + r'\n\Z', l)
651 except re.error:
654 except re.error:
652 # el is an invalid regex
655 # el is an invalid regex
653 return False
656 return False
654
657
655 def globmatch(el, l):
658 def globmatch(el, l):
656 # The only supported special characters are * and ? plus / which also
659 # The only supported special characters are * and ? plus / which also
657 # matches \ on windows. Escaping of these characters is supported.
660 # matches \ on windows. Escaping of these characters is supported.
658 if el + '\n' == l:
661 if el + '\n' == l:
659 if os.altsep:
662 if os.altsep:
660 # matching on "/" is not needed for this line
663 # matching on "/" is not needed for this line
661 return '-glob'
664 return '-glob'
662 return True
665 return True
663 i, n = 0, len(el)
666 i, n = 0, len(el)
664 res = ''
667 res = ''
665 while i < n:
668 while i < n:
666 c = el[i]
669 c = el[i]
667 i += 1
670 i += 1
668 if c == '\\' and el[i] in '*?\\/':
671 if c == '\\' and el[i] in '*?\\/':
669 res += el[i - 1:i + 1]
672 res += el[i - 1:i + 1]
670 i += 1
673 i += 1
671 elif c == '*':
674 elif c == '*':
672 res += '.*'
675 res += '.*'
673 elif c == '?':
676 elif c == '?':
674 res += '.'
677 res += '.'
675 elif c == '/' and os.altsep:
678 elif c == '/' and os.altsep:
676 res += '[/\\\\]'
679 res += '[/\\\\]'
677 else:
680 else:
678 res += re.escape(c)
681 res += re.escape(c)
679 return rematch(res, l)
682 return rematch(res, l)
680
683
681 def linematch(el, l):
684 def linematch(el, l):
682 if el == l: # perfect match (fast)
685 if el == l: # perfect match (fast)
683 return True
686 return True
684 if el:
687 if el:
685 if el.endswith(" (esc)\n"):
688 if el.endswith(" (esc)\n"):
686 el = el[:-7].decode('string-escape') + '\n'
689 el = el[:-7].decode('string-escape') + '\n'
687 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
690 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
688 return True
691 return True
689 if el.endswith(" (re)\n"):
692 if el.endswith(" (re)\n"):
690 return rematch(el[:-6], l)
693 return rematch(el[:-6], l)
691 if el.endswith(" (glob)\n"):
694 if el.endswith(" (glob)\n"):
692 return globmatch(el[:-8], l)
695 return globmatch(el[:-8], l)
693 if os.altsep and l.replace('\\', '/') == el:
696 if os.altsep and l.replace('\\', '/') == el:
694 return '+glob'
697 return '+glob'
695 return False
698 return False
696
699
697 def tsttest(test, wd, options, replacements, env):
700 def tsttest(test, wd, options, replacements, env):
698 # We generate a shell script which outputs unique markers to line
701 # We generate a shell script which outputs unique markers to line
699 # up script results with our source. These markers include input
702 # up script results with our source. These markers include input
700 # line number and the last return code
703 # line number and the last return code
701 salt = "SALT" + str(time.time())
704 salt = "SALT" + str(time.time())
702 def addsalt(line, inpython):
705 def addsalt(line, inpython):
703 if inpython:
706 if inpython:
704 script.append('%s %d 0\n' % (salt, line))
707 script.append('%s %d 0\n' % (salt, line))
705 else:
708 else:
706 script.append('echo %s %s $?\n' % (salt, line))
709 script.append('echo %s %s $?\n' % (salt, line))
707
710
708 # After we run the shell script, we re-unify the script output
711 # After we run the shell script, we re-unify the script output
709 # with non-active parts of the source, with synchronization by our
712 # with non-active parts of the source, with synchronization by our
710 # SALT line number markers. The after table contains the
713 # SALT line number markers. The after table contains the
711 # non-active components, ordered by line number
714 # non-active components, ordered by line number
712 after = {}
715 after = {}
713 pos = prepos = -1
716 pos = prepos = -1
714
717
715 # Expected shell script output
718 # Expected shell script output
716 expected = {}
719 expected = {}
717
720
718 # We keep track of whether or not we're in a Python block so we
721 # We keep track of whether or not we're in a Python block so we
719 # can generate the surrounding doctest magic
722 # can generate the surrounding doctest magic
720 inpython = False
723 inpython = False
721
724
722 # True or False when in a true or false conditional section
725 # True or False when in a true or false conditional section
723 skipping = None
726 skipping = None
724
727
725 def hghave(reqs):
728 def hghave(reqs):
726 # TODO: do something smarter when all other uses of hghave is gone
729 # TODO: do something smarter when all other uses of hghave is gone
727 tdir = TESTDIR.replace('\\', '/')
730 tdir = TESTDIR.replace('\\', '/')
728 proc = Popen4('%s -c "%s/hghave %s"' %
731 proc = Popen4('%s -c "%s/hghave %s"' %
729 (options.shell, tdir, ' '.join(reqs)), wd, 0)
732 (options.shell, tdir, ' '.join(reqs)), wd, 0)
730 stdout, stderr = proc.communicate()
733 stdout, stderr = proc.communicate()
731 ret = proc.wait()
734 ret = proc.wait()
732 if wifexited(ret):
735 if wifexited(ret):
733 ret = os.WEXITSTATUS(ret)
736 ret = os.WEXITSTATUS(ret)
734 if ret == 2:
737 if ret == 2:
735 print stdout
738 print stdout
736 sys.exit(1)
739 sys.exit(1)
737 return ret == 0
740 return ret == 0
738
741
739 f = open(test)
742 f = open(test)
740 t = f.readlines()
743 t = f.readlines()
741 f.close()
744 f.close()
742
745
743 script = []
746 script = []
744 if options.debug:
747 if options.debug:
745 script.append('set -x\n')
748 script.append('set -x\n')
746 if os.getenv('MSYSTEM'):
749 if os.getenv('MSYSTEM'):
747 script.append('alias pwd="pwd -W"\n')
750 script.append('alias pwd="pwd -W"\n')
748 n = 0
751 n = 0
749 for n, l in enumerate(t):
752 for n, l in enumerate(t):
750 if not l.endswith('\n'):
753 if not l.endswith('\n'):
751 l += '\n'
754 l += '\n'
752 if l.startswith('#if'):
755 if l.startswith('#if'):
753 lsplit = l.split()
756 lsplit = l.split()
754 if len(lsplit) < 2 or lsplit[0] != '#if':
757 if len(lsplit) < 2 or lsplit[0] != '#if':
755 after.setdefault(pos, []).append(' !!! invalid #if\n')
758 after.setdefault(pos, []).append(' !!! invalid #if\n')
756 if skipping is not None:
759 if skipping is not None:
757 after.setdefault(pos, []).append(' !!! nested #if\n')
760 after.setdefault(pos, []).append(' !!! nested #if\n')
758 skipping = not hghave(lsplit[1:])
761 skipping = not hghave(lsplit[1:])
759 after.setdefault(pos, []).append(l)
762 after.setdefault(pos, []).append(l)
760 elif l.startswith('#else'):
763 elif l.startswith('#else'):
761 if skipping is None:
764 if skipping is None:
762 after.setdefault(pos, []).append(' !!! missing #if\n')
765 after.setdefault(pos, []).append(' !!! missing #if\n')
763 skipping = not skipping
766 skipping = not skipping
764 after.setdefault(pos, []).append(l)
767 after.setdefault(pos, []).append(l)
765 elif l.startswith('#endif'):
768 elif l.startswith('#endif'):
766 if skipping is None:
769 if skipping is None:
767 after.setdefault(pos, []).append(' !!! missing #if\n')
770 after.setdefault(pos, []).append(' !!! missing #if\n')
768 skipping = None
771 skipping = None
769 after.setdefault(pos, []).append(l)
772 after.setdefault(pos, []).append(l)
770 elif skipping:
773 elif skipping:
771 after.setdefault(pos, []).append(l)
774 after.setdefault(pos, []).append(l)
772 elif l.startswith(' >>> '): # python inlines
775 elif l.startswith(' >>> '): # python inlines
773 after.setdefault(pos, []).append(l)
776 after.setdefault(pos, []).append(l)
774 prepos = pos
777 prepos = pos
775 pos = n
778 pos = n
776 if not inpython:
779 if not inpython:
777 # we've just entered a Python block, add the header
780 # we've just entered a Python block, add the header
778 inpython = True
781 inpython = True
779 addsalt(prepos, False) # make sure we report the exit code
782 addsalt(prepos, False) # make sure we report the exit code
780 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
783 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
781 addsalt(n, True)
784 addsalt(n, True)
782 script.append(l[2:])
785 script.append(l[2:])
783 elif l.startswith(' ... '): # python inlines
786 elif l.startswith(' ... '): # python inlines
784 after.setdefault(prepos, []).append(l)
787 after.setdefault(prepos, []).append(l)
785 script.append(l[2:])
788 script.append(l[2:])
786 elif l.startswith(' $ '): # commands
789 elif l.startswith(' $ '): # commands
787 if inpython:
790 if inpython:
788 script.append("EOF\n")
791 script.append("EOF\n")
789 inpython = False
792 inpython = False
790 after.setdefault(pos, []).append(l)
793 after.setdefault(pos, []).append(l)
791 prepos = pos
794 prepos = pos
792 pos = n
795 pos = n
793 addsalt(n, False)
796 addsalt(n, False)
794 cmd = l[4:].split()
797 cmd = l[4:].split()
795 if len(cmd) == 2 and cmd[0] == 'cd':
798 if len(cmd) == 2 and cmd[0] == 'cd':
796 l = ' $ cd %s || exit 1\n' % cmd[1]
799 l = ' $ cd %s || exit 1\n' % cmd[1]
797 script.append(l[4:])
800 script.append(l[4:])
798 elif l.startswith(' > '): # continuations
801 elif l.startswith(' > '): # continuations
799 after.setdefault(prepos, []).append(l)
802 after.setdefault(prepos, []).append(l)
800 script.append(l[4:])
803 script.append(l[4:])
801 elif l.startswith(' '): # results
804 elif l.startswith(' '): # results
802 # queue up a list of expected results
805 # queue up a list of expected results
803 expected.setdefault(pos, []).append(l[2:])
806 expected.setdefault(pos, []).append(l[2:])
804 else:
807 else:
805 if inpython:
808 if inpython:
806 script.append("EOF\n")
809 script.append("EOF\n")
807 inpython = False
810 inpython = False
808 # non-command/result - queue up for merged output
811 # non-command/result - queue up for merged output
809 after.setdefault(pos, []).append(l)
812 after.setdefault(pos, []).append(l)
810
813
811 if inpython:
814 if inpython:
812 script.append("EOF\n")
815 script.append("EOF\n")
813 if skipping is not None:
816 if skipping is not None:
814 after.setdefault(pos, []).append(' !!! missing #endif\n')
817 after.setdefault(pos, []).append(' !!! missing #endif\n')
815 addsalt(n + 1, False)
818 addsalt(n + 1, False)
816
819
817 # Write out the script and execute it
820 # Write out the script and execute it
818 name = wd + '.sh'
821 name = wd + '.sh'
819 f = open(name, 'w')
822 f = open(name, 'w')
820 for l in script:
823 for l in script:
821 f.write(l)
824 f.write(l)
822 f.close()
825 f.close()
823
826
824 cmd = '%s "%s"' % (options.shell, name)
827 cmd = '%s "%s"' % (options.shell, name)
825 vlog("# Running", cmd)
828 vlog("# Running", cmd)
826 exitcode, output = run(cmd, wd, options, replacements, env)
829 exitcode, output = run(cmd, wd, options, replacements, env)
827 # do not merge output if skipped, return hghave message instead
830 # do not merge output if skipped, return hghave message instead
828 # similarly, with --debug, output is None
831 # similarly, with --debug, output is None
829 if exitcode == SKIPPED_STATUS or output is None:
832 if exitcode == SKIPPED_STATUS or output is None:
830 return exitcode, output
833 return exitcode, output
831
834
832 # Merge the script output back into a unified test
835 # Merge the script output back into a unified test
833
836
834 warnonly = 1 # 1: not yet, 2: yes, 3: for sure not
837 warnonly = 1 # 1: not yet, 2: yes, 3: for sure not
835 if exitcode != 0: # failure has been reported
838 if exitcode != 0: # failure has been reported
836 warnonly = 3 # set to "for sure not"
839 warnonly = 3 # set to "for sure not"
837 pos = -1
840 pos = -1
838 postout = []
841 postout = []
839 for l in output:
842 for l in output:
840 lout, lcmd = l, None
843 lout, lcmd = l, None
841 if salt in l:
844 if salt in l:
842 lout, lcmd = l.split(salt, 1)
845 lout, lcmd = l.split(salt, 1)
843
846
844 if lout:
847 if lout:
845 if not lout.endswith('\n'):
848 if not lout.endswith('\n'):
846 lout += ' (no-eol)\n'
849 lout += ' (no-eol)\n'
847
850
848 # find the expected output at the current position
851 # find the expected output at the current position
849 el = None
852 el = None
850 if pos in expected and expected[pos]:
853 if pos in expected and expected[pos]:
851 el = expected[pos].pop(0)
854 el = expected[pos].pop(0)
852
855
853 r = linematch(el, lout)
856 r = linematch(el, lout)
854 if isinstance(r, str):
857 if isinstance(r, str):
855 if r == '+glob':
858 if r == '+glob':
856 lout = el[:-1] + ' (glob)\n'
859 lout = el[:-1] + ' (glob)\n'
857 r = '' # warn only this line
860 r = '' # warn only this line
858 elif r == '-glob':
861 elif r == '-glob':
859 lout = ''.join(el.rsplit(' (glob)', 1))
862 lout = ''.join(el.rsplit(' (glob)', 1))
860 r = '' # warn only this line
863 r = '' # warn only this line
861 else:
864 else:
862 log('\ninfo, unknown linematch result: %r\n' % r)
865 log('\ninfo, unknown linematch result: %r\n' % r)
863 r = False
866 r = False
864 if r:
867 if r:
865 postout.append(" " + el)
868 postout.append(" " + el)
866 else:
869 else:
867 if needescape(lout):
870 if needescape(lout):
868 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
871 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
869 postout.append(" " + lout) # let diff deal with it
872 postout.append(" " + lout) # let diff deal with it
870 if r != '': # if line failed
873 if r != '': # if line failed
871 warnonly = 3 # set to "for sure not"
874 warnonly = 3 # set to "for sure not"
872 elif warnonly == 1: # is "not yet" (and line is warn only)
875 elif warnonly == 1: # is "not yet" (and line is warn only)
873 warnonly = 2 # set to "yes" do warn
876 warnonly = 2 # set to "yes" do warn
874
877
875 if lcmd:
878 if lcmd:
876 # add on last return code
879 # add on last return code
877 ret = int(lcmd.split()[1])
880 ret = int(lcmd.split()[1])
878 if ret != 0:
881 if ret != 0:
879 postout.append(" [%s]\n" % ret)
882 postout.append(" [%s]\n" % ret)
880 if pos in after:
883 if pos in after:
881 # merge in non-active test bits
884 # merge in non-active test bits
882 postout += after.pop(pos)
885 postout += after.pop(pos)
883 pos = int(lcmd.split()[0])
886 pos = int(lcmd.split()[0])
884
887
885 if pos in after:
888 if pos in after:
886 postout += after.pop(pos)
889 postout += after.pop(pos)
887
890
888 if warnonly == 2:
891 if warnonly == 2:
889 exitcode = False # set exitcode to warned
892 exitcode = False # set exitcode to warned
890 return exitcode, postout
893 return exitcode, postout
891
894
892 class TTest(Test):
895 class TTest(Test):
893 """A "t test" is a test backed by a .t file."""
896 """A "t test" is a test backed by a .t file."""
894
897
895 def _run(self, replacements, env):
898 def _run(self, replacements, env):
896 return tsttest(self._path, self.testtmp, self._options, replacements,
899 return tsttest(self._path, self.testtmp, self._options, replacements,
897 env)
900 env)
898
901
899 wifexited = getattr(os, "WIFEXITED", lambda x: False)
902 wifexited = getattr(os, "WIFEXITED", lambda x: False)
900 def run(cmd, wd, options, replacements, env):
903 def run(cmd, wd, options, replacements, env):
901 """Run command in a sub-process, capturing the output (stdout and stderr).
904 """Run command in a sub-process, capturing the output (stdout and stderr).
902 Return a tuple (exitcode, output). output is None in debug mode."""
905 Return a tuple (exitcode, output). output is None in debug mode."""
903 # TODO: Use subprocess.Popen if we're running on Python 2.4
906 # TODO: Use subprocess.Popen if we're running on Python 2.4
904 if options.debug:
907 if options.debug:
905 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
908 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
906 ret = proc.wait()
909 ret = proc.wait()
907 return (ret, None)
910 return (ret, None)
908
911
909 proc = Popen4(cmd, wd, options.timeout, env)
912 proc = Popen4(cmd, wd, options.timeout, env)
910 def cleanup():
913 def cleanup():
911 terminate(proc)
914 terminate(proc)
912 ret = proc.wait()
915 ret = proc.wait()
913 if ret == 0:
916 if ret == 0:
914 ret = signal.SIGTERM << 8
917 ret = signal.SIGTERM << 8
915 killdaemons(env['DAEMON_PIDS'])
918 killdaemons(env['DAEMON_PIDS'])
916 return ret
919 return ret
917
920
918 output = ''
921 output = ''
919 proc.tochild.close()
922 proc.tochild.close()
920
923
921 try:
924 try:
922 output = proc.fromchild.read()
925 output = proc.fromchild.read()
923 except KeyboardInterrupt:
926 except KeyboardInterrupt:
924 vlog('# Handling keyboard interrupt')
927 vlog('# Handling keyboard interrupt')
925 cleanup()
928 cleanup()
926 raise
929 raise
927
930
928 ret = proc.wait()
931 ret = proc.wait()
929 if wifexited(ret):
932 if wifexited(ret):
930 ret = os.WEXITSTATUS(ret)
933 ret = os.WEXITSTATUS(ret)
931
934
932 if proc.timeout:
935 if proc.timeout:
933 ret = 'timeout'
936 ret = 'timeout'
934
937
935 if ret:
938 if ret:
936 killdaemons(env['DAEMON_PIDS'])
939 killdaemons(env['DAEMON_PIDS'])
937
940
938 if abort:
941 if abort:
939 raise KeyboardInterrupt()
942 raise KeyboardInterrupt()
940
943
941 for s, r in replacements:
944 for s, r in replacements:
942 output = re.sub(s, r, output)
945 output = re.sub(s, r, output)
943 return ret, output.splitlines(True)
946 return ret, output.splitlines(True)
944
947
945 def runone(options, test, count):
948 def runone(options, test, count):
946 '''returns a result element: (code, test, msg)'''
949 '''returns a result element: (code, test, msg)'''
947
950
948 def skip(msg):
951 def skip(msg):
949 if options.verbose:
952 if options.verbose:
950 log("\nSkipping %s: %s" % (testpath, msg))
953 log("\nSkipping %s: %s" % (testpath, msg))
951 return 's', test, msg
954 return 's', test, msg
952
955
953 def fail(msg, ret):
956 def fail(msg, ret):
954 warned = ret is False
957 warned = ret is False
955 if not options.nodiff:
958 if not options.nodiff:
956 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
959 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
957 if (not ret and options.interactive
960 if (not ret and options.interactive
958 and os.path.exists(testpath + ".err")):
961 and os.path.exists(testpath + ".err")):
959 iolock.acquire()
962 iolock.acquire()
960 print "Accept this change? [n] ",
963 print "Accept this change? [n] ",
961 answer = sys.stdin.readline().strip()
964 answer = sys.stdin.readline().strip()
962 iolock.release()
965 iolock.release()
963 if answer.lower() in "y yes".split():
966 if answer.lower() in "y yes".split():
964 if test.endswith(".t"):
967 if test.endswith(".t"):
965 rename(testpath + ".err", testpath)
968 rename(testpath + ".err", testpath)
966 else:
969 else:
967 rename(testpath + ".err", testpath + ".out")
970 rename(testpath + ".err", testpath + ".out")
968 return '.', test, ''
971 return '.', test, ''
969 return warned and '~' or '!', test, msg
972 return warned and '~' or '!', test, msg
970
973
971 def success():
974 def success():
972 return '.', test, ''
975 return '.', test, ''
973
976
974 def ignore(msg):
977 def ignore(msg):
975 return 'i', test, msg
978 return 'i', test, msg
976
979
977 def describe(ret):
980 def describe(ret):
978 if ret < 0:
981 if ret < 0:
979 return 'killed by signal %d' % -ret
982 return 'killed by signal %d' % -ret
980 return 'returned error code %d' % ret
983 return 'returned error code %d' % ret
981
984
982 testpath = os.path.join(TESTDIR, test)
985 testpath = os.path.join(TESTDIR, test)
983 err = os.path.join(TESTDIR, test + ".err")
986 err = os.path.join(TESTDIR, test + ".err")
984 lctest = test.lower()
987 lctest = test.lower()
985
988
986 if not os.path.exists(testpath):
989 if not os.path.exists(testpath):
987 return skip("doesn't exist")
990 return skip("doesn't exist")
988
991
989 if not (options.whitelisted and test in options.whitelisted):
992 if not (options.whitelisted and test in options.whitelisted):
990 if options.blacklist and test in options.blacklist:
993 if options.blacklist and test in options.blacklist:
991 return skip("blacklisted")
994 return skip("blacklisted")
992
995
993 if options.retest and not os.path.exists(test + ".err"):
996 if options.retest and not os.path.exists(test + ".err"):
994 return ignore("not retesting")
997 return ignore("not retesting")
995
998
996 if options.keywords:
999 if options.keywords:
997 fp = open(test)
1000 fp = open(test)
998 t = fp.read().lower() + test.lower()
1001 t = fp.read().lower() + test.lower()
999 fp.close()
1002 fp.close()
1000 for k in options.keywords.lower().split():
1003 for k in options.keywords.lower().split():
1001 if k in t:
1004 if k in t:
1002 break
1005 break
1003 else:
1006 else:
1004 return ignore("doesn't match keyword")
1007 return ignore("doesn't match keyword")
1005
1008
1006 if not os.path.basename(lctest).startswith("test-"):
1009 if not os.path.basename(lctest).startswith("test-"):
1007 return skip("not a test file")
1010 return skip("not a test file")
1008 for ext, cls, out in testtypes:
1011 for ext, cls, out in testtypes:
1009 if lctest.endswith(ext):
1012 if lctest.endswith(ext):
1010 runner = cls
1013 runner = cls
1011 ref = os.path.join(TESTDIR, test + out)
1014 ref = os.path.join(TESTDIR, test + out)
1012 break
1015 break
1013 else:
1016 else:
1014 return skip("unknown test type")
1017 return skip("unknown test type")
1015
1018
1016 vlog("# Test", test)
1019 vlog("# Test", test)
1017
1020
1018 if os.path.exists(err):
1021 if os.path.exists(err):
1019 os.remove(err) # Remove any previous output files
1022 os.remove(err) # Remove any previous output files
1020
1023
1021 # Make a tmp subdirectory to work in
1024 # Make a tmp subdirectory to work in
1022 threadtmp = os.path.join(HGTMP, "child%d" % count)
1025 threadtmp = os.path.join(HGTMP, "child%d" % count)
1023 os.mkdir(threadtmp)
1026 os.mkdir(threadtmp)
1024
1027
1025 t = runner(testpath, options, threadtmp, count)
1028 t = runner(testpath, options, threadtmp, count)
1026 env = t.getenv()
1029 env = t.getenv()
1027
1030
1028 starttime = time.time()
1031 starttime = time.time()
1029 try:
1032 try:
1030 ret, out = t.run(env)
1033 ret, out = t.run(env)
1031 except KeyboardInterrupt:
1034 except KeyboardInterrupt:
1032 endtime = time.time()
1035 endtime = time.time()
1033 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
1036 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
1034 raise
1037 raise
1035 endtime = time.time()
1038 endtime = time.time()
1036 times.append((test, endtime - starttime))
1039 times.append((test, endtime - starttime))
1037 vlog("# Ret was:", ret)
1040 vlog("# Ret was:", ret)
1038
1041
1039 killdaemons(env['DAEMON_PIDS'])
1040
1041 skipped = (ret == SKIPPED_STATUS)
1042 skipped = (ret == SKIPPED_STATUS)
1042
1043
1043 # If we're not in --debug mode and reference output file exists,
1044 # If we're not in --debug mode and reference output file exists,
1044 # check test output against it.
1045 # check test output against it.
1045 if options.debug:
1046 if options.debug:
1046 refout = None # to match "out is None"
1047 refout = None # to match "out is None"
1047 elif os.path.exists(ref):
1048 elif os.path.exists(ref):
1048 f = open(ref, "r")
1049 f = open(ref, "r")
1049 refout = f.read().splitlines(True)
1050 refout = f.read().splitlines(True)
1050 f.close()
1051 f.close()
1051 else:
1052 else:
1052 refout = []
1053 refout = []
1053
1054
1054 if (ret != 0 or out != refout) and not skipped and not options.debug:
1055 if (ret != 0 or out != refout) and not skipped and not options.debug:
1055 # Save errors to a file for diagnosis
1056 # Save errors to a file for diagnosis
1056 f = open(err, "wb")
1057 f = open(err, "wb")
1057 for line in out:
1058 for line in out:
1058 f.write(line)
1059 f.write(line)
1059 f.close()
1060 f.close()
1060
1061
1061 if skipped:
1062 if skipped:
1062 if out is None: # debug mode: nothing to parse
1063 if out is None: # debug mode: nothing to parse
1063 missing = ['unknown']
1064 missing = ['unknown']
1064 failed = None
1065 failed = None
1065 else:
1066 else:
1066 missing, failed = parsehghaveoutput(out)
1067 missing, failed = parsehghaveoutput(out)
1067 if not missing:
1068 if not missing:
1068 missing = ['irrelevant']
1069 missing = ['irrelevant']
1069 if failed:
1070 if failed:
1070 result = fail("hghave failed checking for %s" % failed[-1], ret)
1071 result = fail("hghave failed checking for %s" % failed[-1], ret)
1071 skipped = False
1072 skipped = False
1072 else:
1073 else:
1073 result = skip(missing[-1])
1074 result = skip(missing[-1])
1074 elif ret == 'timeout':
1075 elif ret == 'timeout':
1075 result = fail("timed out", ret)
1076 result = fail("timed out", ret)
1076 elif out != refout:
1077 elif out != refout:
1077 info = {}
1078 info = {}
1078 if not options.nodiff:
1079 if not options.nodiff:
1079 iolock.acquire()
1080 iolock.acquire()
1080 if options.view:
1081 if options.view:
1081 os.system("%s %s %s" % (options.view, ref, err))
1082 os.system("%s %s %s" % (options.view, ref, err))
1082 else:
1083 else:
1083 info = showdiff(refout, out, ref, err)
1084 info = showdiff(refout, out, ref, err)
1084 iolock.release()
1085 iolock.release()
1085 msg = ""
1086 msg = ""
1086 if info.get('servefail'): msg += "serve failed and "
1087 if info.get('servefail'): msg += "serve failed and "
1087 if ret:
1088 if ret:
1088 msg += "output changed and " + describe(ret)
1089 msg += "output changed and " + describe(ret)
1089 else:
1090 else:
1090 msg += "output changed"
1091 msg += "output changed"
1091 result = fail(msg, ret)
1092 result = fail(msg, ret)
1092 elif ret:
1093 elif ret:
1093 result = fail(describe(ret), ret)
1094 result = fail(describe(ret), ret)
1094 else:
1095 else:
1095 result = success()
1096 result = success()
1096
1097
1097 if not options.verbose:
1098 if not options.verbose:
1098 iolock.acquire()
1099 iolock.acquire()
1099 sys.stdout.write(result[0])
1100 sys.stdout.write(result[0])
1100 sys.stdout.flush()
1101 sys.stdout.flush()
1101 iolock.release()
1102 iolock.release()
1102
1103
1103 if not options.keep_tmpdir:
1104 if not options.keep_tmpdir:
1104 shutil.rmtree(threadtmp, True)
1105 shutil.rmtree(threadtmp, True)
1105 return result
1106 return result
1106
1107
1107 _hgpath = None
1108 _hgpath = None
1108
1109
1109 def _gethgpath():
1110 def _gethgpath():
1110 """Return the path to the mercurial package that is actually found by
1111 """Return the path to the mercurial package that is actually found by
1111 the current Python interpreter."""
1112 the current Python interpreter."""
1112 global _hgpath
1113 global _hgpath
1113 if _hgpath is not None:
1114 if _hgpath is not None:
1114 return _hgpath
1115 return _hgpath
1115
1116
1116 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1117 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1117 pipe = os.popen(cmd % PYTHON)
1118 pipe = os.popen(cmd % PYTHON)
1118 try:
1119 try:
1119 _hgpath = pipe.read().strip()
1120 _hgpath = pipe.read().strip()
1120 finally:
1121 finally:
1121 pipe.close()
1122 pipe.close()
1122 return _hgpath
1123 return _hgpath
1123
1124
1124 def _checkhglib(verb):
1125 def _checkhglib(verb):
1125 """Ensure that the 'mercurial' package imported by python is
1126 """Ensure that the 'mercurial' package imported by python is
1126 the one we expect it to be. If not, print a warning to stderr."""
1127 the one we expect it to be. If not, print a warning to stderr."""
1127 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1128 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1128 actualhg = _gethgpath()
1129 actualhg = _gethgpath()
1129 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1130 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1130 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1131 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1131 ' (expected %s)\n'
1132 ' (expected %s)\n'
1132 % (verb, actualhg, expecthg))
1133 % (verb, actualhg, expecthg))
1133
1134
1134 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1135 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1135 times = []
1136 times = []
1136 iolock = threading.Lock()
1137 iolock = threading.Lock()
1137 abort = False
1138 abort = False
1138
1139
1139 def scheduletests(options, tests):
1140 def scheduletests(options, tests):
1140 jobs = options.jobs
1141 jobs = options.jobs
1141 done = queue.Queue()
1142 done = queue.Queue()
1142 running = 0
1143 running = 0
1143 count = 0
1144 count = 0
1144 global abort
1145 global abort
1145
1146
1146 def job(test, count):
1147 def job(test, count):
1147 try:
1148 try:
1148 done.put(runone(options, test, count))
1149 done.put(runone(options, test, count))
1149 except KeyboardInterrupt:
1150 except KeyboardInterrupt:
1150 pass
1151 pass
1151 except: # re-raises
1152 except: # re-raises
1152 done.put(('!', test, 'run-test raised an error, see traceback'))
1153 done.put(('!', test, 'run-test raised an error, see traceback'))
1153 raise
1154 raise
1154
1155
1155 try:
1156 try:
1156 while tests or running:
1157 while tests or running:
1157 if not done.empty() or running == jobs or not tests:
1158 if not done.empty() or running == jobs or not tests:
1158 try:
1159 try:
1159 code, test, msg = done.get(True, 1)
1160 code, test, msg = done.get(True, 1)
1160 results[code].append((test, msg))
1161 results[code].append((test, msg))
1161 if options.first and code not in '.si':
1162 if options.first and code not in '.si':
1162 break
1163 break
1163 except queue.Empty:
1164 except queue.Empty:
1164 continue
1165 continue
1165 running -= 1
1166 running -= 1
1166 if tests and not running == jobs:
1167 if tests and not running == jobs:
1167 test = tests.pop(0)
1168 test = tests.pop(0)
1168 if options.loop:
1169 if options.loop:
1169 tests.append(test)
1170 tests.append(test)
1170 t = threading.Thread(target=job, name=test, args=(test, count))
1171 t = threading.Thread(target=job, name=test, args=(test, count))
1171 t.start()
1172 t.start()
1172 running += 1
1173 running += 1
1173 count += 1
1174 count += 1
1174 except KeyboardInterrupt:
1175 except KeyboardInterrupt:
1175 abort = True
1176 abort = True
1176
1177
1177 def runtests(options, tests):
1178 def runtests(options, tests):
1178 try:
1179 try:
1179 if INST:
1180 if INST:
1180 installhg(options)
1181 installhg(options)
1181 _checkhglib("Testing")
1182 _checkhglib("Testing")
1182 else:
1183 else:
1183 usecorrectpython()
1184 usecorrectpython()
1184
1185
1185 if options.restart:
1186 if options.restart:
1186 orig = list(tests)
1187 orig = list(tests)
1187 while tests:
1188 while tests:
1188 if os.path.exists(tests[0] + ".err"):
1189 if os.path.exists(tests[0] + ".err"):
1189 break
1190 break
1190 tests.pop(0)
1191 tests.pop(0)
1191 if not tests:
1192 if not tests:
1192 print "running all tests"
1193 print "running all tests"
1193 tests = orig
1194 tests = orig
1194
1195
1195 scheduletests(options, tests)
1196 scheduletests(options, tests)
1196
1197
1197 failed = len(results['!'])
1198 failed = len(results['!'])
1198 warned = len(results['~'])
1199 warned = len(results['~'])
1199 tested = len(results['.']) + failed + warned
1200 tested = len(results['.']) + failed + warned
1200 skipped = len(results['s'])
1201 skipped = len(results['s'])
1201 ignored = len(results['i'])
1202 ignored = len(results['i'])
1202
1203
1203 print
1204 print
1204 if not options.noskips:
1205 if not options.noskips:
1205 for s in results['s']:
1206 for s in results['s']:
1206 print "Skipped %s: %s" % s
1207 print "Skipped %s: %s" % s
1207 for s in results['~']:
1208 for s in results['~']:
1208 print "Warned %s: %s" % s
1209 print "Warned %s: %s" % s
1209 for s in results['!']:
1210 for s in results['!']:
1210 print "Failed %s: %s" % s
1211 print "Failed %s: %s" % s
1211 _checkhglib("Tested")
1212 _checkhglib("Tested")
1212 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1213 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1213 tested, skipped + ignored, warned, failed)
1214 tested, skipped + ignored, warned, failed)
1214 if results['!']:
1215 if results['!']:
1215 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1216 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1216 if options.time:
1217 if options.time:
1217 outputtimes(options)
1218 outputtimes(options)
1218
1219
1219 if options.anycoverage:
1220 if options.anycoverage:
1220 outputcoverage(options)
1221 outputcoverage(options)
1221 except KeyboardInterrupt:
1222 except KeyboardInterrupt:
1222 failed = True
1223 failed = True
1223 print "\ninterrupted!"
1224 print "\ninterrupted!"
1224
1225
1225 if failed:
1226 if failed:
1226 return 1
1227 return 1
1227 if warned:
1228 if warned:
1228 return 80
1229 return 80
1229
1230
1230 testtypes = [('.py', PythonTest, '.out'),
1231 testtypes = [('.py', PythonTest, '.out'),
1231 ('.t', TTest, '')]
1232 ('.t', TTest, '')]
1232
1233
1233 def main(args, parser=None):
1234 def main(args, parser=None):
1234 parser = parser or getparser()
1235 parser = parser or getparser()
1235 (options, args) = parseargs(args, parser)
1236 (options, args) = parseargs(args, parser)
1236 os.umask(022)
1237 os.umask(022)
1237
1238
1238 checktools()
1239 checktools()
1239
1240
1240 if not args:
1241 if not args:
1241 if options.changed:
1242 if options.changed:
1242 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1243 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1243 None, 0)
1244 None, 0)
1244 stdout, stderr = proc.communicate()
1245 stdout, stderr = proc.communicate()
1245 args = stdout.strip('\0').split('\0')
1246 args = stdout.strip('\0').split('\0')
1246 else:
1247 else:
1247 args = os.listdir(".")
1248 args = os.listdir(".")
1248
1249
1249 tests = [t for t in args
1250 tests = [t for t in args
1250 if os.path.basename(t).startswith("test-")
1251 if os.path.basename(t).startswith("test-")
1251 and (t.endswith(".py") or t.endswith(".t"))]
1252 and (t.endswith(".py") or t.endswith(".t"))]
1252
1253
1253 if options.random:
1254 if options.random:
1254 random.shuffle(tests)
1255 random.shuffle(tests)
1255 else:
1256 else:
1256 # keywords for slow tests
1257 # keywords for slow tests
1257 slow = 'svn gendoc check-code-hg'.split()
1258 slow = 'svn gendoc check-code-hg'.split()
1258 def sortkey(f):
1259 def sortkey(f):
1259 # run largest tests first, as they tend to take the longest
1260 # run largest tests first, as they tend to take the longest
1260 try:
1261 try:
1261 val = -os.stat(f).st_size
1262 val = -os.stat(f).st_size
1262 except OSError, e:
1263 except OSError, e:
1263 if e.errno != errno.ENOENT:
1264 if e.errno != errno.ENOENT:
1264 raise
1265 raise
1265 return -1e9 # file does not exist, tell early
1266 return -1e9 # file does not exist, tell early
1266 for kw in slow:
1267 for kw in slow:
1267 if kw in f:
1268 if kw in f:
1268 val *= 10
1269 val *= 10
1269 return val
1270 return val
1270 tests.sort(key=sortkey)
1271 tests.sort(key=sortkey)
1271
1272
1272 if 'PYTHONHASHSEED' not in os.environ:
1273 if 'PYTHONHASHSEED' not in os.environ:
1273 # use a random python hash seed all the time
1274 # use a random python hash seed all the time
1274 # we do the randomness ourself to know what seed is used
1275 # we do the randomness ourself to know what seed is used
1275 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1276 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1276
1277
1277 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1278 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1278 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1279 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1279 if options.tmpdir:
1280 if options.tmpdir:
1280 options.keep_tmpdir = True
1281 options.keep_tmpdir = True
1281 tmpdir = options.tmpdir
1282 tmpdir = options.tmpdir
1282 if os.path.exists(tmpdir):
1283 if os.path.exists(tmpdir):
1283 # Meaning of tmpdir has changed since 1.3: we used to create
1284 # Meaning of tmpdir has changed since 1.3: we used to create
1284 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1285 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1285 # tmpdir already exists.
1286 # tmpdir already exists.
1286 print "error: temp dir %r already exists" % tmpdir
1287 print "error: temp dir %r already exists" % tmpdir
1287 return 1
1288 return 1
1288
1289
1289 # Automatically removing tmpdir sounds convenient, but could
1290 # Automatically removing tmpdir sounds convenient, but could
1290 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1291 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1291 # or "--tmpdir=$HOME".
1292 # or "--tmpdir=$HOME".
1292 #vlog("# Removing temp dir", tmpdir)
1293 #vlog("# Removing temp dir", tmpdir)
1293 #shutil.rmtree(tmpdir)
1294 #shutil.rmtree(tmpdir)
1294 os.makedirs(tmpdir)
1295 os.makedirs(tmpdir)
1295 else:
1296 else:
1296 d = None
1297 d = None
1297 if os.name == 'nt':
1298 if os.name == 'nt':
1298 # without this, we get the default temp dir location, but
1299 # without this, we get the default temp dir location, but
1299 # in all lowercase, which causes troubles with paths (issue3490)
1300 # in all lowercase, which causes troubles with paths (issue3490)
1300 d = os.getenv('TMP')
1301 d = os.getenv('TMP')
1301 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1302 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1302 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1303 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1303
1304
1304 if options.with_hg:
1305 if options.with_hg:
1305 INST = None
1306 INST = None
1306 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1307 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1307 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1308 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1308 os.makedirs(TMPBINDIR)
1309 os.makedirs(TMPBINDIR)
1309
1310
1310 # This looks redundant with how Python initializes sys.path from
1311 # This looks redundant with how Python initializes sys.path from
1311 # the location of the script being executed. Needed because the
1312 # the location of the script being executed. Needed because the
1312 # "hg" specified by --with-hg is not the only Python script
1313 # "hg" specified by --with-hg is not the only Python script
1313 # executed in the test suite that needs to import 'mercurial'
1314 # executed in the test suite that needs to import 'mercurial'
1314 # ... which means it's not really redundant at all.
1315 # ... which means it's not really redundant at all.
1315 PYTHONDIR = BINDIR
1316 PYTHONDIR = BINDIR
1316 else:
1317 else:
1317 INST = os.path.join(HGTMP, "install")
1318 INST = os.path.join(HGTMP, "install")
1318 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1319 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1319 TMPBINDIR = BINDIR
1320 TMPBINDIR = BINDIR
1320 PYTHONDIR = os.path.join(INST, "lib", "python")
1321 PYTHONDIR = os.path.join(INST, "lib", "python")
1321
1322
1322 os.environ["BINDIR"] = BINDIR
1323 os.environ["BINDIR"] = BINDIR
1323 os.environ["PYTHON"] = PYTHON
1324 os.environ["PYTHON"] = PYTHON
1324
1325
1325 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1326 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1326 if TMPBINDIR != BINDIR:
1327 if TMPBINDIR != BINDIR:
1327 path = [TMPBINDIR] + path
1328 path = [TMPBINDIR] + path
1328 os.environ["PATH"] = os.pathsep.join(path)
1329 os.environ["PATH"] = os.pathsep.join(path)
1329
1330
1330 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1331 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1331 # can run .../tests/run-tests.py test-foo where test-foo
1332 # can run .../tests/run-tests.py test-foo where test-foo
1332 # adds an extension to HGRC. Also include run-test.py directory to import
1333 # adds an extension to HGRC. Also include run-test.py directory to import
1333 # modules like heredoctest.
1334 # modules like heredoctest.
1334 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1335 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1335 # We have to augment PYTHONPATH, rather than simply replacing
1336 # We have to augment PYTHONPATH, rather than simply replacing
1336 # it, in case external libraries are only available via current
1337 # it, in case external libraries are only available via current
1337 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1338 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1338 # are in /opt/subversion.)
1339 # are in /opt/subversion.)
1339 oldpypath = os.environ.get(IMPL_PATH)
1340 oldpypath = os.environ.get(IMPL_PATH)
1340 if oldpypath:
1341 if oldpypath:
1341 pypath.append(oldpypath)
1342 pypath.append(oldpypath)
1342 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1343 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1343
1344
1344 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1345 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1345
1346
1346 vlog("# Using TESTDIR", TESTDIR)
1347 vlog("# Using TESTDIR", TESTDIR)
1347 vlog("# Using HGTMP", HGTMP)
1348 vlog("# Using HGTMP", HGTMP)
1348 vlog("# Using PATH", os.environ["PATH"])
1349 vlog("# Using PATH", os.environ["PATH"])
1349 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1350 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1350
1351
1351 try:
1352 try:
1352 return runtests(options, tests) or 0
1353 return runtests(options, tests) or 0
1353 finally:
1354 finally:
1354 time.sleep(.1)
1355 time.sleep(.1)
1355 cleanup(options)
1356 cleanup(options)
1356
1357
1357 if __name__ == '__main__':
1358 if __name__ == '__main__':
1358 sys.exit(main(sys.argv[1:]))
1359 sys.exit(main(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now