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