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