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