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