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