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