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