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