##// END OF EJS Templates
tests: catch re.error if test line is not a valid regular expression
Nicolas Dumazet -
r11781:6f59154f default
parent child Browse files
Show More
@@ -1,1051 +1,1058 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 fd, name = tempfile.mkstemp(suffix='hg-tst')
489 fd, name = tempfile.mkstemp(suffix='hg-tst')
490
490
491 try:
491 try:
492 for l in script:
492 for l in script:
493 os.write(fd, l)
493 os.write(fd, l)
494 os.close(fd)
494 os.close(fd)
495
495
496 cmd = '/bin/sh "%s"' % name
496 cmd = '/bin/sh "%s"' % name
497 vlog("# Running", cmd)
497 vlog("# Running", cmd)
498 exitcode, output = run(cmd, options)
498 exitcode, output = run(cmd, options)
499 finally:
499 finally:
500 os.remove(name)
500 os.remove(name)
501
501
502 def rematch(el, l):
503 try:
504 return re.match(el, l)
505 except re.error:
506 # el is an invalid regex
507 return False
508
502 pos = -1
509 pos = -1
503 postout = []
510 postout = []
504 for n, l in enumerate(output):
511 for n, l in enumerate(output):
505 if l.startswith(salt):
512 if l.startswith(salt):
506 if pos in after:
513 if pos in after:
507 postout += after.pop(pos)
514 postout += after.pop(pos)
508 pos = int(l.split()[1])
515 pos = int(l.split()[1])
509 else:
516 else:
510 el = None
517 el = None
511 if pos in expected and expected[pos]:
518 if pos in expected and expected[pos]:
512 el = expected[pos].pop(0)
519 el = expected[pos].pop(0)
513
520
514 if el == l: # perfect match (fast)
521 if el == l: # perfect match (fast)
515 postout.append(" " + l)
522 postout.append(" " + l)
516 elif el and re.match(el, l): # fallback regex match
523 elif el and rematch(el, l): # fallback regex match
517 postout.append(" " + el)
524 postout.append(" " + el)
518 else: # mismatch - let diff deal with it
525 else: # mismatch - let diff deal with it
519 postout.append(" " + l)
526 postout.append(" " + l)
520
527
521 if pos in after:
528 if pos in after:
522 postout += after.pop(pos)
529 postout += after.pop(pos)
523
530
524 return exitcode, postout
531 return exitcode, postout
525
532
526 def run(cmd, options):
533 def run(cmd, options):
527 """Run command in a sub-process, capturing the output (stdout and stderr).
534 """Run command in a sub-process, capturing the output (stdout and stderr).
528 Return a tuple (exitcode, output). output is None in debug mode."""
535 Return a tuple (exitcode, output). output is None in debug mode."""
529 # TODO: Use subprocess.Popen if we're running on Python 2.4
536 # TODO: Use subprocess.Popen if we're running on Python 2.4
530 if options.debug:
537 if options.debug:
531 proc = subprocess.Popen(cmd, shell=True)
538 proc = subprocess.Popen(cmd, shell=True)
532 ret = proc.wait()
539 ret = proc.wait()
533 return (ret, None)
540 return (ret, None)
534
541
535 if os.name == 'nt' or sys.platform.startswith('java'):
542 if os.name == 'nt' or sys.platform.startswith('java'):
536 tochild, fromchild = os.popen4(cmd)
543 tochild, fromchild = os.popen4(cmd)
537 tochild.close()
544 tochild.close()
538 output = fromchild.read()
545 output = fromchild.read()
539 ret = fromchild.close()
546 ret = fromchild.close()
540 if ret == None:
547 if ret == None:
541 ret = 0
548 ret = 0
542 else:
549 else:
543 proc = Popen4(cmd)
550 proc = Popen4(cmd)
544 def cleanup():
551 def cleanup():
545 os.kill(proc.pid, signal.SIGTERM)
552 os.kill(proc.pid, signal.SIGTERM)
546 ret = proc.wait()
553 ret = proc.wait()
547 if ret == 0:
554 if ret == 0:
548 ret = signal.SIGTERM << 8
555 ret = signal.SIGTERM << 8
549 killdaemons()
556 killdaemons()
550 return ret
557 return ret
551
558
552 try:
559 try:
553 output = ''
560 output = ''
554 proc.tochild.close()
561 proc.tochild.close()
555 output = proc.fromchild.read()
562 output = proc.fromchild.read()
556 ret = proc.wait()
563 ret = proc.wait()
557 if os.WIFEXITED(ret):
564 if os.WIFEXITED(ret):
558 ret = os.WEXITSTATUS(ret)
565 ret = os.WEXITSTATUS(ret)
559 except Timeout:
566 except Timeout:
560 vlog('# Process %d timed out - killing it' % proc.pid)
567 vlog('# Process %d timed out - killing it' % proc.pid)
561 ret = cleanup()
568 ret = cleanup()
562 output += ("\n### Abort: timeout after %d seconds.\n"
569 output += ("\n### Abort: timeout after %d seconds.\n"
563 % options.timeout)
570 % options.timeout)
564 except KeyboardInterrupt:
571 except KeyboardInterrupt:
565 vlog('# Handling keyboard interrupt')
572 vlog('# Handling keyboard interrupt')
566 cleanup()
573 cleanup()
567 raise
574 raise
568
575
569 return ret, splitnewlines(output)
576 return ret, splitnewlines(output)
570
577
571 def runone(options, test, skips, fails):
578 def runone(options, test, skips, fails):
572 '''tristate output:
579 '''tristate output:
573 None -> skipped
580 None -> skipped
574 True -> passed
581 True -> passed
575 False -> failed'''
582 False -> failed'''
576
583
577 def skip(msg):
584 def skip(msg):
578 if not options.verbose:
585 if not options.verbose:
579 skips.append((test, msg))
586 skips.append((test, msg))
580 else:
587 else:
581 print "\nSkipping %s: %s" % (testpath, msg)
588 print "\nSkipping %s: %s" % (testpath, msg)
582 return None
589 return None
583
590
584 def fail(msg):
591 def fail(msg):
585 fails.append((test, msg))
592 fails.append((test, msg))
586 if not options.nodiff:
593 if not options.nodiff:
587 print "\nERROR: %s %s" % (testpath, msg)
594 print "\nERROR: %s %s" % (testpath, msg)
588 return None
595 return None
589
596
590 vlog("# Test", test)
597 vlog("# Test", test)
591
598
592 # create a fresh hgrc
599 # create a fresh hgrc
593 hgrc = open(HGRCPATH, 'w+')
600 hgrc = open(HGRCPATH, 'w+')
594 hgrc.write('[ui]\n')
601 hgrc.write('[ui]\n')
595 hgrc.write('slash = True\n')
602 hgrc.write('slash = True\n')
596 hgrc.write('[defaults]\n')
603 hgrc.write('[defaults]\n')
597 hgrc.write('backout = -d "0 0"\n')
604 hgrc.write('backout = -d "0 0"\n')
598 hgrc.write('commit = -d "0 0"\n')
605 hgrc.write('commit = -d "0 0"\n')
599 hgrc.write('tag = -d "0 0"\n')
606 hgrc.write('tag = -d "0 0"\n')
600 if options.inotify:
607 if options.inotify:
601 hgrc.write('[extensions]\n')
608 hgrc.write('[extensions]\n')
602 hgrc.write('inotify=\n')
609 hgrc.write('inotify=\n')
603 hgrc.write('[inotify]\n')
610 hgrc.write('[inotify]\n')
604 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
611 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
605 hgrc.write('appendpid=True\n')
612 hgrc.write('appendpid=True\n')
606 hgrc.close()
613 hgrc.close()
607
614
608 testpath = os.path.join(TESTDIR, test)
615 testpath = os.path.join(TESTDIR, test)
609 ref = os.path.join(TESTDIR, test+".out")
616 ref = os.path.join(TESTDIR, test+".out")
610 err = os.path.join(TESTDIR, test+".err")
617 err = os.path.join(TESTDIR, test+".err")
611 if os.path.exists(err):
618 if os.path.exists(err):
612 os.remove(err) # Remove any previous output files
619 os.remove(err) # Remove any previous output files
613 try:
620 try:
614 tf = open(testpath)
621 tf = open(testpath)
615 firstline = tf.readline().rstrip()
622 firstline = tf.readline().rstrip()
616 tf.close()
623 tf.close()
617 except:
624 except:
618 firstline = ''
625 firstline = ''
619 lctest = test.lower()
626 lctest = test.lower()
620
627
621 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
628 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
622 runner = pytest
629 runner = pytest
623 elif lctest.endswith('.bat'):
630 elif lctest.endswith('.bat'):
624 # do not run batch scripts on non-windows
631 # do not run batch scripts on non-windows
625 if os.name != 'nt':
632 if os.name != 'nt':
626 return skip("batch script")
633 return skip("batch script")
627 runner = battest
634 runner = battest
628 elif lctest.endswith('.t'):
635 elif lctest.endswith('.t'):
629 runner = tsttest
636 runner = tsttest
630 ref = testpath
637 ref = testpath
631 else:
638 else:
632 # do not run shell scripts on windows
639 # do not run shell scripts on windows
633 if os.name == 'nt':
640 if os.name == 'nt':
634 return skip("shell script")
641 return skip("shell script")
635 # do not try to run non-executable programs
642 # do not try to run non-executable programs
636 if not os.path.exists(testpath):
643 if not os.path.exists(testpath):
637 return fail("does not exist")
644 return fail("does not exist")
638 elif not os.access(testpath, os.X_OK):
645 elif not os.access(testpath, os.X_OK):
639 return skip("not executable")
646 return skip("not executable")
640 runner = shtest
647 runner = shtest
641
648
642 # Make a tmp subdirectory to work in
649 # Make a tmp subdirectory to work in
643 tmpd = os.path.join(HGTMP, test)
650 tmpd = os.path.join(HGTMP, test)
644 os.mkdir(tmpd)
651 os.mkdir(tmpd)
645 os.chdir(tmpd)
652 os.chdir(tmpd)
646
653
647 if options.timeout > 0:
654 if options.timeout > 0:
648 signal.alarm(options.timeout)
655 signal.alarm(options.timeout)
649
656
650 ret, out = runner(testpath, options)
657 ret, out = runner(testpath, options)
651 vlog("# Ret was:", ret)
658 vlog("# Ret was:", ret)
652
659
653 if options.timeout > 0:
660 if options.timeout > 0:
654 signal.alarm(0)
661 signal.alarm(0)
655
662
656 mark = '.'
663 mark = '.'
657
664
658 skipped = (ret == SKIPPED_STATUS)
665 skipped = (ret == SKIPPED_STATUS)
659
666
660 # If we're not in --debug mode and reference output file exists,
667 # If we're not in --debug mode and reference output file exists,
661 # check test output against it.
668 # check test output against it.
662 if options.debug:
669 if options.debug:
663 refout = None # to match out == None
670 refout = None # to match out == None
664 elif os.path.exists(ref):
671 elif os.path.exists(ref):
665 f = open(ref, "r")
672 f = open(ref, "r")
666 refout = splitnewlines(f.read())
673 refout = splitnewlines(f.read())
667 f.close()
674 f.close()
668 else:
675 else:
669 refout = []
676 refout = []
670
677
671 if (ret != 0 or out != refout) and not skipped and not options.debug:
678 if (ret != 0 or out != refout) and not skipped and not options.debug:
672 # Save errors to a file for diagnosis
679 # Save errors to a file for diagnosis
673 f = open(err, "wb")
680 f = open(err, "wb")
674 for line in out:
681 for line in out:
675 f.write(line)
682 f.write(line)
676 f.close()
683 f.close()
677
684
678 if skipped:
685 if skipped:
679 mark = 's'
686 mark = 's'
680 if out is None: # debug mode: nothing to parse
687 if out is None: # debug mode: nothing to parse
681 missing = ['unknown']
688 missing = ['unknown']
682 failed = None
689 failed = None
683 else:
690 else:
684 missing, failed = parsehghaveoutput(out)
691 missing, failed = parsehghaveoutput(out)
685 if not missing:
692 if not missing:
686 missing = ['irrelevant']
693 missing = ['irrelevant']
687 if failed:
694 if failed:
688 fail("hghave failed checking for %s" % failed[-1])
695 fail("hghave failed checking for %s" % failed[-1])
689 skipped = False
696 skipped = False
690 else:
697 else:
691 skip(missing[-1])
698 skip(missing[-1])
692 elif out != refout:
699 elif out != refout:
693 mark = '!'
700 mark = '!'
694 if ret:
701 if ret:
695 fail("output changed and returned error code %d" % ret)
702 fail("output changed and returned error code %d" % ret)
696 else:
703 else:
697 fail("output changed")
704 fail("output changed")
698 if not options.nodiff:
705 if not options.nodiff:
699 if options.view:
706 if options.view:
700 os.system("%s %s %s" % (options.view, ref, err))
707 os.system("%s %s %s" % (options.view, ref, err))
701 else:
708 else:
702 showdiff(refout, out, ref, err)
709 showdiff(refout, out, ref, err)
703 ret = 1
710 ret = 1
704 elif ret:
711 elif ret:
705 mark = '!'
712 mark = '!'
706 fail("returned error code %d" % ret)
713 fail("returned error code %d" % ret)
707
714
708 if not options.verbose:
715 if not options.verbose:
709 sys.stdout.write(mark)
716 sys.stdout.write(mark)
710 sys.stdout.flush()
717 sys.stdout.flush()
711
718
712 killdaemons()
719 killdaemons()
713
720
714 os.chdir(TESTDIR)
721 os.chdir(TESTDIR)
715 if not options.keep_tmpdir:
722 if not options.keep_tmpdir:
716 shutil.rmtree(tmpd, True)
723 shutil.rmtree(tmpd, True)
717 if skipped:
724 if skipped:
718 return None
725 return None
719 return ret == 0
726 return ret == 0
720
727
721 _hgpath = None
728 _hgpath = None
722
729
723 def _gethgpath():
730 def _gethgpath():
724 """Return the path to the mercurial package that is actually found by
731 """Return the path to the mercurial package that is actually found by
725 the current Python interpreter."""
732 the current Python interpreter."""
726 global _hgpath
733 global _hgpath
727 if _hgpath is not None:
734 if _hgpath is not None:
728 return _hgpath
735 return _hgpath
729
736
730 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
737 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
731 pipe = os.popen(cmd % PYTHON)
738 pipe = os.popen(cmd % PYTHON)
732 try:
739 try:
733 _hgpath = pipe.read().strip()
740 _hgpath = pipe.read().strip()
734 finally:
741 finally:
735 pipe.close()
742 pipe.close()
736 return _hgpath
743 return _hgpath
737
744
738 def _checkhglib(verb):
745 def _checkhglib(verb):
739 """Ensure that the 'mercurial' package imported by python is
746 """Ensure that the 'mercurial' package imported by python is
740 the one we expect it to be. If not, print a warning to stderr."""
747 the one we expect it to be. If not, print a warning to stderr."""
741 expecthg = os.path.join(PYTHONDIR, 'mercurial')
748 expecthg = os.path.join(PYTHONDIR, 'mercurial')
742 actualhg = _gethgpath()
749 actualhg = _gethgpath()
743 if actualhg != expecthg:
750 if actualhg != expecthg:
744 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
751 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
745 ' (expected %s)\n'
752 ' (expected %s)\n'
746 % (verb, actualhg, expecthg))
753 % (verb, actualhg, expecthg))
747
754
748 def runchildren(options, tests):
755 def runchildren(options, tests):
749 if INST:
756 if INST:
750 installhg(options)
757 installhg(options)
751 _checkhglib("Testing")
758 _checkhglib("Testing")
752
759
753 optcopy = dict(options.__dict__)
760 optcopy = dict(options.__dict__)
754 optcopy['jobs'] = 1
761 optcopy['jobs'] = 1
755 del optcopy['blacklist']
762 del optcopy['blacklist']
756 if optcopy['with_hg'] is None:
763 if optcopy['with_hg'] is None:
757 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
764 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
758 optcopy.pop('anycoverage', None)
765 optcopy.pop('anycoverage', None)
759
766
760 opts = []
767 opts = []
761 for opt, value in optcopy.iteritems():
768 for opt, value in optcopy.iteritems():
762 name = '--' + opt.replace('_', '-')
769 name = '--' + opt.replace('_', '-')
763 if value is True:
770 if value is True:
764 opts.append(name)
771 opts.append(name)
765 elif value is not None:
772 elif value is not None:
766 opts.append(name + '=' + str(value))
773 opts.append(name + '=' + str(value))
767
774
768 tests.reverse()
775 tests.reverse()
769 jobs = [[] for j in xrange(options.jobs)]
776 jobs = [[] for j in xrange(options.jobs)]
770 while tests:
777 while tests:
771 for job in jobs:
778 for job in jobs:
772 if not tests:
779 if not tests:
773 break
780 break
774 job.append(tests.pop())
781 job.append(tests.pop())
775 fps = {}
782 fps = {}
776
783
777 for j, job in enumerate(jobs):
784 for j, job in enumerate(jobs):
778 if not job:
785 if not job:
779 continue
786 continue
780 rfd, wfd = os.pipe()
787 rfd, wfd = os.pipe()
781 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
788 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
782 childtmp = os.path.join(HGTMP, 'child%d' % j)
789 childtmp = os.path.join(HGTMP, 'child%d' % j)
783 childopts += ['--tmpdir', childtmp]
790 childopts += ['--tmpdir', childtmp]
784 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
791 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
785 vlog(' '.join(cmdline))
792 vlog(' '.join(cmdline))
786 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
793 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
787 os.close(wfd)
794 os.close(wfd)
788 signal.signal(signal.SIGINT, signal.SIG_IGN)
795 signal.signal(signal.SIGINT, signal.SIG_IGN)
789 failures = 0
796 failures = 0
790 tested, skipped, failed = 0, 0, 0
797 tested, skipped, failed = 0, 0, 0
791 skips = []
798 skips = []
792 fails = []
799 fails = []
793 while fps:
800 while fps:
794 pid, status = os.wait()
801 pid, status = os.wait()
795 fp = fps.pop(pid)
802 fp = fps.pop(pid)
796 l = fp.read().splitlines()
803 l = fp.read().splitlines()
797 try:
804 try:
798 test, skip, fail = map(int, l[:3])
805 test, skip, fail = map(int, l[:3])
799 except ValueError:
806 except ValueError:
800 test, skip, fail = 0, 0, 0
807 test, skip, fail = 0, 0, 0
801 split = -fail or len(l)
808 split = -fail or len(l)
802 for s in l[3:split]:
809 for s in l[3:split]:
803 skips.append(s.split(" ", 1))
810 skips.append(s.split(" ", 1))
804 for s in l[split:]:
811 for s in l[split:]:
805 fails.append(s.split(" ", 1))
812 fails.append(s.split(" ", 1))
806 tested += test
813 tested += test
807 skipped += skip
814 skipped += skip
808 failed += fail
815 failed += fail
809 vlog('pid %d exited, status %d' % (pid, status))
816 vlog('pid %d exited, status %d' % (pid, status))
810 failures |= status
817 failures |= status
811 print
818 print
812 if not options.noskips:
819 if not options.noskips:
813 for s in skips:
820 for s in skips:
814 print "Skipped %s: %s" % (s[0], s[1])
821 print "Skipped %s: %s" % (s[0], s[1])
815 for s in fails:
822 for s in fails:
816 print "Failed %s: %s" % (s[0], s[1])
823 print "Failed %s: %s" % (s[0], s[1])
817
824
818 _checkhglib("Tested")
825 _checkhglib("Tested")
819 print "# Ran %d tests, %d skipped, %d failed." % (
826 print "# Ran %d tests, %d skipped, %d failed." % (
820 tested, skipped, failed)
827 tested, skipped, failed)
821
828
822 if options.anycoverage:
829 if options.anycoverage:
823 outputcoverage(options)
830 outputcoverage(options)
824 sys.exit(failures != 0)
831 sys.exit(failures != 0)
825
832
826 def runtests(options, tests):
833 def runtests(options, tests):
827 global DAEMON_PIDS, HGRCPATH
834 global DAEMON_PIDS, HGRCPATH
828 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
835 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
829 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
836 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
830
837
831 try:
838 try:
832 if INST:
839 if INST:
833 installhg(options)
840 installhg(options)
834 _checkhglib("Testing")
841 _checkhglib("Testing")
835
842
836 if options.timeout > 0:
843 if options.timeout > 0:
837 try:
844 try:
838 signal.signal(signal.SIGALRM, alarmed)
845 signal.signal(signal.SIGALRM, alarmed)
839 vlog('# Running each test with %d second timeout' %
846 vlog('# Running each test with %d second timeout' %
840 options.timeout)
847 options.timeout)
841 except AttributeError:
848 except AttributeError:
842 print 'WARNING: cannot run tests with timeouts'
849 print 'WARNING: cannot run tests with timeouts'
843 options.timeout = 0
850 options.timeout = 0
844
851
845 tested = 0
852 tested = 0
846 failed = 0
853 failed = 0
847 skipped = 0
854 skipped = 0
848
855
849 if options.restart:
856 if options.restart:
850 orig = list(tests)
857 orig = list(tests)
851 while tests:
858 while tests:
852 if os.path.exists(tests[0] + ".err"):
859 if os.path.exists(tests[0] + ".err"):
853 break
860 break
854 tests.pop(0)
861 tests.pop(0)
855 if not tests:
862 if not tests:
856 print "running all tests"
863 print "running all tests"
857 tests = orig
864 tests = orig
858
865
859 skips = []
866 skips = []
860 fails = []
867 fails = []
861
868
862 for test in tests:
869 for test in tests:
863 if options.blacklist:
870 if options.blacklist:
864 filename = options.blacklist.get(test)
871 filename = options.blacklist.get(test)
865 if filename is not None:
872 if filename is not None:
866 skips.append((test, "blacklisted (%s)" % filename))
873 skips.append((test, "blacklisted (%s)" % filename))
867 skipped += 1
874 skipped += 1
868 continue
875 continue
869
876
870 if options.retest and not os.path.exists(test + ".err"):
877 if options.retest and not os.path.exists(test + ".err"):
871 skipped += 1
878 skipped += 1
872 continue
879 continue
873
880
874 if options.keywords:
881 if options.keywords:
875 t = open(test).read().lower() + test.lower()
882 t = open(test).read().lower() + test.lower()
876 for k in options.keywords.lower().split():
883 for k in options.keywords.lower().split():
877 if k in t:
884 if k in t:
878 break
885 break
879 else:
886 else:
880 skipped += 1
887 skipped += 1
881 continue
888 continue
882
889
883 ret = runone(options, test, skips, fails)
890 ret = runone(options, test, skips, fails)
884 if ret is None:
891 if ret is None:
885 skipped += 1
892 skipped += 1
886 elif not ret:
893 elif not ret:
887 if options.interactive:
894 if options.interactive:
888 print "Accept this change? [n] ",
895 print "Accept this change? [n] ",
889 answer = sys.stdin.readline().strip()
896 answer = sys.stdin.readline().strip()
890 if answer.lower() in "y yes".split():
897 if answer.lower() in "y yes".split():
891 if test.endswith(".t"):
898 if test.endswith(".t"):
892 rename(test + ".err", test)
899 rename(test + ".err", test)
893 else:
900 else:
894 rename(test + ".err", test + ".out")
901 rename(test + ".err", test + ".out")
895 tested += 1
902 tested += 1
896 fails.pop()
903 fails.pop()
897 continue
904 continue
898 failed += 1
905 failed += 1
899 if options.first:
906 if options.first:
900 break
907 break
901 tested += 1
908 tested += 1
902
909
903 if options.child:
910 if options.child:
904 fp = os.fdopen(options.child, 'w')
911 fp = os.fdopen(options.child, 'w')
905 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
912 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
906 for s in skips:
913 for s in skips:
907 fp.write("%s %s\n" % s)
914 fp.write("%s %s\n" % s)
908 for s in fails:
915 for s in fails:
909 fp.write("%s %s\n" % s)
916 fp.write("%s %s\n" % s)
910 fp.close()
917 fp.close()
911 else:
918 else:
912 print
919 print
913 for s in skips:
920 for s in skips:
914 print "Skipped %s: %s" % s
921 print "Skipped %s: %s" % s
915 for s in fails:
922 for s in fails:
916 print "Failed %s: %s" % s
923 print "Failed %s: %s" % s
917 _checkhglib("Tested")
924 _checkhglib("Tested")
918 print "# Ran %d tests, %d skipped, %d failed." % (
925 print "# Ran %d tests, %d skipped, %d failed." % (
919 tested, skipped, failed)
926 tested, skipped, failed)
920
927
921 if options.anycoverage:
928 if options.anycoverage:
922 outputcoverage(options)
929 outputcoverage(options)
923 except KeyboardInterrupt:
930 except KeyboardInterrupt:
924 failed = True
931 failed = True
925 print "\ninterrupted!"
932 print "\ninterrupted!"
926
933
927 if failed:
934 if failed:
928 sys.exit(1)
935 sys.exit(1)
929
936
930 def main():
937 def main():
931 (options, args) = parseargs()
938 (options, args) = parseargs()
932 if not options.child:
939 if not options.child:
933 os.umask(022)
940 os.umask(022)
934
941
935 checktools()
942 checktools()
936
943
937 # Reset some environment variables to well-known values so that
944 # Reset some environment variables to well-known values so that
938 # the tests produce repeatable output.
945 # the tests produce repeatable output.
939 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
946 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
940 os.environ['TZ'] = 'GMT'
947 os.environ['TZ'] = 'GMT'
941 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
948 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
942 os.environ['CDPATH'] = ''
949 os.environ['CDPATH'] = ''
943 os.environ['COLUMNS'] = '80'
950 os.environ['COLUMNS'] = '80'
944 os.environ['GREP_OPTIONS'] = ''
951 os.environ['GREP_OPTIONS'] = ''
945 os.environ['http_proxy'] = ''
952 os.environ['http_proxy'] = ''
946
953
947 # unset env related to hooks
954 # unset env related to hooks
948 for k in os.environ.keys():
955 for k in os.environ.keys():
949 if k.startswith('HG_'):
956 if k.startswith('HG_'):
950 # can't remove on solaris
957 # can't remove on solaris
951 os.environ[k] = ''
958 os.environ[k] = ''
952 del os.environ[k]
959 del os.environ[k]
953
960
954 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
961 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
955 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
962 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
956 if options.tmpdir:
963 if options.tmpdir:
957 options.keep_tmpdir = True
964 options.keep_tmpdir = True
958 tmpdir = options.tmpdir
965 tmpdir = options.tmpdir
959 if os.path.exists(tmpdir):
966 if os.path.exists(tmpdir):
960 # Meaning of tmpdir has changed since 1.3: we used to create
967 # Meaning of tmpdir has changed since 1.3: we used to create
961 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
968 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
962 # tmpdir already exists.
969 # tmpdir already exists.
963 sys.exit("error: temp dir %r already exists" % tmpdir)
970 sys.exit("error: temp dir %r already exists" % tmpdir)
964
971
965 # Automatically removing tmpdir sounds convenient, but could
972 # Automatically removing tmpdir sounds convenient, but could
966 # really annoy anyone in the habit of using "--tmpdir=/tmp"
973 # really annoy anyone in the habit of using "--tmpdir=/tmp"
967 # or "--tmpdir=$HOME".
974 # or "--tmpdir=$HOME".
968 #vlog("# Removing temp dir", tmpdir)
975 #vlog("# Removing temp dir", tmpdir)
969 #shutil.rmtree(tmpdir)
976 #shutil.rmtree(tmpdir)
970 os.makedirs(tmpdir)
977 os.makedirs(tmpdir)
971 else:
978 else:
972 tmpdir = tempfile.mkdtemp('', 'hgtests.')
979 tmpdir = tempfile.mkdtemp('', 'hgtests.')
973 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
980 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
974 DAEMON_PIDS = None
981 DAEMON_PIDS = None
975 HGRCPATH = None
982 HGRCPATH = None
976
983
977 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
984 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
978 os.environ["HGMERGE"] = "internal:merge"
985 os.environ["HGMERGE"] = "internal:merge"
979 os.environ["HGUSER"] = "test"
986 os.environ["HGUSER"] = "test"
980 os.environ["HGENCODING"] = "ascii"
987 os.environ["HGENCODING"] = "ascii"
981 os.environ["HGENCODINGMODE"] = "strict"
988 os.environ["HGENCODINGMODE"] = "strict"
982 os.environ["HGPORT"] = str(options.port)
989 os.environ["HGPORT"] = str(options.port)
983 os.environ["HGPORT1"] = str(options.port + 1)
990 os.environ["HGPORT1"] = str(options.port + 1)
984 os.environ["HGPORT2"] = str(options.port + 2)
991 os.environ["HGPORT2"] = str(options.port + 2)
985
992
986 if options.with_hg:
993 if options.with_hg:
987 INST = None
994 INST = None
988 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
995 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
989
996
990 # This looks redundant with how Python initializes sys.path from
997 # This looks redundant with how Python initializes sys.path from
991 # the location of the script being executed. Needed because the
998 # the location of the script being executed. Needed because the
992 # "hg" specified by --with-hg is not the only Python script
999 # "hg" specified by --with-hg is not the only Python script
993 # executed in the test suite that needs to import 'mercurial'
1000 # executed in the test suite that needs to import 'mercurial'
994 # ... which means it's not really redundant at all.
1001 # ... which means it's not really redundant at all.
995 PYTHONDIR = BINDIR
1002 PYTHONDIR = BINDIR
996 else:
1003 else:
997 INST = os.path.join(HGTMP, "install")
1004 INST = os.path.join(HGTMP, "install")
998 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1005 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
999 PYTHONDIR = os.path.join(INST, "lib", "python")
1006 PYTHONDIR = os.path.join(INST, "lib", "python")
1000
1007
1001 os.environ["BINDIR"] = BINDIR
1008 os.environ["BINDIR"] = BINDIR
1002 os.environ["PYTHON"] = PYTHON
1009 os.environ["PYTHON"] = PYTHON
1003
1010
1004 if not options.child:
1011 if not options.child:
1005 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1012 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1006 os.environ["PATH"] = os.pathsep.join(path)
1013 os.environ["PATH"] = os.pathsep.join(path)
1007
1014
1008 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1015 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1009 # can run .../tests/run-tests.py test-foo where test-foo
1016 # can run .../tests/run-tests.py test-foo where test-foo
1010 # adds an extension to HGRC
1017 # adds an extension to HGRC
1011 pypath = [PYTHONDIR, TESTDIR]
1018 pypath = [PYTHONDIR, TESTDIR]
1012 # We have to augment PYTHONPATH, rather than simply replacing
1019 # We have to augment PYTHONPATH, rather than simply replacing
1013 # it, in case external libraries are only available via current
1020 # it, in case external libraries are only available via current
1014 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1021 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1015 # are in /opt/subversion.)
1022 # are in /opt/subversion.)
1016 oldpypath = os.environ.get(IMPL_PATH)
1023 oldpypath = os.environ.get(IMPL_PATH)
1017 if oldpypath:
1024 if oldpypath:
1018 pypath.append(oldpypath)
1025 pypath.append(oldpypath)
1019 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1026 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1020
1027
1021 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1028 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1022
1029
1023 if len(args) == 0:
1030 if len(args) == 0:
1024 args = os.listdir(".")
1031 args = os.listdir(".")
1025 args.sort()
1032 args.sort()
1026
1033
1027 tests = []
1034 tests = []
1028 for test in args:
1035 for test in args:
1029 if (test.startswith("test-") and '~' not in test and
1036 if (test.startswith("test-") and '~' not in test and
1030 ('.' not in test or test.endswith('.py') or
1037 ('.' not in test or test.endswith('.py') or
1031 test.endswith('.bat') or test.endswith('.t'))):
1038 test.endswith('.bat') or test.endswith('.t'))):
1032 tests.append(test)
1039 tests.append(test)
1033 if not tests:
1040 if not tests:
1034 print "# Ran 0 tests, 0 skipped, 0 failed."
1041 print "# Ran 0 tests, 0 skipped, 0 failed."
1035 return
1042 return
1036
1043
1037 vlog("# Using TESTDIR", TESTDIR)
1044 vlog("# Using TESTDIR", TESTDIR)
1038 vlog("# Using HGTMP", HGTMP)
1045 vlog("# Using HGTMP", HGTMP)
1039 vlog("# Using PATH", os.environ["PATH"])
1046 vlog("# Using PATH", os.environ["PATH"])
1040 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1047 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1041
1048
1042 try:
1049 try:
1043 if len(tests) > 1 and options.jobs > 1:
1050 if len(tests) > 1 and options.jobs > 1:
1044 runchildren(options, tests)
1051 runchildren(options, tests)
1045 else:
1052 else:
1046 runtests(options, tests)
1053 runtests(options, tests)
1047 finally:
1054 finally:
1048 time.sleep(1)
1055 time.sleep(1)
1049 cleanup(options)
1056 cleanup(options)
1050
1057
1051 main()
1058 main()
General Comments 0
You need to be logged in to leave comments. Login now