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