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