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