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