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