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