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