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