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