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