##// END OF EJS Templates
run-tests: force to test pure on pypy as well
Ronny Pfannschmidt -
r10766:afbcea27 default
parent child Browse files
Show More
@@ -1,952 +1,952 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 signal
49 import signal
50 import subprocess
50 import subprocess
51 import shutil
51 import shutil
52 import signal
52 import signal
53 import sys
53 import sys
54 import tempfile
54 import tempfile
55 import time
55 import time
56
56
57 closefds = os.name == 'posix'
57 closefds = os.name == 'posix'
58 def Popen4(cmd, bufsize=-1):
58 def Popen4(cmd, bufsize=-1):
59 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
59 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
60 close_fds=closefds,
60 close_fds=closefds,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
61 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
62 stderr=subprocess.STDOUT)
62 stderr=subprocess.STDOUT)
63 p.fromchild = p.stdout
63 p.fromchild = p.stdout
64 p.tochild = p.stdin
64 p.tochild = p.stdin
65 p.childerr = p.stderr
65 p.childerr = p.stderr
66 return p
66 return p
67
67
68 # reserved exit code to skip test (used by hghave)
68 # reserved exit code to skip test (used by hghave)
69 SKIPPED_STATUS = 80
69 SKIPPED_STATUS = 80
70 SKIPPED_PREFIX = 'skipped: '
70 SKIPPED_PREFIX = 'skipped: '
71 FAILED_PREFIX = 'hghave check failed: '
71 FAILED_PREFIX = 'hghave check failed: '
72 PYTHON = sys.executable
72 PYTHON = sys.executable
73 IMPL_PATH = 'PYTHONPATH'
73 IMPL_PATH = 'PYTHONPATH'
74 if 'java' in sys.platform:
74 if 'java' in sys.platform:
75 IMPL_PATH = 'JYTHONPATH'
75 IMPL_PATH = 'JYTHONPATH'
76
76
77 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
77 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
78
78
79 defaults = {
79 defaults = {
80 'jobs': ('HGTEST_JOBS', 1),
80 'jobs': ('HGTEST_JOBS', 1),
81 'timeout': ('HGTEST_TIMEOUT', 180),
81 'timeout': ('HGTEST_TIMEOUT', 180),
82 'port': ('HGTEST_PORT', 20059),
82 'port': ('HGTEST_PORT', 20059),
83 }
83 }
84
84
85 def parseargs():
85 def parseargs():
86 parser = optparse.OptionParser("%prog [options] [tests]")
86 parser = optparse.OptionParser("%prog [options] [tests]")
87 parser.add_option("-C", "--annotate", action="store_true",
87 parser.add_option("-C", "--annotate", action="store_true",
88 help="output files annotated with coverage")
88 help="output files annotated with coverage")
89 parser.add_option("--child", type="int",
89 parser.add_option("--child", type="int",
90 help="run as child process, summary to given fd")
90 help="run as child process, summary to given fd")
91 parser.add_option("-c", "--cover", action="store_true",
91 parser.add_option("-c", "--cover", action="store_true",
92 help="print a test coverage report")
92 help="print a test coverage report")
93 parser.add_option("-f", "--first", action="store_true",
93 parser.add_option("-f", "--first", action="store_true",
94 help="exit on the first test failure")
94 help="exit on the first test failure")
95 parser.add_option("-i", "--interactive", action="store_true",
95 parser.add_option("-i", "--interactive", action="store_true",
96 help="prompt to accept changed output")
96 help="prompt to accept changed output")
97 parser.add_option("-j", "--jobs", type="int",
97 parser.add_option("-j", "--jobs", type="int",
98 help="number of jobs to run in parallel"
98 help="number of jobs to run in parallel"
99 " (default: $%s or %d)" % defaults['jobs'])
99 " (default: $%s or %d)" % defaults['jobs'])
100 parser.add_option("-k", "--keywords",
100 parser.add_option("-k", "--keywords",
101 help="run tests matching keywords")
101 help="run tests matching keywords")
102 parser.add_option("--keep-tmpdir", action="store_true",
102 parser.add_option("--keep-tmpdir", action="store_true",
103 help="keep temporary directory after running tests")
103 help="keep temporary directory after running tests")
104 parser.add_option("--tmpdir", type="string",
104 parser.add_option("--tmpdir", type="string",
105 help="run tests in the given temporary directory"
105 help="run tests in the given temporary directory"
106 " (implies --keep-tmpdir)")
106 " (implies --keep-tmpdir)")
107 parser.add_option("-d", "--debug", action="store_true",
107 parser.add_option("-d", "--debug", action="store_true",
108 help="debug mode: write output of test scripts to console"
108 help="debug mode: write output of test scripts to console"
109 " rather than capturing and diff'ing it (disables timeout)")
109 " rather than capturing and diff'ing it (disables timeout)")
110 parser.add_option("-R", "--restart", action="store_true",
110 parser.add_option("-R", "--restart", action="store_true",
111 help="restart at last error")
111 help="restart at last error")
112 parser.add_option("-p", "--port", type="int",
112 parser.add_option("-p", "--port", type="int",
113 help="port on which servers should listen"
113 help="port on which servers should listen"
114 " (default: $%s or %d)" % defaults['port'])
114 " (default: $%s or %d)" % defaults['port'])
115 parser.add_option("-r", "--retest", action="store_true",
115 parser.add_option("-r", "--retest", action="store_true",
116 help="retest failed tests")
116 help="retest failed tests")
117 parser.add_option("-S", "--noskips", action="store_true",
117 parser.add_option("-S", "--noskips", action="store_true",
118 help="don't report skip tests verbosely")
118 help="don't report skip tests verbosely")
119 parser.add_option("-t", "--timeout", type="int",
119 parser.add_option("-t", "--timeout", type="int",
120 help="kill errant tests after TIMEOUT seconds"
120 help="kill errant tests after TIMEOUT seconds"
121 " (default: $%s or %d)" % defaults['timeout'])
121 " (default: $%s or %d)" % defaults['timeout'])
122 parser.add_option("-v", "--verbose", action="store_true",
122 parser.add_option("-v", "--verbose", action="store_true",
123 help="output verbose messages")
123 help="output verbose messages")
124 parser.add_option("-n", "--nodiff", action="store_true",
124 parser.add_option("-n", "--nodiff", action="store_true",
125 help="skip showing test changes")
125 help="skip showing test changes")
126 parser.add_option("--with-hg", type="string",
126 parser.add_option("--with-hg", type="string",
127 metavar="HG",
127 metavar="HG",
128 help="test using specified hg script rather than a "
128 help="test using specified hg script rather than a "
129 "temporary installation")
129 "temporary installation")
130 parser.add_option("--local", action="store_true",
130 parser.add_option("--local", action="store_true",
131 help="shortcut for --with-hg=<testdir>/../hg")
131 help="shortcut for --with-hg=<testdir>/../hg")
132 parser.add_option("--pure", action="store_true",
132 parser.add_option("--pure", action="store_true",
133 help="use pure Python code instead of C extensions")
133 help="use pure Python code instead of C extensions")
134 parser.add_option("-3", "--py3k-warnings", action="store_true",
134 parser.add_option("-3", "--py3k-warnings", action="store_true",
135 help="enable Py3k warnings on Python 2.6+")
135 help="enable Py3k warnings on Python 2.6+")
136 parser.add_option("--inotify", action="store_true",
136 parser.add_option("--inotify", action="store_true",
137 help="enable inotify extension when running tests")
137 help="enable inotify extension when running tests")
138 parser.add_option("--blacklist", action="append",
138 parser.add_option("--blacklist", action="append",
139 help="skip tests listed in the specified blacklist file")
139 help="skip tests listed in the specified blacklist file")
140
140
141 for option, default in defaults.items():
141 for option, default in defaults.items():
142 defaults[option] = int(os.environ.get(*default))
142 defaults[option] = int(os.environ.get(*default))
143 parser.set_defaults(**defaults)
143 parser.set_defaults(**defaults)
144 (options, args) = parser.parse_args()
144 (options, args) = parser.parse_args()
145
145
146 # jython is always pure
146 # jython is always pure
147 if 'java' in sys.platform:
147 if 'java' in sys.platform or '__pypy__' in sys.modules:
148 options.pure = True
148 options.pure = True
149
149
150 if options.with_hg:
150 if options.with_hg:
151 if not (os.path.isfile(options.with_hg) and
151 if not (os.path.isfile(options.with_hg) and
152 os.access(options.with_hg, os.X_OK)):
152 os.access(options.with_hg, os.X_OK)):
153 parser.error('--with-hg must specify an executable hg script')
153 parser.error('--with-hg must specify an executable hg script')
154 if not os.path.basename(options.with_hg) == 'hg':
154 if not os.path.basename(options.with_hg) == 'hg':
155 sys.stderr.write('warning: --with-hg should specify an hg script')
155 sys.stderr.write('warning: --with-hg should specify an hg script')
156 if options.local:
156 if options.local:
157 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
157 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
158 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
158 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
159 if not os.access(hgbin, os.X_OK):
159 if not os.access(hgbin, os.X_OK):
160 parser.error('--local specified, but %r not found or not executable'
160 parser.error('--local specified, but %r not found or not executable'
161 % hgbin)
161 % hgbin)
162 options.with_hg = hgbin
162 options.with_hg = hgbin
163
163
164 options.anycoverage = options.cover or options.annotate
164 options.anycoverage = options.cover or options.annotate
165 if options.anycoverage:
165 if options.anycoverage:
166 try:
166 try:
167 import coverage
167 import coverage
168 covver = version.StrictVersion(coverage.__version__).version
168 covver = version.StrictVersion(coverage.__version__).version
169 if covver < (3, 3):
169 if covver < (3, 3):
170 parser.error('coverage options require coverage 3.3 or later')
170 parser.error('coverage options require coverage 3.3 or later')
171 except ImportError:
171 except ImportError:
172 parser.error('coverage options now require the coverage package')
172 parser.error('coverage options now require the coverage package')
173
173
174 if options.anycoverage and options.local:
174 if options.anycoverage and options.local:
175 # this needs some path mangling somewhere, I guess
175 # this needs some path mangling somewhere, I guess
176 parser.error("sorry, coverage options do not work when --local "
176 parser.error("sorry, coverage options do not work when --local "
177 "is specified")
177 "is specified")
178
178
179 global vlog
179 global vlog
180 if options.verbose:
180 if options.verbose:
181 if options.jobs > 1 or options.child is not None:
181 if options.jobs > 1 or options.child is not None:
182 pid = "[%d]" % os.getpid()
182 pid = "[%d]" % os.getpid()
183 else:
183 else:
184 pid = None
184 pid = None
185 def vlog(*msg):
185 def vlog(*msg):
186 if pid:
186 if pid:
187 print pid,
187 print pid,
188 for m in msg:
188 for m in msg:
189 print m,
189 print m,
190 print
190 print
191 sys.stdout.flush()
191 sys.stdout.flush()
192 else:
192 else:
193 vlog = lambda *msg: None
193 vlog = lambda *msg: None
194
194
195 if options.tmpdir:
195 if options.tmpdir:
196 options.tmpdir = os.path.expanduser(options.tmpdir)
196 options.tmpdir = os.path.expanduser(options.tmpdir)
197
197
198 if options.jobs < 1:
198 if options.jobs < 1:
199 parser.error('--jobs must be positive')
199 parser.error('--jobs must be positive')
200 if options.interactive and options.jobs > 1:
200 if options.interactive and options.jobs > 1:
201 print '(--interactive overrides --jobs)'
201 print '(--interactive overrides --jobs)'
202 options.jobs = 1
202 options.jobs = 1
203 if options.interactive and options.debug:
203 if options.interactive and options.debug:
204 parser.error("-i/--interactive and -d/--debug are incompatible")
204 parser.error("-i/--interactive and -d/--debug are incompatible")
205 if options.debug:
205 if options.debug:
206 if options.timeout != defaults['timeout']:
206 if options.timeout != defaults['timeout']:
207 sys.stderr.write(
207 sys.stderr.write(
208 'warning: --timeout option ignored with --debug\n')
208 'warning: --timeout option ignored with --debug\n')
209 options.timeout = 0
209 options.timeout = 0
210 if options.py3k_warnings:
210 if options.py3k_warnings:
211 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
211 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
212 parser.error('--py3k-warnings can only be used on Python 2.6+')
212 parser.error('--py3k-warnings can only be used on Python 2.6+')
213 if options.blacklist:
213 if options.blacklist:
214 blacklist = dict()
214 blacklist = dict()
215 for filename in options.blacklist:
215 for filename in options.blacklist:
216 try:
216 try:
217 path = os.path.expanduser(os.path.expandvars(filename))
217 path = os.path.expanduser(os.path.expandvars(filename))
218 f = open(path, "r")
218 f = open(path, "r")
219 except IOError, err:
219 except IOError, err:
220 if err.errno != errno.ENOENT:
220 if err.errno != errno.ENOENT:
221 raise
221 raise
222 print "warning: no such blacklist file: %s" % filename
222 print "warning: no such blacklist file: %s" % filename
223 continue
223 continue
224
224
225 for line in f.readlines():
225 for line in f.readlines():
226 line = line.strip()
226 line = line.strip()
227 if line and not line.startswith('#'):
227 if line and not line.startswith('#'):
228 blacklist[line] = filename
228 blacklist[line] = filename
229
229
230 options.blacklist = blacklist
230 options.blacklist = blacklist
231
231
232 return (options, args)
232 return (options, args)
233
233
234 def rename(src, dst):
234 def rename(src, dst):
235 """Like os.rename(), trade atomicity and opened files friendliness
235 """Like os.rename(), trade atomicity and opened files friendliness
236 for existing destination support.
236 for existing destination support.
237 """
237 """
238 shutil.copy(src, dst)
238 shutil.copy(src, dst)
239 os.remove(src)
239 os.remove(src)
240
240
241 def splitnewlines(text):
241 def splitnewlines(text):
242 '''like str.splitlines, but only split on newlines.
242 '''like str.splitlines, but only split on newlines.
243 keep line endings.'''
243 keep line endings.'''
244 i = 0
244 i = 0
245 lines = []
245 lines = []
246 while True:
246 while True:
247 n = text.find('\n', i)
247 n = text.find('\n', i)
248 if n == -1:
248 if n == -1:
249 last = text[i:]
249 last = text[i:]
250 if last:
250 if last:
251 lines.append(last)
251 lines.append(last)
252 return lines
252 return lines
253 lines.append(text[i:n + 1])
253 lines.append(text[i:n + 1])
254 i = n + 1
254 i = n + 1
255
255
256 def parsehghaveoutput(lines):
256 def parsehghaveoutput(lines):
257 '''Parse hghave log lines.
257 '''Parse hghave log lines.
258 Return tuple of lists (missing, failed):
258 Return tuple of lists (missing, failed):
259 * the missing/unknown features
259 * the missing/unknown features
260 * the features for which existence check failed'''
260 * the features for which existence check failed'''
261 missing = []
261 missing = []
262 failed = []
262 failed = []
263 for line in lines:
263 for line in lines:
264 if line.startswith(SKIPPED_PREFIX):
264 if line.startswith(SKIPPED_PREFIX):
265 line = line.splitlines()[0]
265 line = line.splitlines()[0]
266 missing.append(line[len(SKIPPED_PREFIX):])
266 missing.append(line[len(SKIPPED_PREFIX):])
267 elif line.startswith(FAILED_PREFIX):
267 elif line.startswith(FAILED_PREFIX):
268 line = line.splitlines()[0]
268 line = line.splitlines()[0]
269 failed.append(line[len(FAILED_PREFIX):])
269 failed.append(line[len(FAILED_PREFIX):])
270
270
271 return missing, failed
271 return missing, failed
272
272
273 def showdiff(expected, output, ref, err):
273 def showdiff(expected, output, ref, err):
274 for line in difflib.unified_diff(expected, output, ref, err):
274 for line in difflib.unified_diff(expected, output, ref, err):
275 sys.stdout.write(line)
275 sys.stdout.write(line)
276
276
277 def findprogram(program):
277 def findprogram(program):
278 """Search PATH for a executable program"""
278 """Search PATH for a executable program"""
279 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
279 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
280 name = os.path.join(p, program)
280 name = os.path.join(p, program)
281 if os.access(name, os.X_OK):
281 if os.access(name, os.X_OK):
282 return name
282 return name
283 return None
283 return None
284
284
285 def checktools():
285 def checktools():
286 # Before we go any further, check for pre-requisite tools
286 # Before we go any further, check for pre-requisite tools
287 # stuff from coreutils (cat, rm, etc) are not tested
287 # stuff from coreutils (cat, rm, etc) are not tested
288 for p in requiredtools:
288 for p in requiredtools:
289 if os.name == 'nt':
289 if os.name == 'nt':
290 p += '.exe'
290 p += '.exe'
291 found = findprogram(p)
291 found = findprogram(p)
292 if found:
292 if found:
293 vlog("# Found prerequisite", p, "at", found)
293 vlog("# Found prerequisite", p, "at", found)
294 else:
294 else:
295 print "WARNING: Did not find prerequisite tool: "+p
295 print "WARNING: Did not find prerequisite tool: "+p
296
296
297 def killdaemons():
297 def killdaemons():
298 # Kill off any leftover daemon processes
298 # Kill off any leftover daemon processes
299 try:
299 try:
300 fp = open(DAEMON_PIDS)
300 fp = open(DAEMON_PIDS)
301 for line in fp:
301 for line in fp:
302 try:
302 try:
303 pid = int(line)
303 pid = int(line)
304 except ValueError:
304 except ValueError:
305 continue
305 continue
306 try:
306 try:
307 os.kill(pid, 0)
307 os.kill(pid, 0)
308 vlog('# Killing daemon process %d' % pid)
308 vlog('# Killing daemon process %d' % pid)
309 os.kill(pid, signal.SIGTERM)
309 os.kill(pid, signal.SIGTERM)
310 time.sleep(0.25)
310 time.sleep(0.25)
311 os.kill(pid, 0)
311 os.kill(pid, 0)
312 vlog('# Daemon process %d is stuck - really killing it' % pid)
312 vlog('# Daemon process %d is stuck - really killing it' % pid)
313 os.kill(pid, signal.SIGKILL)
313 os.kill(pid, signal.SIGKILL)
314 except OSError, err:
314 except OSError, err:
315 if err.errno != errno.ESRCH:
315 if err.errno != errno.ESRCH:
316 raise
316 raise
317 fp.close()
317 fp.close()
318 os.unlink(DAEMON_PIDS)
318 os.unlink(DAEMON_PIDS)
319 except IOError:
319 except IOError:
320 pass
320 pass
321
321
322 def cleanup(options):
322 def cleanup(options):
323 if not options.keep_tmpdir:
323 if not options.keep_tmpdir:
324 vlog("# Cleaning up HGTMP", HGTMP)
324 vlog("# Cleaning up HGTMP", HGTMP)
325 shutil.rmtree(HGTMP, True)
325 shutil.rmtree(HGTMP, True)
326
326
327 def usecorrectpython():
327 def usecorrectpython():
328 # some tests run python interpreter. they must use same
328 # some tests run python interpreter. they must use same
329 # interpreter we use or bad things will happen.
329 # interpreter we use or bad things will happen.
330 exedir, exename = os.path.split(sys.executable)
330 exedir, exename = os.path.split(sys.executable)
331 if exename == 'python':
331 if exename == 'python':
332 path = findprogram('python')
332 path = findprogram('python')
333 if os.path.dirname(path) == exedir:
333 if os.path.dirname(path) == exedir:
334 return
334 return
335 vlog('# Making python executable in test path use correct Python')
335 vlog('# Making python executable in test path use correct Python')
336 mypython = os.path.join(BINDIR, 'python')
336 mypython = os.path.join(BINDIR, 'python')
337 try:
337 try:
338 os.symlink(sys.executable, mypython)
338 os.symlink(sys.executable, mypython)
339 except AttributeError:
339 except AttributeError:
340 # windows fallback
340 # windows fallback
341 shutil.copyfile(sys.executable, mypython)
341 shutil.copyfile(sys.executable, mypython)
342 shutil.copymode(sys.executable, mypython)
342 shutil.copymode(sys.executable, mypython)
343
343
344 def installhg(options):
344 def installhg(options):
345 vlog("# Performing temporary installation of HG")
345 vlog("# Performing temporary installation of HG")
346 installerrs = os.path.join("tests", "install.err")
346 installerrs = os.path.join("tests", "install.err")
347 pure = options.pure and "--pure" or ""
347 pure = options.pure and "--pure" or ""
348
348
349 # Run installer in hg root
349 # Run installer in hg root
350 script = os.path.realpath(sys.argv[0])
350 script = os.path.realpath(sys.argv[0])
351 hgroot = os.path.dirname(os.path.dirname(script))
351 hgroot = os.path.dirname(os.path.dirname(script))
352 os.chdir(hgroot)
352 os.chdir(hgroot)
353 nohome = '--home=""'
353 nohome = '--home=""'
354 if os.name == 'nt':
354 if os.name == 'nt':
355 # The --home="" trick works only on OS where os.sep == '/'
355 # The --home="" trick works only on OS where os.sep == '/'
356 # because of a distutils convert_path() fast-path. Avoid it at
356 # because of a distutils convert_path() fast-path. Avoid it at
357 # least on Windows for now, deal with .pydistutils.cfg bugs
357 # least on Windows for now, deal with .pydistutils.cfg bugs
358 # when they happen.
358 # when they happen.
359 nohome = ''
359 nohome = ''
360 cmd = ('%s setup.py %s clean --all'
360 cmd = ('%s setup.py %s clean --all'
361 ' install --force --prefix="%s" --install-lib="%s"'
361 ' install --force --prefix="%s" --install-lib="%s"'
362 ' --install-scripts="%s" %s >%s 2>&1'
362 ' --install-scripts="%s" %s >%s 2>&1'
363 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
363 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome,
364 installerrs))
364 installerrs))
365 vlog("# Running", cmd)
365 vlog("# Running", cmd)
366 if os.system(cmd) == 0:
366 if os.system(cmd) == 0:
367 if not options.verbose:
367 if not options.verbose:
368 os.remove(installerrs)
368 os.remove(installerrs)
369 else:
369 else:
370 f = open(installerrs)
370 f = open(installerrs)
371 for line in f:
371 for line in f:
372 print line,
372 print line,
373 f.close()
373 f.close()
374 sys.exit(1)
374 sys.exit(1)
375 os.chdir(TESTDIR)
375 os.chdir(TESTDIR)
376
376
377 usecorrectpython()
377 usecorrectpython()
378
378
379 vlog("# Installing dummy diffstat")
379 vlog("# Installing dummy diffstat")
380 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
380 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
381 f.write('#!' + sys.executable + '\n'
381 f.write('#!' + sys.executable + '\n'
382 'import sys\n'
382 'import sys\n'
383 'files = 0\n'
383 'files = 0\n'
384 'for line in sys.stdin:\n'
384 'for line in sys.stdin:\n'
385 ' if line.startswith("diff "):\n'
385 ' if line.startswith("diff "):\n'
386 ' files += 1\n'
386 ' files += 1\n'
387 'sys.stdout.write("files patched: %d\\n" % files)\n')
387 'sys.stdout.write("files patched: %d\\n" % files)\n')
388 f.close()
388 f.close()
389 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
389 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
390
390
391 if options.py3k_warnings and not options.anycoverage:
391 if options.py3k_warnings and not options.anycoverage:
392 vlog("# Updating hg command to enable Py3k Warnings switch")
392 vlog("# Updating hg command to enable Py3k Warnings switch")
393 f = open(os.path.join(BINDIR, 'hg'), 'r')
393 f = open(os.path.join(BINDIR, 'hg'), 'r')
394 lines = [line.rstrip() for line in f]
394 lines = [line.rstrip() for line in f]
395 lines[0] += ' -3'
395 lines[0] += ' -3'
396 f.close()
396 f.close()
397 f = open(os.path.join(BINDIR, 'hg'), 'w')
397 f = open(os.path.join(BINDIR, 'hg'), 'w')
398 for line in lines:
398 for line in lines:
399 f.write(line + '\n')
399 f.write(line + '\n')
400 f.close()
400 f.close()
401
401
402 if options.anycoverage:
402 if options.anycoverage:
403 custom = os.path.join(TESTDIR, 'sitecustomize.py')
403 custom = os.path.join(TESTDIR, 'sitecustomize.py')
404 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
404 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
405 vlog('# Installing coverage trigger to %s' % target)
405 vlog('# Installing coverage trigger to %s' % target)
406 shutil.copyfile(custom, target)
406 shutil.copyfile(custom, target)
407 rc = os.path.join(TESTDIR, '.coveragerc')
407 rc = os.path.join(TESTDIR, '.coveragerc')
408 vlog('# Installing coverage rc to %s' % rc)
408 vlog('# Installing coverage rc to %s' % rc)
409 os.environ['COVERAGE_PROCESS_START'] = rc
409 os.environ['COVERAGE_PROCESS_START'] = rc
410 fn = os.path.join(INST, '..', '.coverage')
410 fn = os.path.join(INST, '..', '.coverage')
411 os.environ['COVERAGE_FILE'] = fn
411 os.environ['COVERAGE_FILE'] = fn
412
412
413 def outputcoverage(options):
413 def outputcoverage(options):
414
414
415 vlog('# Producing coverage report')
415 vlog('# Producing coverage report')
416 os.chdir(PYTHONDIR)
416 os.chdir(PYTHONDIR)
417
417
418 def covrun(*args):
418 def covrun(*args):
419 cmd = 'coverage %s' % ' '.join(args)
419 cmd = 'coverage %s' % ' '.join(args)
420 vlog('# Running: %s' % cmd)
420 vlog('# Running: %s' % cmd)
421 os.system(cmd)
421 os.system(cmd)
422
422
423 if options.child:
423 if options.child:
424 return
424 return
425
425
426 covrun('-c')
426 covrun('-c')
427 omit = ','.join([BINDIR, TESTDIR])
427 omit = ','.join([BINDIR, TESTDIR])
428 covrun('-i', '-r', '"--omit=%s"' % omit) # report
428 covrun('-i', '-r', '"--omit=%s"' % omit) # report
429 if options.annotate:
429 if options.annotate:
430 adir = os.path.join(TESTDIR, 'annotated')
430 adir = os.path.join(TESTDIR, 'annotated')
431 if not os.path.isdir(adir):
431 if not os.path.isdir(adir):
432 os.mkdir(adir)
432 os.mkdir(adir)
433 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
433 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
434
434
435 class Timeout(Exception):
435 class Timeout(Exception):
436 pass
436 pass
437
437
438 def alarmed(signum, frame):
438 def alarmed(signum, frame):
439 raise Timeout
439 raise Timeout
440
440
441 def run(cmd, options):
441 def run(cmd, options):
442 """Run command in a sub-process, capturing the output (stdout and stderr).
442 """Run command in a sub-process, capturing the output (stdout and stderr).
443 Return a tuple (exitcode, output). output is None in debug mode."""
443 Return a tuple (exitcode, output). output is None in debug mode."""
444 # TODO: Use subprocess.Popen if we're running on Python 2.4
444 # TODO: Use subprocess.Popen if we're running on Python 2.4
445 if options.debug:
445 if options.debug:
446 proc = subprocess.Popen(cmd, shell=True)
446 proc = subprocess.Popen(cmd, shell=True)
447 ret = proc.wait()
447 ret = proc.wait()
448 return (ret, None)
448 return (ret, None)
449
449
450 if os.name == 'nt' or sys.platform.startswith('java'):
450 if os.name == 'nt' or sys.platform.startswith('java'):
451 tochild, fromchild = os.popen4(cmd)
451 tochild, fromchild = os.popen4(cmd)
452 tochild.close()
452 tochild.close()
453 output = fromchild.read()
453 output = fromchild.read()
454 ret = fromchild.close()
454 ret = fromchild.close()
455 if ret == None:
455 if ret == None:
456 ret = 0
456 ret = 0
457 else:
457 else:
458 proc = Popen4(cmd)
458 proc = Popen4(cmd)
459 def cleanup():
459 def cleanup():
460 os.kill(proc.pid, signal.SIGTERM)
460 os.kill(proc.pid, signal.SIGTERM)
461 ret = proc.wait()
461 ret = proc.wait()
462 if ret == 0:
462 if ret == 0:
463 ret = signal.SIGTERM << 8
463 ret = signal.SIGTERM << 8
464 killdaemons()
464 killdaemons()
465 return ret
465 return ret
466
466
467 try:
467 try:
468 output = ''
468 output = ''
469 proc.tochild.close()
469 proc.tochild.close()
470 output = proc.fromchild.read()
470 output = proc.fromchild.read()
471 ret = proc.wait()
471 ret = proc.wait()
472 if os.WIFEXITED(ret):
472 if os.WIFEXITED(ret):
473 ret = os.WEXITSTATUS(ret)
473 ret = os.WEXITSTATUS(ret)
474 except Timeout:
474 except Timeout:
475 vlog('# Process %d timed out - killing it' % proc.pid)
475 vlog('# Process %d timed out - killing it' % proc.pid)
476 ret = cleanup()
476 ret = cleanup()
477 output += ("\n### Abort: timeout after %d seconds.\n"
477 output += ("\n### Abort: timeout after %d seconds.\n"
478 % options.timeout)
478 % options.timeout)
479 except KeyboardInterrupt:
479 except KeyboardInterrupt:
480 vlog('# Handling keyboard interrupt')
480 vlog('# Handling keyboard interrupt')
481 cleanup()
481 cleanup()
482 raise
482 raise
483
483
484 return ret, splitnewlines(output)
484 return ret, splitnewlines(output)
485
485
486 def runone(options, test, skips, fails):
486 def runone(options, test, skips, fails):
487 '''tristate output:
487 '''tristate output:
488 None -> skipped
488 None -> skipped
489 True -> passed
489 True -> passed
490 False -> failed'''
490 False -> failed'''
491
491
492 def skip(msg):
492 def skip(msg):
493 if not options.verbose:
493 if not options.verbose:
494 skips.append((test, msg))
494 skips.append((test, msg))
495 else:
495 else:
496 print "\nSkipping %s: %s" % (testpath, msg)
496 print "\nSkipping %s: %s" % (testpath, msg)
497 return None
497 return None
498
498
499 def fail(msg):
499 def fail(msg):
500 fails.append((test, msg))
500 fails.append((test, msg))
501 if not options.nodiff:
501 if not options.nodiff:
502 print "\nERROR: %s %s" % (testpath, msg)
502 print "\nERROR: %s %s" % (testpath, msg)
503 return None
503 return None
504
504
505 vlog("# Test", test)
505 vlog("# Test", test)
506
506
507 # create a fresh hgrc
507 # create a fresh hgrc
508 hgrc = open(HGRCPATH, 'w+')
508 hgrc = open(HGRCPATH, 'w+')
509 hgrc.write('[ui]\n')
509 hgrc.write('[ui]\n')
510 hgrc.write('slash = True\n')
510 hgrc.write('slash = True\n')
511 hgrc.write('[defaults]\n')
511 hgrc.write('[defaults]\n')
512 hgrc.write('backout = -d "0 0"\n')
512 hgrc.write('backout = -d "0 0"\n')
513 hgrc.write('commit = -d "0 0"\n')
513 hgrc.write('commit = -d "0 0"\n')
514 hgrc.write('tag = -d "0 0"\n')
514 hgrc.write('tag = -d "0 0"\n')
515 if options.inotify:
515 if options.inotify:
516 hgrc.write('[extensions]\n')
516 hgrc.write('[extensions]\n')
517 hgrc.write('inotify=\n')
517 hgrc.write('inotify=\n')
518 hgrc.write('[inotify]\n')
518 hgrc.write('[inotify]\n')
519 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
519 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
520 hgrc.write('appendpid=True\n')
520 hgrc.write('appendpid=True\n')
521 hgrc.close()
521 hgrc.close()
522
522
523 testpath = os.path.join(TESTDIR, test)
523 testpath = os.path.join(TESTDIR, test)
524 ref = os.path.join(TESTDIR, test+".out")
524 ref = os.path.join(TESTDIR, test+".out")
525 err = os.path.join(TESTDIR, test+".err")
525 err = os.path.join(TESTDIR, test+".err")
526 if os.path.exists(err):
526 if os.path.exists(err):
527 os.remove(err) # Remove any previous output files
527 os.remove(err) # Remove any previous output files
528 try:
528 try:
529 tf = open(testpath)
529 tf = open(testpath)
530 firstline = tf.readline().rstrip()
530 firstline = tf.readline().rstrip()
531 tf.close()
531 tf.close()
532 except:
532 except:
533 firstline = ''
533 firstline = ''
534 lctest = test.lower()
534 lctest = test.lower()
535
535
536 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
536 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
537 py3kswitch = options.py3k_warnings and ' -3' or ''
537 py3kswitch = options.py3k_warnings and ' -3' or ''
538 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
538 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath)
539 elif lctest.endswith('.bat'):
539 elif lctest.endswith('.bat'):
540 # do not run batch scripts on non-windows
540 # do not run batch scripts on non-windows
541 if os.name != 'nt':
541 if os.name != 'nt':
542 return skip("batch script")
542 return skip("batch script")
543 # To reliably get the error code from batch files on WinXP,
543 # To reliably get the error code from batch files on WinXP,
544 # the "cmd /c call" prefix is needed. Grrr
544 # the "cmd /c call" prefix is needed. Grrr
545 cmd = 'cmd /c call "%s"' % testpath
545 cmd = 'cmd /c call "%s"' % testpath
546 else:
546 else:
547 # do not run shell scripts on windows
547 # do not run shell scripts on windows
548 if os.name == 'nt':
548 if os.name == 'nt':
549 return skip("shell script")
549 return skip("shell script")
550 # do not try to run non-executable programs
550 # do not try to run non-executable programs
551 if not os.path.exists(testpath):
551 if not os.path.exists(testpath):
552 return fail("does not exist")
552 return fail("does not exist")
553 elif not os.access(testpath, os.X_OK):
553 elif not os.access(testpath, os.X_OK):
554 return skip("not executable")
554 return skip("not executable")
555 cmd = '"%s"' % testpath
555 cmd = '"%s"' % testpath
556
556
557 # Make a tmp subdirectory to work in
557 # Make a tmp subdirectory to work in
558 tmpd = os.path.join(HGTMP, test)
558 tmpd = os.path.join(HGTMP, test)
559 os.mkdir(tmpd)
559 os.mkdir(tmpd)
560 os.chdir(tmpd)
560 os.chdir(tmpd)
561
561
562 if options.timeout > 0:
562 if options.timeout > 0:
563 signal.alarm(options.timeout)
563 signal.alarm(options.timeout)
564
564
565 vlog("# Running", cmd)
565 vlog("# Running", cmd)
566 ret, out = run(cmd, options)
566 ret, out = run(cmd, options)
567 vlog("# Ret was:", ret)
567 vlog("# Ret was:", ret)
568
568
569 if options.timeout > 0:
569 if options.timeout > 0:
570 signal.alarm(0)
570 signal.alarm(0)
571
571
572 mark = '.'
572 mark = '.'
573
573
574 skipped = (ret == SKIPPED_STATUS)
574 skipped = (ret == SKIPPED_STATUS)
575 # If we're not in --debug mode and reference output file exists,
575 # If we're not in --debug mode and reference output file exists,
576 # check test output against it.
576 # check test output against it.
577 if options.debug:
577 if options.debug:
578 refout = None # to match out == None
578 refout = None # to match out == None
579 elif os.path.exists(ref):
579 elif os.path.exists(ref):
580 f = open(ref, "r")
580 f = open(ref, "r")
581 refout = splitnewlines(f.read())
581 refout = splitnewlines(f.read())
582 f.close()
582 f.close()
583 else:
583 else:
584 refout = []
584 refout = []
585
585
586 if skipped:
586 if skipped:
587 mark = 's'
587 mark = 's'
588 if out is None: # debug mode: nothing to parse
588 if out is None: # debug mode: nothing to parse
589 missing = ['unknown']
589 missing = ['unknown']
590 failed = None
590 failed = None
591 else:
591 else:
592 missing, failed = parsehghaveoutput(out)
592 missing, failed = parsehghaveoutput(out)
593 if not missing:
593 if not missing:
594 missing = ['irrelevant']
594 missing = ['irrelevant']
595 if failed:
595 if failed:
596 fail("hghave failed checking for %s" % failed[-1])
596 fail("hghave failed checking for %s" % failed[-1])
597 skipped = False
597 skipped = False
598 else:
598 else:
599 skip(missing[-1])
599 skip(missing[-1])
600 elif out != refout:
600 elif out != refout:
601 mark = '!'
601 mark = '!'
602 if ret:
602 if ret:
603 fail("output changed and returned error code %d" % ret)
603 fail("output changed and returned error code %d" % ret)
604 else:
604 else:
605 fail("output changed")
605 fail("output changed")
606 if not options.nodiff:
606 if not options.nodiff:
607 showdiff(refout, out, ref, err)
607 showdiff(refout, out, ref, err)
608 ret = 1
608 ret = 1
609 elif ret:
609 elif ret:
610 mark = '!'
610 mark = '!'
611 fail("returned error code %d" % ret)
611 fail("returned error code %d" % ret)
612
612
613 if not options.verbose:
613 if not options.verbose:
614 sys.stdout.write(mark)
614 sys.stdout.write(mark)
615 sys.stdout.flush()
615 sys.stdout.flush()
616
616
617 if ret != 0 and not skipped and not options.debug:
617 if ret != 0 and not skipped and not options.debug:
618 # Save errors to a file for diagnosis
618 # Save errors to a file for diagnosis
619 f = open(err, "wb")
619 f = open(err, "wb")
620 for line in out:
620 for line in out:
621 f.write(line)
621 f.write(line)
622 f.close()
622 f.close()
623
623
624 killdaemons()
624 killdaemons()
625
625
626 os.chdir(TESTDIR)
626 os.chdir(TESTDIR)
627 if not options.keep_tmpdir:
627 if not options.keep_tmpdir:
628 shutil.rmtree(tmpd, True)
628 shutil.rmtree(tmpd, True)
629 if skipped:
629 if skipped:
630 return None
630 return None
631 return ret == 0
631 return ret == 0
632
632
633 _hgpath = None
633 _hgpath = None
634
634
635 def _gethgpath():
635 def _gethgpath():
636 """Return the path to the mercurial package that is actually found by
636 """Return the path to the mercurial package that is actually found by
637 the current Python interpreter."""
637 the current Python interpreter."""
638 global _hgpath
638 global _hgpath
639 if _hgpath is not None:
639 if _hgpath is not None:
640 return _hgpath
640 return _hgpath
641
641
642 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
642 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
643 pipe = os.popen(cmd % PYTHON)
643 pipe = os.popen(cmd % PYTHON)
644 try:
644 try:
645 _hgpath = pipe.read().strip()
645 _hgpath = pipe.read().strip()
646 finally:
646 finally:
647 pipe.close()
647 pipe.close()
648 return _hgpath
648 return _hgpath
649
649
650 def _checkhglib(verb):
650 def _checkhglib(verb):
651 """Ensure that the 'mercurial' package imported by python is
651 """Ensure that the 'mercurial' package imported by python is
652 the one we expect it to be. If not, print a warning to stderr."""
652 the one we expect it to be. If not, print a warning to stderr."""
653 expecthg = os.path.join(PYTHONDIR, 'mercurial')
653 expecthg = os.path.join(PYTHONDIR, 'mercurial')
654 actualhg = _gethgpath()
654 actualhg = _gethgpath()
655 if actualhg != expecthg:
655 if actualhg != expecthg:
656 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
656 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
657 ' (expected %s)\n'
657 ' (expected %s)\n'
658 % (verb, actualhg, expecthg))
658 % (verb, actualhg, expecthg))
659
659
660 def runchildren(options, tests):
660 def runchildren(options, tests):
661 if INST:
661 if INST:
662 installhg(options)
662 installhg(options)
663 _checkhglib("Testing")
663 _checkhglib("Testing")
664
664
665 optcopy = dict(options.__dict__)
665 optcopy = dict(options.__dict__)
666 optcopy['jobs'] = 1
666 optcopy['jobs'] = 1
667 if optcopy['with_hg'] is None:
667 if optcopy['with_hg'] is None:
668 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
668 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
669 optcopy.pop('anycoverage', None)
669 optcopy.pop('anycoverage', None)
670
670
671 opts = []
671 opts = []
672 for opt, value in optcopy.iteritems():
672 for opt, value in optcopy.iteritems():
673 name = '--' + opt.replace('_', '-')
673 name = '--' + opt.replace('_', '-')
674 if value is True:
674 if value is True:
675 opts.append(name)
675 opts.append(name)
676 elif value is not None:
676 elif value is not None:
677 opts.append(name + '=' + str(value))
677 opts.append(name + '=' + str(value))
678
678
679 tests.reverse()
679 tests.reverse()
680 jobs = [[] for j in xrange(options.jobs)]
680 jobs = [[] for j in xrange(options.jobs)]
681 while tests:
681 while tests:
682 for job in jobs:
682 for job in jobs:
683 if not tests:
683 if not tests:
684 break
684 break
685 job.append(tests.pop())
685 job.append(tests.pop())
686 fps = {}
686 fps = {}
687
687
688 for j, job in enumerate(jobs):
688 for j, job in enumerate(jobs):
689 if not job:
689 if not job:
690 continue
690 continue
691 rfd, wfd = os.pipe()
691 rfd, wfd = os.pipe()
692 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
692 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
693 childtmp = os.path.join(HGTMP, 'child%d' % j)
693 childtmp = os.path.join(HGTMP, 'child%d' % j)
694 childopts += ['--tmpdir', childtmp]
694 childopts += ['--tmpdir', childtmp]
695 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
695 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
696 vlog(' '.join(cmdline))
696 vlog(' '.join(cmdline))
697 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
697 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
698 os.close(wfd)
698 os.close(wfd)
699 signal.signal(signal.SIGINT, signal.SIG_IGN)
699 signal.signal(signal.SIGINT, signal.SIG_IGN)
700 failures = 0
700 failures = 0
701 tested, skipped, failed = 0, 0, 0
701 tested, skipped, failed = 0, 0, 0
702 skips = []
702 skips = []
703 fails = []
703 fails = []
704 while fps:
704 while fps:
705 pid, status = os.wait()
705 pid, status = os.wait()
706 fp = fps.pop(pid)
706 fp = fps.pop(pid)
707 l = fp.read().splitlines()
707 l = fp.read().splitlines()
708 try:
708 try:
709 test, skip, fail = map(int, l[:3])
709 test, skip, fail = map(int, l[:3])
710 except ValueError:
710 except ValueError:
711 test, skip, fail = 0, 0, 0
711 test, skip, fail = 0, 0, 0
712 split = -fail or len(l)
712 split = -fail or len(l)
713 for s in l[3:split]:
713 for s in l[3:split]:
714 skips.append(s.split(" ", 1))
714 skips.append(s.split(" ", 1))
715 for s in l[split:]:
715 for s in l[split:]:
716 fails.append(s.split(" ", 1))
716 fails.append(s.split(" ", 1))
717 tested += test
717 tested += test
718 skipped += skip
718 skipped += skip
719 failed += fail
719 failed += fail
720 vlog('pid %d exited, status %d' % (pid, status))
720 vlog('pid %d exited, status %d' % (pid, status))
721 failures |= status
721 failures |= status
722 print
722 print
723 if not options.noskips:
723 if not options.noskips:
724 for s in skips:
724 for s in skips:
725 print "Skipped %s: %s" % (s[0], s[1])
725 print "Skipped %s: %s" % (s[0], s[1])
726 for s in fails:
726 for s in fails:
727 print "Failed %s: %s" % (s[0], s[1])
727 print "Failed %s: %s" % (s[0], s[1])
728
728
729 _checkhglib("Tested")
729 _checkhglib("Tested")
730 print "# Ran %d tests, %d skipped, %d failed." % (
730 print "# Ran %d tests, %d skipped, %d failed." % (
731 tested, skipped, failed)
731 tested, skipped, failed)
732
732
733 if options.anycoverage:
733 if options.anycoverage:
734 outputcoverage(options)
734 outputcoverage(options)
735 sys.exit(failures != 0)
735 sys.exit(failures != 0)
736
736
737 def runtests(options, tests):
737 def runtests(options, tests):
738 global DAEMON_PIDS, HGRCPATH
738 global DAEMON_PIDS, HGRCPATH
739 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
739 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
740 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
740 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
741
741
742 try:
742 try:
743 if INST:
743 if INST:
744 installhg(options)
744 installhg(options)
745 _checkhglib("Testing")
745 _checkhglib("Testing")
746
746
747 if options.timeout > 0:
747 if options.timeout > 0:
748 try:
748 try:
749 signal.signal(signal.SIGALRM, alarmed)
749 signal.signal(signal.SIGALRM, alarmed)
750 vlog('# Running each test with %d second timeout' %
750 vlog('# Running each test with %d second timeout' %
751 options.timeout)
751 options.timeout)
752 except AttributeError:
752 except AttributeError:
753 print 'WARNING: cannot run tests with timeouts'
753 print 'WARNING: cannot run tests with timeouts'
754 options.timeout = 0
754 options.timeout = 0
755
755
756 tested = 0
756 tested = 0
757 failed = 0
757 failed = 0
758 skipped = 0
758 skipped = 0
759
759
760 if options.restart:
760 if options.restart:
761 orig = list(tests)
761 orig = list(tests)
762 while tests:
762 while tests:
763 if os.path.exists(tests[0] + ".err"):
763 if os.path.exists(tests[0] + ".err"):
764 break
764 break
765 tests.pop(0)
765 tests.pop(0)
766 if not tests:
766 if not tests:
767 print "running all tests"
767 print "running all tests"
768 tests = orig
768 tests = orig
769
769
770 skips = []
770 skips = []
771 fails = []
771 fails = []
772
772
773 for test in tests:
773 for test in tests:
774 if options.blacklist:
774 if options.blacklist:
775 filename = options.blacklist.get(test)
775 filename = options.blacklist.get(test)
776 if filename is not None:
776 if filename is not None:
777 skips.append((test, "blacklisted (%s)" % filename))
777 skips.append((test, "blacklisted (%s)" % filename))
778 skipped += 1
778 skipped += 1
779 continue
779 continue
780
780
781 if options.retest and not os.path.exists(test + ".err"):
781 if options.retest and not os.path.exists(test + ".err"):
782 skipped += 1
782 skipped += 1
783 continue
783 continue
784
784
785 if options.keywords:
785 if options.keywords:
786 t = open(test).read().lower() + test.lower()
786 t = open(test).read().lower() + test.lower()
787 for k in options.keywords.lower().split():
787 for k in options.keywords.lower().split():
788 if k in t:
788 if k in t:
789 break
789 break
790 else:
790 else:
791 skipped += 1
791 skipped += 1
792 continue
792 continue
793
793
794 ret = runone(options, test, skips, fails)
794 ret = runone(options, test, skips, fails)
795 if ret is None:
795 if ret is None:
796 skipped += 1
796 skipped += 1
797 elif not ret:
797 elif not ret:
798 if options.interactive:
798 if options.interactive:
799 print "Accept this change? [n] ",
799 print "Accept this change? [n] ",
800 answer = sys.stdin.readline().strip()
800 answer = sys.stdin.readline().strip()
801 if answer.lower() in "y yes".split():
801 if answer.lower() in "y yes".split():
802 rename(test + ".err", test + ".out")
802 rename(test + ".err", test + ".out")
803 tested += 1
803 tested += 1
804 fails.pop()
804 fails.pop()
805 continue
805 continue
806 failed += 1
806 failed += 1
807 if options.first:
807 if options.first:
808 break
808 break
809 tested += 1
809 tested += 1
810
810
811 if options.child:
811 if options.child:
812 fp = os.fdopen(options.child, 'w')
812 fp = os.fdopen(options.child, 'w')
813 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
813 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
814 for s in skips:
814 for s in skips:
815 fp.write("%s %s\n" % s)
815 fp.write("%s %s\n" % s)
816 for s in fails:
816 for s in fails:
817 fp.write("%s %s\n" % s)
817 fp.write("%s %s\n" % s)
818 fp.close()
818 fp.close()
819 else:
819 else:
820 print
820 print
821 for s in skips:
821 for s in skips:
822 print "Skipped %s: %s" % s
822 print "Skipped %s: %s" % s
823 for s in fails:
823 for s in fails:
824 print "Failed %s: %s" % s
824 print "Failed %s: %s" % s
825 _checkhglib("Tested")
825 _checkhglib("Tested")
826 print "# Ran %d tests, %d skipped, %d failed." % (
826 print "# Ran %d tests, %d skipped, %d failed." % (
827 tested, skipped, failed)
827 tested, skipped, failed)
828
828
829 if options.anycoverage:
829 if options.anycoverage:
830 outputcoverage(options)
830 outputcoverage(options)
831 except KeyboardInterrupt:
831 except KeyboardInterrupt:
832 failed = True
832 failed = True
833 print "\ninterrupted!"
833 print "\ninterrupted!"
834
834
835 if failed:
835 if failed:
836 sys.exit(1)
836 sys.exit(1)
837
837
838 def main():
838 def main():
839 (options, args) = parseargs()
839 (options, args) = parseargs()
840 if not options.child:
840 if not options.child:
841 os.umask(022)
841 os.umask(022)
842
842
843 checktools()
843 checktools()
844
844
845 # Reset some environment variables to well-known values so that
845 # Reset some environment variables to well-known values so that
846 # the tests produce repeatable output.
846 # the tests produce repeatable output.
847 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
847 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
848 os.environ['TZ'] = 'GMT'
848 os.environ['TZ'] = 'GMT'
849 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
849 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
850 os.environ['CDPATH'] = ''
850 os.environ['CDPATH'] = ''
851 os.environ['COLUMNS'] = '80'
851 os.environ['COLUMNS'] = '80'
852 os.environ['GREP_OPTIONS'] = ''
852 os.environ['GREP_OPTIONS'] = ''
853 os.environ['http_proxy'] = ''
853 os.environ['http_proxy'] = ''
854
854
855 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
855 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
856 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
856 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
857 if options.tmpdir:
857 if options.tmpdir:
858 options.keep_tmpdir = True
858 options.keep_tmpdir = True
859 tmpdir = options.tmpdir
859 tmpdir = options.tmpdir
860 if os.path.exists(tmpdir):
860 if os.path.exists(tmpdir):
861 # Meaning of tmpdir has changed since 1.3: we used to create
861 # Meaning of tmpdir has changed since 1.3: we used to create
862 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
862 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
863 # tmpdir already exists.
863 # tmpdir already exists.
864 sys.exit("error: temp dir %r already exists" % tmpdir)
864 sys.exit("error: temp dir %r already exists" % tmpdir)
865
865
866 # Automatically removing tmpdir sounds convenient, but could
866 # Automatically removing tmpdir sounds convenient, but could
867 # really annoy anyone in the habit of using "--tmpdir=/tmp"
867 # really annoy anyone in the habit of using "--tmpdir=/tmp"
868 # or "--tmpdir=$HOME".
868 # or "--tmpdir=$HOME".
869 #vlog("# Removing temp dir", tmpdir)
869 #vlog("# Removing temp dir", tmpdir)
870 #shutil.rmtree(tmpdir)
870 #shutil.rmtree(tmpdir)
871 os.makedirs(tmpdir)
871 os.makedirs(tmpdir)
872 else:
872 else:
873 tmpdir = tempfile.mkdtemp('', 'hgtests.')
873 tmpdir = tempfile.mkdtemp('', 'hgtests.')
874 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
874 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
875 DAEMON_PIDS = None
875 DAEMON_PIDS = None
876 HGRCPATH = None
876 HGRCPATH = None
877
877
878 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
878 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
879 os.environ["HGMERGE"] = "internal:merge"
879 os.environ["HGMERGE"] = "internal:merge"
880 os.environ["HGUSER"] = "test"
880 os.environ["HGUSER"] = "test"
881 os.environ["HGENCODING"] = "ascii"
881 os.environ["HGENCODING"] = "ascii"
882 os.environ["HGENCODINGMODE"] = "strict"
882 os.environ["HGENCODINGMODE"] = "strict"
883 os.environ["HGPORT"] = str(options.port)
883 os.environ["HGPORT"] = str(options.port)
884 os.environ["HGPORT1"] = str(options.port + 1)
884 os.environ["HGPORT1"] = str(options.port + 1)
885 os.environ["HGPORT2"] = str(options.port + 2)
885 os.environ["HGPORT2"] = str(options.port + 2)
886
886
887 if options.with_hg:
887 if options.with_hg:
888 INST = None
888 INST = None
889 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
889 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
890
890
891 # This looks redundant with how Python initializes sys.path from
891 # This looks redundant with how Python initializes sys.path from
892 # the location of the script being executed. Needed because the
892 # the location of the script being executed. Needed because the
893 # "hg" specified by --with-hg is not the only Python script
893 # "hg" specified by --with-hg is not the only Python script
894 # executed in the test suite that needs to import 'mercurial'
894 # executed in the test suite that needs to import 'mercurial'
895 # ... which means it's not really redundant at all.
895 # ... which means it's not really redundant at all.
896 PYTHONDIR = BINDIR
896 PYTHONDIR = BINDIR
897 else:
897 else:
898 INST = os.path.join(HGTMP, "install")
898 INST = os.path.join(HGTMP, "install")
899 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
899 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
900 PYTHONDIR = os.path.join(INST, "lib", "python")
900 PYTHONDIR = os.path.join(INST, "lib", "python")
901
901
902 os.environ["BINDIR"] = BINDIR
902 os.environ["BINDIR"] = BINDIR
903 os.environ["PYTHON"] = PYTHON
903 os.environ["PYTHON"] = PYTHON
904
904
905 if not options.child:
905 if not options.child:
906 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
906 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
907 os.environ["PATH"] = os.pathsep.join(path)
907 os.environ["PATH"] = os.pathsep.join(path)
908
908
909 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
909 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
910 # can run .../tests/run-tests.py test-foo where test-foo
910 # can run .../tests/run-tests.py test-foo where test-foo
911 # adds an extension to HGRC
911 # adds an extension to HGRC
912 pypath = [PYTHONDIR, TESTDIR]
912 pypath = [PYTHONDIR, TESTDIR]
913 # We have to augment PYTHONPATH, rather than simply replacing
913 # We have to augment PYTHONPATH, rather than simply replacing
914 # it, in case external libraries are only available via current
914 # it, in case external libraries are only available via current
915 # PYTHONPATH. (In particular, the Subversion bindings on OS X
915 # PYTHONPATH. (In particular, the Subversion bindings on OS X
916 # are in /opt/subversion.)
916 # are in /opt/subversion.)
917 oldpypath = os.environ.get(IMPL_PATH)
917 oldpypath = os.environ.get(IMPL_PATH)
918 if oldpypath:
918 if oldpypath:
919 pypath.append(oldpypath)
919 pypath.append(oldpypath)
920 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
920 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
921
921
922 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
922 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
923
923
924 if len(args) == 0:
924 if len(args) == 0:
925 args = os.listdir(".")
925 args = os.listdir(".")
926 args.sort()
926 args.sort()
927
927
928 tests = []
928 tests = []
929 for test in args:
929 for test in args:
930 if (test.startswith("test-") and '~' not in test and
930 if (test.startswith("test-") and '~' not in test and
931 ('.' not in test or test.endswith('.py') or
931 ('.' not in test or test.endswith('.py') or
932 test.endswith('.bat'))):
932 test.endswith('.bat'))):
933 tests.append(test)
933 tests.append(test)
934 if not tests:
934 if not tests:
935 print "# Ran 0 tests, 0 skipped, 0 failed."
935 print "# Ran 0 tests, 0 skipped, 0 failed."
936 return
936 return
937
937
938 vlog("# Using TESTDIR", TESTDIR)
938 vlog("# Using TESTDIR", TESTDIR)
939 vlog("# Using HGTMP", HGTMP)
939 vlog("# Using HGTMP", HGTMP)
940 vlog("# Using PATH", os.environ["PATH"])
940 vlog("# Using PATH", os.environ["PATH"])
941 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
941 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
942
942
943 try:
943 try:
944 if len(tests) > 1 and options.jobs > 1:
944 if len(tests) > 1 and options.jobs > 1:
945 runchildren(options, tests)
945 runchildren(options, tests)
946 else:
946 else:
947 runtests(options, tests)
947 runtests(options, tests)
948 finally:
948 finally:
949 time.sleep(1)
949 time.sleep(1)
950 cleanup(options)
950 cleanup(options)
951
951
952 main()
952 main()
General Comments 0
You need to be logged in to leave comments. Login now