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