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