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