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