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