##// END OF EJS Templates
run-tests: clean up temp directory variables...
Gregory Szorc -
r21304:e626a67d default
parent child Browse files
Show More
@@ -1,1359 +1,1357 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, count):
551 self._path = path
551 self._path = path
552 self._options = options
552 self._options = options
553 self._threadtmp = threadtmp
554
553
555 self.testtmp = os.path.join(threadtmp, os.path.basename(path))
554 self.threadtmp = os.path.join(HGTMP, 'child%d' % count)
556 os.mkdir(self.testtmp)
555 os.mkdir(self.threadtmp)
556
557 self._testtmp = os.path.join(self.threadtmp, os.path.basename(path))
558 os.mkdir(self._testtmp)
557
559
558 self._setreplacements(count)
560 self._setreplacements(count)
559
561
560 def run(self):
562 def run(self):
561 env = self._getenv()
563 env = self._getenv()
562 createhgrc(env['HGRCPATH'], self._options)
564 createhgrc(env['HGRCPATH'], self._options)
563
565
564 try:
566 try:
565 return self._run(self._replacements, env)
567 return self._run(self._replacements, env)
566 finally:
568 finally:
567 killdaemons(env['DAEMON_PIDS'])
569 killdaemons(env['DAEMON_PIDS'])
568
570
569 def _run(self, replacements, env):
571 def _run(self, replacements, env):
570 raise NotImplemented('Subclasses must implement Test.run()')
572 raise NotImplemented('Subclasses must implement Test.run()')
571
573
572 def _setreplacements(self, count):
574 def _setreplacements(self, count):
573 port = self._options.port + count * 3
575 port = self._options.port + count * 3
574 r = [
576 r = [
575 (r':%s\b' % port, ':$HGPORT'),
577 (r':%s\b' % port, ':$HGPORT'),
576 (r':%s\b' % (port + 1), ':$HGPORT1'),
578 (r':%s\b' % (port + 1), ':$HGPORT1'),
577 (r':%s\b' % (port + 2), ':$HGPORT2'),
579 (r':%s\b' % (port + 2), ':$HGPORT2'),
578 ]
580 ]
579
581
580 if os.name == 'nt':
582 if os.name == 'nt':
581 r.append(
583 r.append(
582 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
584 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
583 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
585 c in '/\\' and r'[/\\]' or c.isdigit() and c or '\\' + c
584 for c in self.testtmp), '$TESTTMP'))
586 for c in self._testtmp), '$TESTTMP'))
585 else:
587 else:
586 r.append((re.escape(self.testtmp), '$TESTTMP'))
588 r.append((re.escape(self._testtmp), '$TESTTMP'))
587
589
588 self._replacements = r
590 self._replacements = r
589 self._port = port
591 self._port = port
590
592
591 def _getenv(self):
593 def _getenv(self):
592 env = os.environ.copy()
594 env = os.environ.copy()
593 env['TESTTMP'] = self.testtmp
595 env['TESTTMP'] = self._testtmp
594 env['HOME'] = self.testtmp
596 env['HOME'] = self._testtmp
595 env["HGPORT"] = str(self._port)
597 env["HGPORT"] = str(self._port)
596 env["HGPORT1"] = str(self._port + 1)
598 env["HGPORT1"] = str(self._port + 1)
597 env["HGPORT2"] = str(self._port + 2)
599 env["HGPORT2"] = str(self._port + 2)
598 env["HGRCPATH"] = os.path.join(self._threadtmp, '.hgrc')
600 env["HGRCPATH"] = os.path.join(self.threadtmp, '.hgrc')
599 env["DAEMON_PIDS"] = os.path.join(self._threadtmp, 'daemon.pids')
601 env["DAEMON_PIDS"] = os.path.join(self.threadtmp, 'daemon.pids')
600 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
602 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
601 env["HGMERGE"] = "internal:merge"
603 env["HGMERGE"] = "internal:merge"
602 env["HGUSER"] = "test"
604 env["HGUSER"] = "test"
603 env["HGENCODING"] = "ascii"
605 env["HGENCODING"] = "ascii"
604 env["HGENCODINGMODE"] = "strict"
606 env["HGENCODINGMODE"] = "strict"
605
607
606 # Reset some environment variables to well-known values so that
608 # Reset some environment variables to well-known values so that
607 # the tests produce repeatable output.
609 # the tests produce repeatable output.
608 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
610 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
609 env['TZ'] = 'GMT'
611 env['TZ'] = 'GMT'
610 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
612 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
611 env['COLUMNS'] = '80'
613 env['COLUMNS'] = '80'
612 env['TERM'] = 'xterm'
614 env['TERM'] = 'xterm'
613
615
614 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
616 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
615 'NO_PROXY').split():
617 'NO_PROXY').split():
616 if k in env:
618 if k in env:
617 del env[k]
619 del env[k]
618
620
619 # unset env related to hooks
621 # unset env related to hooks
620 for k in env.keys():
622 for k in env.keys():
621 if k.startswith('HG_'):
623 if k.startswith('HG_'):
622 del env[k]
624 del env[k]
623
625
624 return env
626 return env
625
627
626 def pytest(test, wd, options, replacements, env):
628 def pytest(test, wd, options, replacements, env):
627 py3kswitch = options.py3k_warnings and ' -3' or ''
629 py3kswitch = options.py3k_warnings and ' -3' or ''
628 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
630 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
629 vlog("# Running", cmd)
631 vlog("# Running", cmd)
630 if os.name == 'nt':
632 if os.name == 'nt':
631 replacements.append((r'\r\n', '\n'))
633 replacements.append((r'\r\n', '\n'))
632 return run(cmd, wd, options, replacements, env)
634 return run(cmd, wd, options, replacements, env)
633
635
634 class PythonTest(Test):
636 class PythonTest(Test):
635 """A Python-based test."""
637 """A Python-based test."""
636 def _run(self, replacements, env):
638 def _run(self, replacements, env):
637 return pytest(self._path, self.testtmp, self._options, replacements,
639 return pytest(self._path, self._testtmp, self._options, replacements,
638 env)
640 env)
639
641
640 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
642 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
641 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
643 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
642 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
644 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
643 escapemap.update({'\\': '\\\\', '\r': r'\r'})
645 escapemap.update({'\\': '\\\\', '\r': r'\r'})
644 def escapef(m):
646 def escapef(m):
645 return escapemap[m.group(0)]
647 return escapemap[m.group(0)]
646 def stringescape(s):
648 def stringescape(s):
647 return escapesub(escapef, s)
649 return escapesub(escapef, s)
648
650
649 def rematch(el, l):
651 def rematch(el, l):
650 try:
652 try:
651 # use \Z to ensure that the regex matches to the end of the string
653 # use \Z to ensure that the regex matches to the end of the string
652 if os.name == 'nt':
654 if os.name == 'nt':
653 return re.match(el + r'\r?\n\Z', l)
655 return re.match(el + r'\r?\n\Z', l)
654 return re.match(el + r'\n\Z', l)
656 return re.match(el + r'\n\Z', l)
655 except re.error:
657 except re.error:
656 # el is an invalid regex
658 # el is an invalid regex
657 return False
659 return False
658
660
659 def globmatch(el, l):
661 def globmatch(el, l):
660 # The only supported special characters are * and ? plus / which also
662 # The only supported special characters are * and ? plus / which also
661 # matches \ on windows. Escaping of these characters is supported.
663 # matches \ on windows. Escaping of these characters is supported.
662 if el + '\n' == l:
664 if el + '\n' == l:
663 if os.altsep:
665 if os.altsep:
664 # matching on "/" is not needed for this line
666 # matching on "/" is not needed for this line
665 return '-glob'
667 return '-glob'
666 return True
668 return True
667 i, n = 0, len(el)
669 i, n = 0, len(el)
668 res = ''
670 res = ''
669 while i < n:
671 while i < n:
670 c = el[i]
672 c = el[i]
671 i += 1
673 i += 1
672 if c == '\\' and el[i] in '*?\\/':
674 if c == '\\' and el[i] in '*?\\/':
673 res += el[i - 1:i + 1]
675 res += el[i - 1:i + 1]
674 i += 1
676 i += 1
675 elif c == '*':
677 elif c == '*':
676 res += '.*'
678 res += '.*'
677 elif c == '?':
679 elif c == '?':
678 res += '.'
680 res += '.'
679 elif c == '/' and os.altsep:
681 elif c == '/' and os.altsep:
680 res += '[/\\\\]'
682 res += '[/\\\\]'
681 else:
683 else:
682 res += re.escape(c)
684 res += re.escape(c)
683 return rematch(res, l)
685 return rematch(res, l)
684
686
685 def linematch(el, l):
687 def linematch(el, l):
686 if el == l: # perfect match (fast)
688 if el == l: # perfect match (fast)
687 return True
689 return True
688 if el:
690 if el:
689 if el.endswith(" (esc)\n"):
691 if el.endswith(" (esc)\n"):
690 el = el[:-7].decode('string-escape') + '\n'
692 el = el[:-7].decode('string-escape') + '\n'
691 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
693 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
692 return True
694 return True
693 if el.endswith(" (re)\n"):
695 if el.endswith(" (re)\n"):
694 return rematch(el[:-6], l)
696 return rematch(el[:-6], l)
695 if el.endswith(" (glob)\n"):
697 if el.endswith(" (glob)\n"):
696 return globmatch(el[:-8], l)
698 return globmatch(el[:-8], l)
697 if os.altsep and l.replace('\\', '/') == el:
699 if os.altsep and l.replace('\\', '/') == el:
698 return '+glob'
700 return '+glob'
699 return False
701 return False
700
702
701 def tsttest(test, wd, options, replacements, env):
703 def tsttest(test, wd, options, replacements, env):
702 # We generate a shell script which outputs unique markers to line
704 # We generate a shell script which outputs unique markers to line
703 # up script results with our source. These markers include input
705 # up script results with our source. These markers include input
704 # line number and the last return code
706 # line number and the last return code
705 salt = "SALT" + str(time.time())
707 salt = "SALT" + str(time.time())
706 def addsalt(line, inpython):
708 def addsalt(line, inpython):
707 if inpython:
709 if inpython:
708 script.append('%s %d 0\n' % (salt, line))
710 script.append('%s %d 0\n' % (salt, line))
709 else:
711 else:
710 script.append('echo %s %s $?\n' % (salt, line))
712 script.append('echo %s %s $?\n' % (salt, line))
711
713
712 # After we run the shell script, we re-unify the script output
714 # After we run the shell script, we re-unify the script output
713 # with non-active parts of the source, with synchronization by our
715 # with non-active parts of the source, with synchronization by our
714 # SALT line number markers. The after table contains the
716 # SALT line number markers. The after table contains the
715 # non-active components, ordered by line number
717 # non-active components, ordered by line number
716 after = {}
718 after = {}
717 pos = prepos = -1
719 pos = prepos = -1
718
720
719 # Expected shell script output
721 # Expected shell script output
720 expected = {}
722 expected = {}
721
723
722 # We keep track of whether or not we're in a Python block so we
724 # We keep track of whether or not we're in a Python block so we
723 # can generate the surrounding doctest magic
725 # can generate the surrounding doctest magic
724 inpython = False
726 inpython = False
725
727
726 # True or False when in a true or false conditional section
728 # True or False when in a true or false conditional section
727 skipping = None
729 skipping = None
728
730
729 def hghave(reqs):
731 def hghave(reqs):
730 # TODO: do something smarter when all other uses of hghave is gone
732 # TODO: do something smarter when all other uses of hghave is gone
731 tdir = TESTDIR.replace('\\', '/')
733 tdir = TESTDIR.replace('\\', '/')
732 proc = Popen4('%s -c "%s/hghave %s"' %
734 proc = Popen4('%s -c "%s/hghave %s"' %
733 (options.shell, tdir, ' '.join(reqs)), wd, 0)
735 (options.shell, tdir, ' '.join(reqs)), wd, 0)
734 stdout, stderr = proc.communicate()
736 stdout, stderr = proc.communicate()
735 ret = proc.wait()
737 ret = proc.wait()
736 if wifexited(ret):
738 if wifexited(ret):
737 ret = os.WEXITSTATUS(ret)
739 ret = os.WEXITSTATUS(ret)
738 if ret == 2:
740 if ret == 2:
739 print stdout
741 print stdout
740 sys.exit(1)
742 sys.exit(1)
741 return ret == 0
743 return ret == 0
742
744
743 f = open(test)
745 f = open(test)
744 t = f.readlines()
746 t = f.readlines()
745 f.close()
747 f.close()
746
748
747 script = []
749 script = []
748 if options.debug:
750 if options.debug:
749 script.append('set -x\n')
751 script.append('set -x\n')
750 if os.getenv('MSYSTEM'):
752 if os.getenv('MSYSTEM'):
751 script.append('alias pwd="pwd -W"\n')
753 script.append('alias pwd="pwd -W"\n')
752 n = 0
754 n = 0
753 for n, l in enumerate(t):
755 for n, l in enumerate(t):
754 if not l.endswith('\n'):
756 if not l.endswith('\n'):
755 l += '\n'
757 l += '\n'
756 if l.startswith('#if'):
758 if l.startswith('#if'):
757 lsplit = l.split()
759 lsplit = l.split()
758 if len(lsplit) < 2 or lsplit[0] != '#if':
760 if len(lsplit) < 2 or lsplit[0] != '#if':
759 after.setdefault(pos, []).append(' !!! invalid #if\n')
761 after.setdefault(pos, []).append(' !!! invalid #if\n')
760 if skipping is not None:
762 if skipping is not None:
761 after.setdefault(pos, []).append(' !!! nested #if\n')
763 after.setdefault(pos, []).append(' !!! nested #if\n')
762 skipping = not hghave(lsplit[1:])
764 skipping = not hghave(lsplit[1:])
763 after.setdefault(pos, []).append(l)
765 after.setdefault(pos, []).append(l)
764 elif l.startswith('#else'):
766 elif l.startswith('#else'):
765 if skipping is None:
767 if skipping is None:
766 after.setdefault(pos, []).append(' !!! missing #if\n')
768 after.setdefault(pos, []).append(' !!! missing #if\n')
767 skipping = not skipping
769 skipping = not skipping
768 after.setdefault(pos, []).append(l)
770 after.setdefault(pos, []).append(l)
769 elif l.startswith('#endif'):
771 elif l.startswith('#endif'):
770 if skipping is None:
772 if skipping is None:
771 after.setdefault(pos, []).append(' !!! missing #if\n')
773 after.setdefault(pos, []).append(' !!! missing #if\n')
772 skipping = None
774 skipping = None
773 after.setdefault(pos, []).append(l)
775 after.setdefault(pos, []).append(l)
774 elif skipping:
776 elif skipping:
775 after.setdefault(pos, []).append(l)
777 after.setdefault(pos, []).append(l)
776 elif l.startswith(' >>> '): # python inlines
778 elif l.startswith(' >>> '): # python inlines
777 after.setdefault(pos, []).append(l)
779 after.setdefault(pos, []).append(l)
778 prepos = pos
780 prepos = pos
779 pos = n
781 pos = n
780 if not inpython:
782 if not inpython:
781 # we've just entered a Python block, add the header
783 # we've just entered a Python block, add the header
782 inpython = True
784 inpython = True
783 addsalt(prepos, False) # make sure we report the exit code
785 addsalt(prepos, False) # make sure we report the exit code
784 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
786 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
785 addsalt(n, True)
787 addsalt(n, True)
786 script.append(l[2:])
788 script.append(l[2:])
787 elif l.startswith(' ... '): # python inlines
789 elif l.startswith(' ... '): # python inlines
788 after.setdefault(prepos, []).append(l)
790 after.setdefault(prepos, []).append(l)
789 script.append(l[2:])
791 script.append(l[2:])
790 elif l.startswith(' $ '): # commands
792 elif l.startswith(' $ '): # commands
791 if inpython:
793 if inpython:
792 script.append("EOF\n")
794 script.append("EOF\n")
793 inpython = False
795 inpython = False
794 after.setdefault(pos, []).append(l)
796 after.setdefault(pos, []).append(l)
795 prepos = pos
797 prepos = pos
796 pos = n
798 pos = n
797 addsalt(n, False)
799 addsalt(n, False)
798 cmd = l[4:].split()
800 cmd = l[4:].split()
799 if len(cmd) == 2 and cmd[0] == 'cd':
801 if len(cmd) == 2 and cmd[0] == 'cd':
800 l = ' $ cd %s || exit 1\n' % cmd[1]
802 l = ' $ cd %s || exit 1\n' % cmd[1]
801 script.append(l[4:])
803 script.append(l[4:])
802 elif l.startswith(' > '): # continuations
804 elif l.startswith(' > '): # continuations
803 after.setdefault(prepos, []).append(l)
805 after.setdefault(prepos, []).append(l)
804 script.append(l[4:])
806 script.append(l[4:])
805 elif l.startswith(' '): # results
807 elif l.startswith(' '): # results
806 # queue up a list of expected results
808 # queue up a list of expected results
807 expected.setdefault(pos, []).append(l[2:])
809 expected.setdefault(pos, []).append(l[2:])
808 else:
810 else:
809 if inpython:
811 if inpython:
810 script.append("EOF\n")
812 script.append("EOF\n")
811 inpython = False
813 inpython = False
812 # non-command/result - queue up for merged output
814 # non-command/result - queue up for merged output
813 after.setdefault(pos, []).append(l)
815 after.setdefault(pos, []).append(l)
814
816
815 if inpython:
817 if inpython:
816 script.append("EOF\n")
818 script.append("EOF\n")
817 if skipping is not None:
819 if skipping is not None:
818 after.setdefault(pos, []).append(' !!! missing #endif\n')
820 after.setdefault(pos, []).append(' !!! missing #endif\n')
819 addsalt(n + 1, False)
821 addsalt(n + 1, False)
820
822
821 # Write out the script and execute it
823 # Write out the script and execute it
822 name = wd + '.sh'
824 name = wd + '.sh'
823 f = open(name, 'w')
825 f = open(name, 'w')
824 for l in script:
826 for l in script:
825 f.write(l)
827 f.write(l)
826 f.close()
828 f.close()
827
829
828 cmd = '%s "%s"' % (options.shell, name)
830 cmd = '%s "%s"' % (options.shell, name)
829 vlog("# Running", cmd)
831 vlog("# Running", cmd)
830 exitcode, output = run(cmd, wd, options, replacements, env)
832 exitcode, output = run(cmd, wd, options, replacements, env)
831 # do not merge output if skipped, return hghave message instead
833 # do not merge output if skipped, return hghave message instead
832 # similarly, with --debug, output is None
834 # similarly, with --debug, output is None
833 if exitcode == SKIPPED_STATUS or output is None:
835 if exitcode == SKIPPED_STATUS or output is None:
834 return exitcode, output
836 return exitcode, output
835
837
836 # Merge the script output back into a unified test
838 # Merge the script output back into a unified test
837
839
838 warnonly = 1 # 1: not yet, 2: yes, 3: for sure not
840 warnonly = 1 # 1: not yet, 2: yes, 3: for sure not
839 if exitcode != 0: # failure has been reported
841 if exitcode != 0: # failure has been reported
840 warnonly = 3 # set to "for sure not"
842 warnonly = 3 # set to "for sure not"
841 pos = -1
843 pos = -1
842 postout = []
844 postout = []
843 for l in output:
845 for l in output:
844 lout, lcmd = l, None
846 lout, lcmd = l, None
845 if salt in l:
847 if salt in l:
846 lout, lcmd = l.split(salt, 1)
848 lout, lcmd = l.split(salt, 1)
847
849
848 if lout:
850 if lout:
849 if not lout.endswith('\n'):
851 if not lout.endswith('\n'):
850 lout += ' (no-eol)\n'
852 lout += ' (no-eol)\n'
851
853
852 # find the expected output at the current position
854 # find the expected output at the current position
853 el = None
855 el = None
854 if pos in expected and expected[pos]:
856 if pos in expected and expected[pos]:
855 el = expected[pos].pop(0)
857 el = expected[pos].pop(0)
856
858
857 r = linematch(el, lout)
859 r = linematch(el, lout)
858 if isinstance(r, str):
860 if isinstance(r, str):
859 if r == '+glob':
861 if r == '+glob':
860 lout = el[:-1] + ' (glob)\n'
862 lout = el[:-1] + ' (glob)\n'
861 r = '' # warn only this line
863 r = '' # warn only this line
862 elif r == '-glob':
864 elif r == '-glob':
863 lout = ''.join(el.rsplit(' (glob)', 1))
865 lout = ''.join(el.rsplit(' (glob)', 1))
864 r = '' # warn only this line
866 r = '' # warn only this line
865 else:
867 else:
866 log('\ninfo, unknown linematch result: %r\n' % r)
868 log('\ninfo, unknown linematch result: %r\n' % r)
867 r = False
869 r = False
868 if r:
870 if r:
869 postout.append(" " + el)
871 postout.append(" " + el)
870 else:
872 else:
871 if needescape(lout):
873 if needescape(lout):
872 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
874 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
873 postout.append(" " + lout) # let diff deal with it
875 postout.append(" " + lout) # let diff deal with it
874 if r != '': # if line failed
876 if r != '': # if line failed
875 warnonly = 3 # set to "for sure not"
877 warnonly = 3 # set to "for sure not"
876 elif warnonly == 1: # is "not yet" (and line is warn only)
878 elif warnonly == 1: # is "not yet" (and line is warn only)
877 warnonly = 2 # set to "yes" do warn
879 warnonly = 2 # set to "yes" do warn
878
880
879 if lcmd:
881 if lcmd:
880 # add on last return code
882 # add on last return code
881 ret = int(lcmd.split()[1])
883 ret = int(lcmd.split()[1])
882 if ret != 0:
884 if ret != 0:
883 postout.append(" [%s]\n" % ret)
885 postout.append(" [%s]\n" % ret)
884 if pos in after:
886 if pos in after:
885 # merge in non-active test bits
887 # merge in non-active test bits
886 postout += after.pop(pos)
888 postout += after.pop(pos)
887 pos = int(lcmd.split()[0])
889 pos = int(lcmd.split()[0])
888
890
889 if pos in after:
891 if pos in after:
890 postout += after.pop(pos)
892 postout += after.pop(pos)
891
893
892 if warnonly == 2:
894 if warnonly == 2:
893 exitcode = False # set exitcode to warned
895 exitcode = False # set exitcode to warned
894 return exitcode, postout
896 return exitcode, postout
895
897
896 class TTest(Test):
898 class TTest(Test):
897 """A "t test" is a test backed by a .t file."""
899 """A "t test" is a test backed by a .t file."""
898
900
899 def _run(self, replacements, env):
901 def _run(self, replacements, env):
900 return tsttest(self._path, self.testtmp, self._options, replacements,
902 return tsttest(self._path, self._testtmp, self._options, replacements,
901 env)
903 env)
902
904
903 wifexited = getattr(os, "WIFEXITED", lambda x: False)
905 wifexited = getattr(os, "WIFEXITED", lambda x: False)
904 def run(cmd, wd, options, replacements, env):
906 def run(cmd, wd, options, replacements, env):
905 """Run command in a sub-process, capturing the output (stdout and stderr).
907 """Run command in a sub-process, capturing the output (stdout and stderr).
906 Return a tuple (exitcode, output). output is None in debug mode."""
908 Return a tuple (exitcode, output). output is None in debug mode."""
907 # TODO: Use subprocess.Popen if we're running on Python 2.4
909 # TODO: Use subprocess.Popen if we're running on Python 2.4
908 if options.debug:
910 if options.debug:
909 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
911 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
910 ret = proc.wait()
912 ret = proc.wait()
911 return (ret, None)
913 return (ret, None)
912
914
913 proc = Popen4(cmd, wd, options.timeout, env)
915 proc = Popen4(cmd, wd, options.timeout, env)
914 def cleanup():
916 def cleanup():
915 terminate(proc)
917 terminate(proc)
916 ret = proc.wait()
918 ret = proc.wait()
917 if ret == 0:
919 if ret == 0:
918 ret = signal.SIGTERM << 8
920 ret = signal.SIGTERM << 8
919 killdaemons(env['DAEMON_PIDS'])
921 killdaemons(env['DAEMON_PIDS'])
920 return ret
922 return ret
921
923
922 output = ''
924 output = ''
923 proc.tochild.close()
925 proc.tochild.close()
924
926
925 try:
927 try:
926 output = proc.fromchild.read()
928 output = proc.fromchild.read()
927 except KeyboardInterrupt:
929 except KeyboardInterrupt:
928 vlog('# Handling keyboard interrupt')
930 vlog('# Handling keyboard interrupt')
929 cleanup()
931 cleanup()
930 raise
932 raise
931
933
932 ret = proc.wait()
934 ret = proc.wait()
933 if wifexited(ret):
935 if wifexited(ret):
934 ret = os.WEXITSTATUS(ret)
936 ret = os.WEXITSTATUS(ret)
935
937
936 if proc.timeout:
938 if proc.timeout:
937 ret = 'timeout'
939 ret = 'timeout'
938
940
939 if ret:
941 if ret:
940 killdaemons(env['DAEMON_PIDS'])
942 killdaemons(env['DAEMON_PIDS'])
941
943
942 if abort:
944 if abort:
943 raise KeyboardInterrupt()
945 raise KeyboardInterrupt()
944
946
945 for s, r in replacements:
947 for s, r in replacements:
946 output = re.sub(s, r, output)
948 output = re.sub(s, r, output)
947 return ret, output.splitlines(True)
949 return ret, output.splitlines(True)
948
950
949 def runone(options, test, count):
951 def runone(options, test, count):
950 '''returns a result element: (code, test, msg)'''
952 '''returns a result element: (code, test, msg)'''
951
953
952 def skip(msg):
954 def skip(msg):
953 if options.verbose:
955 if options.verbose:
954 log("\nSkipping %s: %s" % (testpath, msg))
956 log("\nSkipping %s: %s" % (testpath, msg))
955 return 's', test, msg
957 return 's', test, msg
956
958
957 def fail(msg, ret):
959 def fail(msg, ret):
958 warned = ret is False
960 warned = ret is False
959 if not options.nodiff:
961 if not options.nodiff:
960 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
962 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
961 if (not ret and options.interactive
963 if (not ret and options.interactive
962 and os.path.exists(testpath + ".err")):
964 and os.path.exists(testpath + ".err")):
963 iolock.acquire()
965 iolock.acquire()
964 print "Accept this change? [n] ",
966 print "Accept this change? [n] ",
965 answer = sys.stdin.readline().strip()
967 answer = sys.stdin.readline().strip()
966 iolock.release()
968 iolock.release()
967 if answer.lower() in "y yes".split():
969 if answer.lower() in "y yes".split():
968 if test.endswith(".t"):
970 if test.endswith(".t"):
969 rename(testpath + ".err", testpath)
971 rename(testpath + ".err", testpath)
970 else:
972 else:
971 rename(testpath + ".err", testpath + ".out")
973 rename(testpath + ".err", testpath + ".out")
972 return '.', test, ''
974 return '.', test, ''
973 return warned and '~' or '!', test, msg
975 return warned and '~' or '!', test, msg
974
976
975 def success():
977 def success():
976 return '.', test, ''
978 return '.', test, ''
977
979
978 def ignore(msg):
980 def ignore(msg):
979 return 'i', test, msg
981 return 'i', test, msg
980
982
981 def describe(ret):
983 def describe(ret):
982 if ret < 0:
984 if ret < 0:
983 return 'killed by signal %d' % -ret
985 return 'killed by signal %d' % -ret
984 return 'returned error code %d' % ret
986 return 'returned error code %d' % ret
985
987
986 testpath = os.path.join(TESTDIR, test)
988 testpath = os.path.join(TESTDIR, test)
987 err = os.path.join(TESTDIR, test + ".err")
989 err = os.path.join(TESTDIR, test + ".err")
988 lctest = test.lower()
990 lctest = test.lower()
989
991
990 if not os.path.exists(testpath):
992 if not os.path.exists(testpath):
991 return skip("doesn't exist")
993 return skip("doesn't exist")
992
994
993 if not (options.whitelisted and test in options.whitelisted):
995 if not (options.whitelisted and test in options.whitelisted):
994 if options.blacklist and test in options.blacklist:
996 if options.blacklist and test in options.blacklist:
995 return skip("blacklisted")
997 return skip("blacklisted")
996
998
997 if options.retest and not os.path.exists(test + ".err"):
999 if options.retest and not os.path.exists(test + ".err"):
998 return ignore("not retesting")
1000 return ignore("not retesting")
999
1001
1000 if options.keywords:
1002 if options.keywords:
1001 fp = open(test)
1003 fp = open(test)
1002 t = fp.read().lower() + test.lower()
1004 t = fp.read().lower() + test.lower()
1003 fp.close()
1005 fp.close()
1004 for k in options.keywords.lower().split():
1006 for k in options.keywords.lower().split():
1005 if k in t:
1007 if k in t:
1006 break
1008 break
1007 else:
1009 else:
1008 return ignore("doesn't match keyword")
1010 return ignore("doesn't match keyword")
1009
1011
1010 if not os.path.basename(lctest).startswith("test-"):
1012 if not os.path.basename(lctest).startswith("test-"):
1011 return skip("not a test file")
1013 return skip("not a test file")
1012 for ext, cls, out in testtypes:
1014 for ext, cls, out in testtypes:
1013 if lctest.endswith(ext):
1015 if lctest.endswith(ext):
1014 runner = cls
1016 runner = cls
1015 ref = os.path.join(TESTDIR, test + out)
1017 ref = os.path.join(TESTDIR, test + out)
1016 break
1018 break
1017 else:
1019 else:
1018 return skip("unknown test type")
1020 return skip("unknown test type")
1019
1021
1020 vlog("# Test", test)
1022 vlog("# Test", test)
1021
1023
1022 if os.path.exists(err):
1024 if os.path.exists(err):
1023 os.remove(err) # Remove any previous output files
1025 os.remove(err) # Remove any previous output files
1024
1026
1025 # Make a tmp subdirectory to work in
1027 t = runner(testpath, options, count)
1026 threadtmp = os.path.join(HGTMP, "child%d" % count)
1027 os.mkdir(threadtmp)
1028
1029 t = runner(testpath, options, threadtmp, count)
1030
1028
1031 starttime = time.time()
1029 starttime = time.time()
1032 try:
1030 try:
1033 ret, out = t.run()
1031 ret, out = t.run()
1034 except KeyboardInterrupt:
1032 except KeyboardInterrupt:
1035 endtime = time.time()
1033 endtime = time.time()
1036 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
1034 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
1037 raise
1035 raise
1038 endtime = time.time()
1036 endtime = time.time()
1039 times.append((test, endtime - starttime))
1037 times.append((test, endtime - starttime))
1040 vlog("# Ret was:", ret)
1038 vlog("# Ret was:", ret)
1041
1039
1042 skipped = (ret == SKIPPED_STATUS)
1040 skipped = (ret == SKIPPED_STATUS)
1043
1041
1044 # If we're not in --debug mode and reference output file exists,
1042 # If we're not in --debug mode and reference output file exists,
1045 # check test output against it.
1043 # check test output against it.
1046 if options.debug:
1044 if options.debug:
1047 refout = None # to match "out is None"
1045 refout = None # to match "out is None"
1048 elif os.path.exists(ref):
1046 elif os.path.exists(ref):
1049 f = open(ref, "r")
1047 f = open(ref, "r")
1050 refout = f.read().splitlines(True)
1048 refout = f.read().splitlines(True)
1051 f.close()
1049 f.close()
1052 else:
1050 else:
1053 refout = []
1051 refout = []
1054
1052
1055 if (ret != 0 or out != refout) and not skipped and not options.debug:
1053 if (ret != 0 or out != refout) and not skipped and not options.debug:
1056 # Save errors to a file for diagnosis
1054 # Save errors to a file for diagnosis
1057 f = open(err, "wb")
1055 f = open(err, "wb")
1058 for line in out:
1056 for line in out:
1059 f.write(line)
1057 f.write(line)
1060 f.close()
1058 f.close()
1061
1059
1062 if skipped:
1060 if skipped:
1063 if out is None: # debug mode: nothing to parse
1061 if out is None: # debug mode: nothing to parse
1064 missing = ['unknown']
1062 missing = ['unknown']
1065 failed = None
1063 failed = None
1066 else:
1064 else:
1067 missing, failed = parsehghaveoutput(out)
1065 missing, failed = parsehghaveoutput(out)
1068 if not missing:
1066 if not missing:
1069 missing = ['irrelevant']
1067 missing = ['irrelevant']
1070 if failed:
1068 if failed:
1071 result = fail("hghave failed checking for %s" % failed[-1], ret)
1069 result = fail("hghave failed checking for %s" % failed[-1], ret)
1072 skipped = False
1070 skipped = False
1073 else:
1071 else:
1074 result = skip(missing[-1])
1072 result = skip(missing[-1])
1075 elif ret == 'timeout':
1073 elif ret == 'timeout':
1076 result = fail("timed out", ret)
1074 result = fail("timed out", ret)
1077 elif out != refout:
1075 elif out != refout:
1078 info = {}
1076 info = {}
1079 if not options.nodiff:
1077 if not options.nodiff:
1080 iolock.acquire()
1078 iolock.acquire()
1081 if options.view:
1079 if options.view:
1082 os.system("%s %s %s" % (options.view, ref, err))
1080 os.system("%s %s %s" % (options.view, ref, err))
1083 else:
1081 else:
1084 info = showdiff(refout, out, ref, err)
1082 info = showdiff(refout, out, ref, err)
1085 iolock.release()
1083 iolock.release()
1086 msg = ""
1084 msg = ""
1087 if info.get('servefail'): msg += "serve failed and "
1085 if info.get('servefail'): msg += "serve failed and "
1088 if ret:
1086 if ret:
1089 msg += "output changed and " + describe(ret)
1087 msg += "output changed and " + describe(ret)
1090 else:
1088 else:
1091 msg += "output changed"
1089 msg += "output changed"
1092 result = fail(msg, ret)
1090 result = fail(msg, ret)
1093 elif ret:
1091 elif ret:
1094 result = fail(describe(ret), ret)
1092 result = fail(describe(ret), ret)
1095 else:
1093 else:
1096 result = success()
1094 result = success()
1097
1095
1098 if not options.verbose:
1096 if not options.verbose:
1099 iolock.acquire()
1097 iolock.acquire()
1100 sys.stdout.write(result[0])
1098 sys.stdout.write(result[0])
1101 sys.stdout.flush()
1099 sys.stdout.flush()
1102 iolock.release()
1100 iolock.release()
1103
1101
1104 if not options.keep_tmpdir:
1102 if not options.keep_tmpdir:
1105 shutil.rmtree(threadtmp, True)
1103 shutil.rmtree(t.threadtmp, True)
1106 return result
1104 return result
1107
1105
1108 _hgpath = None
1106 _hgpath = None
1109
1107
1110 def _gethgpath():
1108 def _gethgpath():
1111 """Return the path to the mercurial package that is actually found by
1109 """Return the path to the mercurial package that is actually found by
1112 the current Python interpreter."""
1110 the current Python interpreter."""
1113 global _hgpath
1111 global _hgpath
1114 if _hgpath is not None:
1112 if _hgpath is not None:
1115 return _hgpath
1113 return _hgpath
1116
1114
1117 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1115 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1118 pipe = os.popen(cmd % PYTHON)
1116 pipe = os.popen(cmd % PYTHON)
1119 try:
1117 try:
1120 _hgpath = pipe.read().strip()
1118 _hgpath = pipe.read().strip()
1121 finally:
1119 finally:
1122 pipe.close()
1120 pipe.close()
1123 return _hgpath
1121 return _hgpath
1124
1122
1125 def _checkhglib(verb):
1123 def _checkhglib(verb):
1126 """Ensure that the 'mercurial' package imported by python is
1124 """Ensure that the 'mercurial' package imported by python is
1127 the one we expect it to be. If not, print a warning to stderr."""
1125 the one we expect it to be. If not, print a warning to stderr."""
1128 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1126 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1129 actualhg = _gethgpath()
1127 actualhg = _gethgpath()
1130 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1128 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1131 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1129 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1132 ' (expected %s)\n'
1130 ' (expected %s)\n'
1133 % (verb, actualhg, expecthg))
1131 % (verb, actualhg, expecthg))
1134
1132
1135 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1133 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1136 times = []
1134 times = []
1137 iolock = threading.Lock()
1135 iolock = threading.Lock()
1138 abort = False
1136 abort = False
1139
1137
1140 def scheduletests(options, tests):
1138 def scheduletests(options, tests):
1141 jobs = options.jobs
1139 jobs = options.jobs
1142 done = queue.Queue()
1140 done = queue.Queue()
1143 running = 0
1141 running = 0
1144 count = 0
1142 count = 0
1145 global abort
1143 global abort
1146
1144
1147 def job(test, count):
1145 def job(test, count):
1148 try:
1146 try:
1149 done.put(runone(options, test, count))
1147 done.put(runone(options, test, count))
1150 except KeyboardInterrupt:
1148 except KeyboardInterrupt:
1151 pass
1149 pass
1152 except: # re-raises
1150 except: # re-raises
1153 done.put(('!', test, 'run-test raised an error, see traceback'))
1151 done.put(('!', test, 'run-test raised an error, see traceback'))
1154 raise
1152 raise
1155
1153
1156 try:
1154 try:
1157 while tests or running:
1155 while tests or running:
1158 if not done.empty() or running == jobs or not tests:
1156 if not done.empty() or running == jobs or not tests:
1159 try:
1157 try:
1160 code, test, msg = done.get(True, 1)
1158 code, test, msg = done.get(True, 1)
1161 results[code].append((test, msg))
1159 results[code].append((test, msg))
1162 if options.first and code not in '.si':
1160 if options.first and code not in '.si':
1163 break
1161 break
1164 except queue.Empty:
1162 except queue.Empty:
1165 continue
1163 continue
1166 running -= 1
1164 running -= 1
1167 if tests and not running == jobs:
1165 if tests and not running == jobs:
1168 test = tests.pop(0)
1166 test = tests.pop(0)
1169 if options.loop:
1167 if options.loop:
1170 tests.append(test)
1168 tests.append(test)
1171 t = threading.Thread(target=job, name=test, args=(test, count))
1169 t = threading.Thread(target=job, name=test, args=(test, count))
1172 t.start()
1170 t.start()
1173 running += 1
1171 running += 1
1174 count += 1
1172 count += 1
1175 except KeyboardInterrupt:
1173 except KeyboardInterrupt:
1176 abort = True
1174 abort = True
1177
1175
1178 def runtests(options, tests):
1176 def runtests(options, tests):
1179 try:
1177 try:
1180 if INST:
1178 if INST:
1181 installhg(options)
1179 installhg(options)
1182 _checkhglib("Testing")
1180 _checkhglib("Testing")
1183 else:
1181 else:
1184 usecorrectpython()
1182 usecorrectpython()
1185
1183
1186 if options.restart:
1184 if options.restart:
1187 orig = list(tests)
1185 orig = list(tests)
1188 while tests:
1186 while tests:
1189 if os.path.exists(tests[0] + ".err"):
1187 if os.path.exists(tests[0] + ".err"):
1190 break
1188 break
1191 tests.pop(0)
1189 tests.pop(0)
1192 if not tests:
1190 if not tests:
1193 print "running all tests"
1191 print "running all tests"
1194 tests = orig
1192 tests = orig
1195
1193
1196 scheduletests(options, tests)
1194 scheduletests(options, tests)
1197
1195
1198 failed = len(results['!'])
1196 failed = len(results['!'])
1199 warned = len(results['~'])
1197 warned = len(results['~'])
1200 tested = len(results['.']) + failed + warned
1198 tested = len(results['.']) + failed + warned
1201 skipped = len(results['s'])
1199 skipped = len(results['s'])
1202 ignored = len(results['i'])
1200 ignored = len(results['i'])
1203
1201
1204 print
1202 print
1205 if not options.noskips:
1203 if not options.noskips:
1206 for s in results['s']:
1204 for s in results['s']:
1207 print "Skipped %s: %s" % s
1205 print "Skipped %s: %s" % s
1208 for s in results['~']:
1206 for s in results['~']:
1209 print "Warned %s: %s" % s
1207 print "Warned %s: %s" % s
1210 for s in results['!']:
1208 for s in results['!']:
1211 print "Failed %s: %s" % s
1209 print "Failed %s: %s" % s
1212 _checkhglib("Tested")
1210 _checkhglib("Tested")
1213 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1211 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1214 tested, skipped + ignored, warned, failed)
1212 tested, skipped + ignored, warned, failed)
1215 if results['!']:
1213 if results['!']:
1216 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1214 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1217 if options.time:
1215 if options.time:
1218 outputtimes(options)
1216 outputtimes(options)
1219
1217
1220 if options.anycoverage:
1218 if options.anycoverage:
1221 outputcoverage(options)
1219 outputcoverage(options)
1222 except KeyboardInterrupt:
1220 except KeyboardInterrupt:
1223 failed = True
1221 failed = True
1224 print "\ninterrupted!"
1222 print "\ninterrupted!"
1225
1223
1226 if failed:
1224 if failed:
1227 return 1
1225 return 1
1228 if warned:
1226 if warned:
1229 return 80
1227 return 80
1230
1228
1231 testtypes = [('.py', PythonTest, '.out'),
1229 testtypes = [('.py', PythonTest, '.out'),
1232 ('.t', TTest, '')]
1230 ('.t', TTest, '')]
1233
1231
1234 def main(args, parser=None):
1232 def main(args, parser=None):
1235 parser = parser or getparser()
1233 parser = parser or getparser()
1236 (options, args) = parseargs(args, parser)
1234 (options, args) = parseargs(args, parser)
1237 os.umask(022)
1235 os.umask(022)
1238
1236
1239 checktools()
1237 checktools()
1240
1238
1241 if not args:
1239 if not args:
1242 if options.changed:
1240 if options.changed:
1243 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1241 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1244 None, 0)
1242 None, 0)
1245 stdout, stderr = proc.communicate()
1243 stdout, stderr = proc.communicate()
1246 args = stdout.strip('\0').split('\0')
1244 args = stdout.strip('\0').split('\0')
1247 else:
1245 else:
1248 args = os.listdir(".")
1246 args = os.listdir(".")
1249
1247
1250 tests = [t for t in args
1248 tests = [t for t in args
1251 if os.path.basename(t).startswith("test-")
1249 if os.path.basename(t).startswith("test-")
1252 and (t.endswith(".py") or t.endswith(".t"))]
1250 and (t.endswith(".py") or t.endswith(".t"))]
1253
1251
1254 if options.random:
1252 if options.random:
1255 random.shuffle(tests)
1253 random.shuffle(tests)
1256 else:
1254 else:
1257 # keywords for slow tests
1255 # keywords for slow tests
1258 slow = 'svn gendoc check-code-hg'.split()
1256 slow = 'svn gendoc check-code-hg'.split()
1259 def sortkey(f):
1257 def sortkey(f):
1260 # run largest tests first, as they tend to take the longest
1258 # run largest tests first, as they tend to take the longest
1261 try:
1259 try:
1262 val = -os.stat(f).st_size
1260 val = -os.stat(f).st_size
1263 except OSError, e:
1261 except OSError, e:
1264 if e.errno != errno.ENOENT:
1262 if e.errno != errno.ENOENT:
1265 raise
1263 raise
1266 return -1e9 # file does not exist, tell early
1264 return -1e9 # file does not exist, tell early
1267 for kw in slow:
1265 for kw in slow:
1268 if kw in f:
1266 if kw in f:
1269 val *= 10
1267 val *= 10
1270 return val
1268 return val
1271 tests.sort(key=sortkey)
1269 tests.sort(key=sortkey)
1272
1270
1273 if 'PYTHONHASHSEED' not in os.environ:
1271 if 'PYTHONHASHSEED' not in os.environ:
1274 # use a random python hash seed all the time
1272 # use a random python hash seed all the time
1275 # we do the randomness ourself to know what seed is used
1273 # we do the randomness ourself to know what seed is used
1276 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1274 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1277
1275
1278 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1276 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1279 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1277 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1280 if options.tmpdir:
1278 if options.tmpdir:
1281 options.keep_tmpdir = True
1279 options.keep_tmpdir = True
1282 tmpdir = options.tmpdir
1280 tmpdir = options.tmpdir
1283 if os.path.exists(tmpdir):
1281 if os.path.exists(tmpdir):
1284 # Meaning of tmpdir has changed since 1.3: we used to create
1282 # Meaning of tmpdir has changed since 1.3: we used to create
1285 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1283 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1286 # tmpdir already exists.
1284 # tmpdir already exists.
1287 print "error: temp dir %r already exists" % tmpdir
1285 print "error: temp dir %r already exists" % tmpdir
1288 return 1
1286 return 1
1289
1287
1290 # Automatically removing tmpdir sounds convenient, but could
1288 # Automatically removing tmpdir sounds convenient, but could
1291 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1289 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1292 # or "--tmpdir=$HOME".
1290 # or "--tmpdir=$HOME".
1293 #vlog("# Removing temp dir", tmpdir)
1291 #vlog("# Removing temp dir", tmpdir)
1294 #shutil.rmtree(tmpdir)
1292 #shutil.rmtree(tmpdir)
1295 os.makedirs(tmpdir)
1293 os.makedirs(tmpdir)
1296 else:
1294 else:
1297 d = None
1295 d = None
1298 if os.name == 'nt':
1296 if os.name == 'nt':
1299 # without this, we get the default temp dir location, but
1297 # without this, we get the default temp dir location, but
1300 # in all lowercase, which causes troubles with paths (issue3490)
1298 # in all lowercase, which causes troubles with paths (issue3490)
1301 d = os.getenv('TMP')
1299 d = os.getenv('TMP')
1302 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1300 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1303 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1301 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1304
1302
1305 if options.with_hg:
1303 if options.with_hg:
1306 INST = None
1304 INST = None
1307 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1305 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1308 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1306 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1309 os.makedirs(TMPBINDIR)
1307 os.makedirs(TMPBINDIR)
1310
1308
1311 # This looks redundant with how Python initializes sys.path from
1309 # This looks redundant with how Python initializes sys.path from
1312 # the location of the script being executed. Needed because the
1310 # the location of the script being executed. Needed because the
1313 # "hg" specified by --with-hg is not the only Python script
1311 # "hg" specified by --with-hg is not the only Python script
1314 # executed in the test suite that needs to import 'mercurial'
1312 # executed in the test suite that needs to import 'mercurial'
1315 # ... which means it's not really redundant at all.
1313 # ... which means it's not really redundant at all.
1316 PYTHONDIR = BINDIR
1314 PYTHONDIR = BINDIR
1317 else:
1315 else:
1318 INST = os.path.join(HGTMP, "install")
1316 INST = os.path.join(HGTMP, "install")
1319 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1317 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1320 TMPBINDIR = BINDIR
1318 TMPBINDIR = BINDIR
1321 PYTHONDIR = os.path.join(INST, "lib", "python")
1319 PYTHONDIR = os.path.join(INST, "lib", "python")
1322
1320
1323 os.environ["BINDIR"] = BINDIR
1321 os.environ["BINDIR"] = BINDIR
1324 os.environ["PYTHON"] = PYTHON
1322 os.environ["PYTHON"] = PYTHON
1325
1323
1326 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1324 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1327 if TMPBINDIR != BINDIR:
1325 if TMPBINDIR != BINDIR:
1328 path = [TMPBINDIR] + path
1326 path = [TMPBINDIR] + path
1329 os.environ["PATH"] = os.pathsep.join(path)
1327 os.environ["PATH"] = os.pathsep.join(path)
1330
1328
1331 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1329 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1332 # can run .../tests/run-tests.py test-foo where test-foo
1330 # can run .../tests/run-tests.py test-foo where test-foo
1333 # adds an extension to HGRC. Also include run-test.py directory to import
1331 # adds an extension to HGRC. Also include run-test.py directory to import
1334 # modules like heredoctest.
1332 # modules like heredoctest.
1335 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1333 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1336 # We have to augment PYTHONPATH, rather than simply replacing
1334 # We have to augment PYTHONPATH, rather than simply replacing
1337 # it, in case external libraries are only available via current
1335 # it, in case external libraries are only available via current
1338 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1336 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1339 # are in /opt/subversion.)
1337 # are in /opt/subversion.)
1340 oldpypath = os.environ.get(IMPL_PATH)
1338 oldpypath = os.environ.get(IMPL_PATH)
1341 if oldpypath:
1339 if oldpypath:
1342 pypath.append(oldpypath)
1340 pypath.append(oldpypath)
1343 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1341 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1344
1342
1345 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1343 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1346
1344
1347 vlog("# Using TESTDIR", TESTDIR)
1345 vlog("# Using TESTDIR", TESTDIR)
1348 vlog("# Using HGTMP", HGTMP)
1346 vlog("# Using HGTMP", HGTMP)
1349 vlog("# Using PATH", os.environ["PATH"])
1347 vlog("# Using PATH", os.environ["PATH"])
1350 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1348 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1351
1349
1352 try:
1350 try:
1353 return runtests(options, tests) or 0
1351 return runtests(options, tests) or 0
1354 finally:
1352 finally:
1355 time.sleep(.1)
1353 time.sleep(.1)
1356 cleanup(options)
1354 cleanup(options)
1357
1355
1358 if __name__ == '__main__':
1356 if __name__ == '__main__':
1359 sys.exit(main(sys.argv[1:]))
1357 sys.exit(main(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now