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