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