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