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