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