##// END OF EJS Templates
run-tests: create classes for representing tests...
Gregory Szorc -
r21296:cd877603 default
parent child Browse files
Show More
@@ -1,1323 +1,1349 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):
583 """Encapsulates a single, runnable test."""
584
585 def __init__(self, path, options):
586 self._path = path
587 self._options = options
588
589 def run(self, testtmp, replacements, env):
590 return self._run(testtmp, replacements, env)
591
592 def _run(self, testtmp, replacements, env):
593 raise NotImplemented('Subclasses must implement Test.run()')
594
582 def pytest(test, wd, options, replacements, env):
595 def pytest(test, wd, options, replacements, env):
583 py3kswitch = options.py3k_warnings and ' -3' or ''
596 py3kswitch = options.py3k_warnings and ' -3' or ''
584 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
597 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
585 vlog("# Running", cmd)
598 vlog("# Running", cmd)
586 if os.name == 'nt':
599 if os.name == 'nt':
587 replacements.append((r'\r\n', '\n'))
600 replacements.append((r'\r\n', '\n'))
588 return run(cmd, wd, options, replacements, env)
601 return run(cmd, wd, options, replacements, env)
589
602
603 class PythonTest(Test):
604 """A Python-based test."""
605 def _run(self, testtmp, replacements, env):
606 return pytest(self._path, testtmp, self._options, replacements, env)
607
590 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
608 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
591 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
609 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
592 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
610 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
593 escapemap.update({'\\': '\\\\', '\r': r'\r'})
611 escapemap.update({'\\': '\\\\', '\r': r'\r'})
594 def escapef(m):
612 def escapef(m):
595 return escapemap[m.group(0)]
613 return escapemap[m.group(0)]
596 def stringescape(s):
614 def stringescape(s):
597 return escapesub(escapef, s)
615 return escapesub(escapef, s)
598
616
599 def rematch(el, l):
617 def rematch(el, l):
600 try:
618 try:
601 # use \Z to ensure that the regex matches to the end of the string
619 # use \Z to ensure that the regex matches to the end of the string
602 if os.name == 'nt':
620 if os.name == 'nt':
603 return re.match(el + r'\r?\n\Z', l)
621 return re.match(el + r'\r?\n\Z', l)
604 return re.match(el + r'\n\Z', l)
622 return re.match(el + r'\n\Z', l)
605 except re.error:
623 except re.error:
606 # el is an invalid regex
624 # el is an invalid regex
607 return False
625 return False
608
626
609 def globmatch(el, l):
627 def globmatch(el, l):
610 # The only supported special characters are * and ? plus / which also
628 # The only supported special characters are * and ? plus / which also
611 # matches \ on windows. Escaping of these characters is supported.
629 # matches \ on windows. Escaping of these characters is supported.
612 if el + '\n' == l:
630 if el + '\n' == l:
613 if os.altsep:
631 if os.altsep:
614 # matching on "/" is not needed for this line
632 # matching on "/" is not needed for this line
615 return '-glob'
633 return '-glob'
616 return True
634 return True
617 i, n = 0, len(el)
635 i, n = 0, len(el)
618 res = ''
636 res = ''
619 while i < n:
637 while i < n:
620 c = el[i]
638 c = el[i]
621 i += 1
639 i += 1
622 if c == '\\' and el[i] in '*?\\/':
640 if c == '\\' and el[i] in '*?\\/':
623 res += el[i - 1:i + 1]
641 res += el[i - 1:i + 1]
624 i += 1
642 i += 1
625 elif c == '*':
643 elif c == '*':
626 res += '.*'
644 res += '.*'
627 elif c == '?':
645 elif c == '?':
628 res += '.'
646 res += '.'
629 elif c == '/' and os.altsep:
647 elif c == '/' and os.altsep:
630 res += '[/\\\\]'
648 res += '[/\\\\]'
631 else:
649 else:
632 res += re.escape(c)
650 res += re.escape(c)
633 return rematch(res, l)
651 return rematch(res, l)
634
652
635 def linematch(el, l):
653 def linematch(el, l):
636 if el == l: # perfect match (fast)
654 if el == l: # perfect match (fast)
637 return True
655 return True
638 if el:
656 if el:
639 if el.endswith(" (esc)\n"):
657 if el.endswith(" (esc)\n"):
640 el = el[:-7].decode('string-escape') + '\n'
658 el = el[:-7].decode('string-escape') + '\n'
641 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
659 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
642 return True
660 return True
643 if el.endswith(" (re)\n"):
661 if el.endswith(" (re)\n"):
644 return rematch(el[:-6], l)
662 return rematch(el[:-6], l)
645 if el.endswith(" (glob)\n"):
663 if el.endswith(" (glob)\n"):
646 return globmatch(el[:-8], l)
664 return globmatch(el[:-8], l)
647 if os.altsep and l.replace('\\', '/') == el:
665 if os.altsep and l.replace('\\', '/') == el:
648 return '+glob'
666 return '+glob'
649 return False
667 return False
650
668
651 def tsttest(test, wd, options, replacements, env):
669 def tsttest(test, wd, options, replacements, env):
652 # We generate a shell script which outputs unique markers to line
670 # We generate a shell script which outputs unique markers to line
653 # up script results with our source. These markers include input
671 # up script results with our source. These markers include input
654 # line number and the last return code
672 # line number and the last return code
655 salt = "SALT" + str(time.time())
673 salt = "SALT" + str(time.time())
656 def addsalt(line, inpython):
674 def addsalt(line, inpython):
657 if inpython:
675 if inpython:
658 script.append('%s %d 0\n' % (salt, line))
676 script.append('%s %d 0\n' % (salt, line))
659 else:
677 else:
660 script.append('echo %s %s $?\n' % (salt, line))
678 script.append('echo %s %s $?\n' % (salt, line))
661
679
662 # After we run the shell script, we re-unify the script output
680 # After we run the shell script, we re-unify the script output
663 # with non-active parts of the source, with synchronization by our
681 # with non-active parts of the source, with synchronization by our
664 # SALT line number markers. The after table contains the
682 # SALT line number markers. The after table contains the
665 # non-active components, ordered by line number
683 # non-active components, ordered by line number
666 after = {}
684 after = {}
667 pos = prepos = -1
685 pos = prepos = -1
668
686
669 # Expected shell script output
687 # Expected shell script output
670 expected = {}
688 expected = {}
671
689
672 # We keep track of whether or not we're in a Python block so we
690 # We keep track of whether or not we're in a Python block so we
673 # can generate the surrounding doctest magic
691 # can generate the surrounding doctest magic
674 inpython = False
692 inpython = False
675
693
676 # True or False when in a true or false conditional section
694 # True or False when in a true or false conditional section
677 skipping = None
695 skipping = None
678
696
679 def hghave(reqs):
697 def hghave(reqs):
680 # TODO: do something smarter when all other uses of hghave is gone
698 # TODO: do something smarter when all other uses of hghave is gone
681 tdir = TESTDIR.replace('\\', '/')
699 tdir = TESTDIR.replace('\\', '/')
682 proc = Popen4('%s -c "%s/hghave %s"' %
700 proc = Popen4('%s -c "%s/hghave %s"' %
683 (options.shell, tdir, ' '.join(reqs)), wd, 0)
701 (options.shell, tdir, ' '.join(reqs)), wd, 0)
684 stdout, stderr = proc.communicate()
702 stdout, stderr = proc.communicate()
685 ret = proc.wait()
703 ret = proc.wait()
686 if wifexited(ret):
704 if wifexited(ret):
687 ret = os.WEXITSTATUS(ret)
705 ret = os.WEXITSTATUS(ret)
688 if ret == 2:
706 if ret == 2:
689 print stdout
707 print stdout
690 sys.exit(1)
708 sys.exit(1)
691 return ret == 0
709 return ret == 0
692
710
693 f = open(test)
711 f = open(test)
694 t = f.readlines()
712 t = f.readlines()
695 f.close()
713 f.close()
696
714
697 script = []
715 script = []
698 if options.debug:
716 if options.debug:
699 script.append('set -x\n')
717 script.append('set -x\n')
700 if os.getenv('MSYSTEM'):
718 if os.getenv('MSYSTEM'):
701 script.append('alias pwd="pwd -W"\n')
719 script.append('alias pwd="pwd -W"\n')
702 n = 0
720 n = 0
703 for n, l in enumerate(t):
721 for n, l in enumerate(t):
704 if not l.endswith('\n'):
722 if not l.endswith('\n'):
705 l += '\n'
723 l += '\n'
706 if l.startswith('#if'):
724 if l.startswith('#if'):
707 lsplit = l.split()
725 lsplit = l.split()
708 if len(lsplit) < 2 or lsplit[0] != '#if':
726 if len(lsplit) < 2 or lsplit[0] != '#if':
709 after.setdefault(pos, []).append(' !!! invalid #if\n')
727 after.setdefault(pos, []).append(' !!! invalid #if\n')
710 if skipping is not None:
728 if skipping is not None:
711 after.setdefault(pos, []).append(' !!! nested #if\n')
729 after.setdefault(pos, []).append(' !!! nested #if\n')
712 skipping = not hghave(lsplit[1:])
730 skipping = not hghave(lsplit[1:])
713 after.setdefault(pos, []).append(l)
731 after.setdefault(pos, []).append(l)
714 elif l.startswith('#else'):
732 elif l.startswith('#else'):
715 if skipping is None:
733 if skipping is None:
716 after.setdefault(pos, []).append(' !!! missing #if\n')
734 after.setdefault(pos, []).append(' !!! missing #if\n')
717 skipping = not skipping
735 skipping = not skipping
718 after.setdefault(pos, []).append(l)
736 after.setdefault(pos, []).append(l)
719 elif l.startswith('#endif'):
737 elif l.startswith('#endif'):
720 if skipping is None:
738 if skipping is None:
721 after.setdefault(pos, []).append(' !!! missing #if\n')
739 after.setdefault(pos, []).append(' !!! missing #if\n')
722 skipping = None
740 skipping = None
723 after.setdefault(pos, []).append(l)
741 after.setdefault(pos, []).append(l)
724 elif skipping:
742 elif skipping:
725 after.setdefault(pos, []).append(l)
743 after.setdefault(pos, []).append(l)
726 elif l.startswith(' >>> '): # python inlines
744 elif l.startswith(' >>> '): # python inlines
727 after.setdefault(pos, []).append(l)
745 after.setdefault(pos, []).append(l)
728 prepos = pos
746 prepos = pos
729 pos = n
747 pos = n
730 if not inpython:
748 if not inpython:
731 # we've just entered a Python block, add the header
749 # we've just entered a Python block, add the header
732 inpython = True
750 inpython = True
733 addsalt(prepos, False) # make sure we report the exit code
751 addsalt(prepos, False) # make sure we report the exit code
734 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
752 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
735 addsalt(n, True)
753 addsalt(n, True)
736 script.append(l[2:])
754 script.append(l[2:])
737 elif l.startswith(' ... '): # python inlines
755 elif l.startswith(' ... '): # python inlines
738 after.setdefault(prepos, []).append(l)
756 after.setdefault(prepos, []).append(l)
739 script.append(l[2:])
757 script.append(l[2:])
740 elif l.startswith(' $ '): # commands
758 elif l.startswith(' $ '): # commands
741 if inpython:
759 if inpython:
742 script.append("EOF\n")
760 script.append("EOF\n")
743 inpython = False
761 inpython = False
744 after.setdefault(pos, []).append(l)
762 after.setdefault(pos, []).append(l)
745 prepos = pos
763 prepos = pos
746 pos = n
764 pos = n
747 addsalt(n, False)
765 addsalt(n, False)
748 cmd = l[4:].split()
766 cmd = l[4:].split()
749 if len(cmd) == 2 and cmd[0] == 'cd':
767 if len(cmd) == 2 and cmd[0] == 'cd':
750 l = ' $ cd %s || exit 1\n' % cmd[1]
768 l = ' $ cd %s || exit 1\n' % cmd[1]
751 script.append(l[4:])
769 script.append(l[4:])
752 elif l.startswith(' > '): # continuations
770 elif l.startswith(' > '): # continuations
753 after.setdefault(prepos, []).append(l)
771 after.setdefault(prepos, []).append(l)
754 script.append(l[4:])
772 script.append(l[4:])
755 elif l.startswith(' '): # results
773 elif l.startswith(' '): # results
756 # queue up a list of expected results
774 # queue up a list of expected results
757 expected.setdefault(pos, []).append(l[2:])
775 expected.setdefault(pos, []).append(l[2:])
758 else:
776 else:
759 if inpython:
777 if inpython:
760 script.append("EOF\n")
778 script.append("EOF\n")
761 inpython = False
779 inpython = False
762 # non-command/result - queue up for merged output
780 # non-command/result - queue up for merged output
763 after.setdefault(pos, []).append(l)
781 after.setdefault(pos, []).append(l)
764
782
765 if inpython:
783 if inpython:
766 script.append("EOF\n")
784 script.append("EOF\n")
767 if skipping is not None:
785 if skipping is not None:
768 after.setdefault(pos, []).append(' !!! missing #endif\n')
786 after.setdefault(pos, []).append(' !!! missing #endif\n')
769 addsalt(n + 1, False)
787 addsalt(n + 1, False)
770
788
771 # Write out the script and execute it
789 # Write out the script and execute it
772 name = wd + '.sh'
790 name = wd + '.sh'
773 f = open(name, 'w')
791 f = open(name, 'w')
774 for l in script:
792 for l in script:
775 f.write(l)
793 f.write(l)
776 f.close()
794 f.close()
777
795
778 cmd = '%s "%s"' % (options.shell, name)
796 cmd = '%s "%s"' % (options.shell, name)
779 vlog("# Running", cmd)
797 vlog("# Running", cmd)
780 exitcode, output = run(cmd, wd, options, replacements, env)
798 exitcode, output = run(cmd, wd, options, replacements, env)
781 # do not merge output if skipped, return hghave message instead
799 # do not merge output if skipped, return hghave message instead
782 # similarly, with --debug, output is None
800 # similarly, with --debug, output is None
783 if exitcode == SKIPPED_STATUS or output is None:
801 if exitcode == SKIPPED_STATUS or output is None:
784 return exitcode, output
802 return exitcode, output
785
803
786 # Merge the script output back into a unified test
804 # Merge the script output back into a unified test
787
805
788 warnonly = 1 # 1: not yet, 2: yes, 3: for sure not
806 warnonly = 1 # 1: not yet, 2: yes, 3: for sure not
789 if exitcode != 0: # failure has been reported
807 if exitcode != 0: # failure has been reported
790 warnonly = 3 # set to "for sure not"
808 warnonly = 3 # set to "for sure not"
791 pos = -1
809 pos = -1
792 postout = []
810 postout = []
793 for l in output:
811 for l in output:
794 lout, lcmd = l, None
812 lout, lcmd = l, None
795 if salt in l:
813 if salt in l:
796 lout, lcmd = l.split(salt, 1)
814 lout, lcmd = l.split(salt, 1)
797
815
798 if lout:
816 if lout:
799 if not lout.endswith('\n'):
817 if not lout.endswith('\n'):
800 lout += ' (no-eol)\n'
818 lout += ' (no-eol)\n'
801
819
802 # find the expected output at the current position
820 # find the expected output at the current position
803 el = None
821 el = None
804 if pos in expected and expected[pos]:
822 if pos in expected and expected[pos]:
805 el = expected[pos].pop(0)
823 el = expected[pos].pop(0)
806
824
807 r = linematch(el, lout)
825 r = linematch(el, lout)
808 if isinstance(r, str):
826 if isinstance(r, str):
809 if r == '+glob':
827 if r == '+glob':
810 lout = el[:-1] + ' (glob)\n'
828 lout = el[:-1] + ' (glob)\n'
811 r = '' # warn only this line
829 r = '' # warn only this line
812 elif r == '-glob':
830 elif r == '-glob':
813 lout = ''.join(el.rsplit(' (glob)', 1))
831 lout = ''.join(el.rsplit(' (glob)', 1))
814 r = '' # warn only this line
832 r = '' # warn only this line
815 else:
833 else:
816 log('\ninfo, unknown linematch result: %r\n' % r)
834 log('\ninfo, unknown linematch result: %r\n' % r)
817 r = False
835 r = False
818 if r:
836 if r:
819 postout.append(" " + el)
837 postout.append(" " + el)
820 else:
838 else:
821 if needescape(lout):
839 if needescape(lout):
822 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
840 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
823 postout.append(" " + lout) # let diff deal with it
841 postout.append(" " + lout) # let diff deal with it
824 if r != '': # if line failed
842 if r != '': # if line failed
825 warnonly = 3 # set to "for sure not"
843 warnonly = 3 # set to "for sure not"
826 elif warnonly == 1: # is "not yet" (and line is warn only)
844 elif warnonly == 1: # is "not yet" (and line is warn only)
827 warnonly = 2 # set to "yes" do warn
845 warnonly = 2 # set to "yes" do warn
828
846
829 if lcmd:
847 if lcmd:
830 # add on last return code
848 # add on last return code
831 ret = int(lcmd.split()[1])
849 ret = int(lcmd.split()[1])
832 if ret != 0:
850 if ret != 0:
833 postout.append(" [%s]\n" % ret)
851 postout.append(" [%s]\n" % ret)
834 if pos in after:
852 if pos in after:
835 # merge in non-active test bits
853 # merge in non-active test bits
836 postout += after.pop(pos)
854 postout += after.pop(pos)
837 pos = int(lcmd.split()[0])
855 pos = int(lcmd.split()[0])
838
856
839 if pos in after:
857 if pos in after:
840 postout += after.pop(pos)
858 postout += after.pop(pos)
841
859
842 if warnonly == 2:
860 if warnonly == 2:
843 exitcode = False # set exitcode to warned
861 exitcode = False # set exitcode to warned
844 return exitcode, postout
862 return exitcode, postout
845
863
864 class TTest(Test):
865 """A "t test" is a test backed by a .t file."""
866
867 def _run(self, testtmp, replacements, env):
868 return tsttest(self._path, testtmp, self._options, replacements, env)
869
846 wifexited = getattr(os, "WIFEXITED", lambda x: False)
870 wifexited = getattr(os, "WIFEXITED", lambda x: False)
847 def run(cmd, wd, options, replacements, env):
871 def run(cmd, wd, options, replacements, env):
848 """Run command in a sub-process, capturing the output (stdout and stderr).
872 """Run command in a sub-process, capturing the output (stdout and stderr).
849 Return a tuple (exitcode, output). output is None in debug mode."""
873 Return a tuple (exitcode, output). output is None in debug mode."""
850 # TODO: Use subprocess.Popen if we're running on Python 2.4
874 # TODO: Use subprocess.Popen if we're running on Python 2.4
851 if options.debug:
875 if options.debug:
852 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
876 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
853 ret = proc.wait()
877 ret = proc.wait()
854 return (ret, None)
878 return (ret, None)
855
879
856 proc = Popen4(cmd, wd, options.timeout, env)
880 proc = Popen4(cmd, wd, options.timeout, env)
857 def cleanup():
881 def cleanup():
858 terminate(proc)
882 terminate(proc)
859 ret = proc.wait()
883 ret = proc.wait()
860 if ret == 0:
884 if ret == 0:
861 ret = signal.SIGTERM << 8
885 ret = signal.SIGTERM << 8
862 killdaemons(env['DAEMON_PIDS'])
886 killdaemons(env['DAEMON_PIDS'])
863 return ret
887 return ret
864
888
865 output = ''
889 output = ''
866 proc.tochild.close()
890 proc.tochild.close()
867
891
868 try:
892 try:
869 output = proc.fromchild.read()
893 output = proc.fromchild.read()
870 except KeyboardInterrupt:
894 except KeyboardInterrupt:
871 vlog('# Handling keyboard interrupt')
895 vlog('# Handling keyboard interrupt')
872 cleanup()
896 cleanup()
873 raise
897 raise
874
898
875 ret = proc.wait()
899 ret = proc.wait()
876 if wifexited(ret):
900 if wifexited(ret):
877 ret = os.WEXITSTATUS(ret)
901 ret = os.WEXITSTATUS(ret)
878
902
879 if proc.timeout:
903 if proc.timeout:
880 ret = 'timeout'
904 ret = 'timeout'
881
905
882 if ret:
906 if ret:
883 killdaemons(env['DAEMON_PIDS'])
907 killdaemons(env['DAEMON_PIDS'])
884
908
885 if abort:
909 if abort:
886 raise KeyboardInterrupt()
910 raise KeyboardInterrupt()
887
911
888 for s, r in replacements:
912 for s, r in replacements:
889 output = re.sub(s, r, output)
913 output = re.sub(s, r, output)
890 return ret, output.splitlines(True)
914 return ret, output.splitlines(True)
891
915
892 def runone(options, test, count):
916 def runone(options, test, count):
893 '''returns a result element: (code, test, msg)'''
917 '''returns a result element: (code, test, msg)'''
894
918
895 def skip(msg):
919 def skip(msg):
896 if options.verbose:
920 if options.verbose:
897 log("\nSkipping %s: %s" % (testpath, msg))
921 log("\nSkipping %s: %s" % (testpath, msg))
898 return 's', test, msg
922 return 's', test, msg
899
923
900 def fail(msg, ret):
924 def fail(msg, ret):
901 warned = ret is False
925 warned = ret is False
902 if not options.nodiff:
926 if not options.nodiff:
903 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
927 log("\n%s: %s %s" % (warned and 'Warning' or 'ERROR', test, msg))
904 if (not ret and options.interactive
928 if (not ret and options.interactive
905 and os.path.exists(testpath + ".err")):
929 and os.path.exists(testpath + ".err")):
906 iolock.acquire()
930 iolock.acquire()
907 print "Accept this change? [n] ",
931 print "Accept this change? [n] ",
908 answer = sys.stdin.readline().strip()
932 answer = sys.stdin.readline().strip()
909 iolock.release()
933 iolock.release()
910 if answer.lower() in "y yes".split():
934 if answer.lower() in "y yes".split():
911 if test.endswith(".t"):
935 if test.endswith(".t"):
912 rename(testpath + ".err", testpath)
936 rename(testpath + ".err", testpath)
913 else:
937 else:
914 rename(testpath + ".err", testpath + ".out")
938 rename(testpath + ".err", testpath + ".out")
915 return '.', test, ''
939 return '.', test, ''
916 return warned and '~' or '!', test, msg
940 return warned and '~' or '!', test, msg
917
941
918 def success():
942 def success():
919 return '.', test, ''
943 return '.', test, ''
920
944
921 def ignore(msg):
945 def ignore(msg):
922 return 'i', test, msg
946 return 'i', test, msg
923
947
924 def describe(ret):
948 def describe(ret):
925 if ret < 0:
949 if ret < 0:
926 return 'killed by signal %d' % -ret
950 return 'killed by signal %d' % -ret
927 return 'returned error code %d' % ret
951 return 'returned error code %d' % ret
928
952
929 testpath = os.path.join(TESTDIR, test)
953 testpath = os.path.join(TESTDIR, test)
930 err = os.path.join(TESTDIR, test + ".err")
954 err = os.path.join(TESTDIR, test + ".err")
931 lctest = test.lower()
955 lctest = test.lower()
932
956
933 if not os.path.exists(testpath):
957 if not os.path.exists(testpath):
934 return skip("doesn't exist")
958 return skip("doesn't exist")
935
959
936 if not (options.whitelisted and test in options.whitelisted):
960 if not (options.whitelisted and test in options.whitelisted):
937 if options.blacklist and test in options.blacklist:
961 if options.blacklist and test in options.blacklist:
938 return skip("blacklisted")
962 return skip("blacklisted")
939
963
940 if options.retest and not os.path.exists(test + ".err"):
964 if options.retest and not os.path.exists(test + ".err"):
941 return ignore("not retesting")
965 return ignore("not retesting")
942
966
943 if options.keywords:
967 if options.keywords:
944 fp = open(test)
968 fp = open(test)
945 t = fp.read().lower() + test.lower()
969 t = fp.read().lower() + test.lower()
946 fp.close()
970 fp.close()
947 for k in options.keywords.lower().split():
971 for k in options.keywords.lower().split():
948 if k in t:
972 if k in t:
949 break
973 break
950 else:
974 else:
951 return ignore("doesn't match keyword")
975 return ignore("doesn't match keyword")
952
976
953 if not os.path.basename(lctest).startswith("test-"):
977 if not os.path.basename(lctest).startswith("test-"):
954 return skip("not a test file")
978 return skip("not a test file")
955 for ext, func, out in testtypes:
979 for ext, cls, out in testtypes:
956 if lctest.endswith(ext):
980 if lctest.endswith(ext):
957 runner = func
981 runner = cls
958 ref = os.path.join(TESTDIR, test + out)
982 ref = os.path.join(TESTDIR, test + out)
959 break
983 break
960 else:
984 else:
961 return skip("unknown test type")
985 return skip("unknown test type")
962
986
963 vlog("# Test", test)
987 vlog("# Test", test)
964
988
965 if os.path.exists(err):
989 if os.path.exists(err):
966 os.remove(err) # Remove any previous output files
990 os.remove(err) # Remove any previous output files
967
991
992 t = runner(testpath, options)
993
968 # Make a tmp subdirectory to work in
994 # Make a tmp subdirectory to work in
969 threadtmp = os.path.join(HGTMP, "child%d" % count)
995 threadtmp = os.path.join(HGTMP, "child%d" % count)
970 testtmp = os.path.join(threadtmp, os.path.basename(test))
996 testtmp = os.path.join(threadtmp, os.path.basename(test))
971 os.mkdir(threadtmp)
997 os.mkdir(threadtmp)
972 os.mkdir(testtmp)
998 os.mkdir(testtmp)
973
999
974 port = options.port + count * 3
1000 port = options.port + count * 3
975 replacements = [
1001 replacements = [
976 (r':%s\b' % port, ':$HGPORT'),
1002 (r':%s\b' % port, ':$HGPORT'),
977 (r':%s\b' % (port + 1), ':$HGPORT1'),
1003 (r':%s\b' % (port + 1), ':$HGPORT1'),
978 (r':%s\b' % (port + 2), ':$HGPORT2'),
1004 (r':%s\b' % (port + 2), ':$HGPORT2'),
979 ]
1005 ]
980 if os.name == 'nt':
1006 if os.name == 'nt':
981 replacements.append(
1007 replacements.append(
982 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
1008 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
983 c in '/\\' and r'[/\\]' or
1009 c in '/\\' and r'[/\\]' or
984 c.isdigit() and c or
1010 c.isdigit() and c or
985 '\\' + c
1011 '\\' + c
986 for c in testtmp), '$TESTTMP'))
1012 for c in testtmp), '$TESTTMP'))
987 else:
1013 else:
988 replacements.append((re.escape(testtmp), '$TESTTMP'))
1014 replacements.append((re.escape(testtmp), '$TESTTMP'))
989
1015
990 env = createenv(options, testtmp, threadtmp, port)
1016 env = createenv(options, testtmp, threadtmp, port)
991 createhgrc(env['HGRCPATH'], options)
1017 createhgrc(env['HGRCPATH'], options)
992
1018
993 starttime = time.time()
1019 starttime = time.time()
994 try:
1020 try:
995 ret, out = runner(testpath, testtmp, options, replacements, env)
1021 ret, out = t.run(testtmp, replacements, env)
996 except KeyboardInterrupt:
1022 except KeyboardInterrupt:
997 endtime = time.time()
1023 endtime = time.time()
998 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
1024 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
999 raise
1025 raise
1000 endtime = time.time()
1026 endtime = time.time()
1001 times.append((test, endtime - starttime))
1027 times.append((test, endtime - starttime))
1002 vlog("# Ret was:", ret)
1028 vlog("# Ret was:", ret)
1003
1029
1004 killdaemons(env['DAEMON_PIDS'])
1030 killdaemons(env['DAEMON_PIDS'])
1005
1031
1006 skipped = (ret == SKIPPED_STATUS)
1032 skipped = (ret == SKIPPED_STATUS)
1007
1033
1008 # If we're not in --debug mode and reference output file exists,
1034 # If we're not in --debug mode and reference output file exists,
1009 # check test output against it.
1035 # check test output against it.
1010 if options.debug:
1036 if options.debug:
1011 refout = None # to match "out is None"
1037 refout = None # to match "out is None"
1012 elif os.path.exists(ref):
1038 elif os.path.exists(ref):
1013 f = open(ref, "r")
1039 f = open(ref, "r")
1014 refout = f.read().splitlines(True)
1040 refout = f.read().splitlines(True)
1015 f.close()
1041 f.close()
1016 else:
1042 else:
1017 refout = []
1043 refout = []
1018
1044
1019 if (ret != 0 or out != refout) and not skipped and not options.debug:
1045 if (ret != 0 or out != refout) and not skipped and not options.debug:
1020 # Save errors to a file for diagnosis
1046 # Save errors to a file for diagnosis
1021 f = open(err, "wb")
1047 f = open(err, "wb")
1022 for line in out:
1048 for line in out:
1023 f.write(line)
1049 f.write(line)
1024 f.close()
1050 f.close()
1025
1051
1026 if skipped:
1052 if skipped:
1027 if out is None: # debug mode: nothing to parse
1053 if out is None: # debug mode: nothing to parse
1028 missing = ['unknown']
1054 missing = ['unknown']
1029 failed = None
1055 failed = None
1030 else:
1056 else:
1031 missing, failed = parsehghaveoutput(out)
1057 missing, failed = parsehghaveoutput(out)
1032 if not missing:
1058 if not missing:
1033 missing = ['irrelevant']
1059 missing = ['irrelevant']
1034 if failed:
1060 if failed:
1035 result = fail("hghave failed checking for %s" % failed[-1], ret)
1061 result = fail("hghave failed checking for %s" % failed[-1], ret)
1036 skipped = False
1062 skipped = False
1037 else:
1063 else:
1038 result = skip(missing[-1])
1064 result = skip(missing[-1])
1039 elif ret == 'timeout':
1065 elif ret == 'timeout':
1040 result = fail("timed out", ret)
1066 result = fail("timed out", ret)
1041 elif out != refout:
1067 elif out != refout:
1042 info = {}
1068 info = {}
1043 if not options.nodiff:
1069 if not options.nodiff:
1044 iolock.acquire()
1070 iolock.acquire()
1045 if options.view:
1071 if options.view:
1046 os.system("%s %s %s" % (options.view, ref, err))
1072 os.system("%s %s %s" % (options.view, ref, err))
1047 else:
1073 else:
1048 info = showdiff(refout, out, ref, err)
1074 info = showdiff(refout, out, ref, err)
1049 iolock.release()
1075 iolock.release()
1050 msg = ""
1076 msg = ""
1051 if info.get('servefail'): msg += "serve failed and "
1077 if info.get('servefail'): msg += "serve failed and "
1052 if ret:
1078 if ret:
1053 msg += "output changed and " + describe(ret)
1079 msg += "output changed and " + describe(ret)
1054 else:
1080 else:
1055 msg += "output changed"
1081 msg += "output changed"
1056 result = fail(msg, ret)
1082 result = fail(msg, ret)
1057 elif ret:
1083 elif ret:
1058 result = fail(describe(ret), ret)
1084 result = fail(describe(ret), ret)
1059 else:
1085 else:
1060 result = success()
1086 result = success()
1061
1087
1062 if not options.verbose:
1088 if not options.verbose:
1063 iolock.acquire()
1089 iolock.acquire()
1064 sys.stdout.write(result[0])
1090 sys.stdout.write(result[0])
1065 sys.stdout.flush()
1091 sys.stdout.flush()
1066 iolock.release()
1092 iolock.release()
1067
1093
1068 if not options.keep_tmpdir:
1094 if not options.keep_tmpdir:
1069 shutil.rmtree(threadtmp, True)
1095 shutil.rmtree(threadtmp, True)
1070 return result
1096 return result
1071
1097
1072 _hgpath = None
1098 _hgpath = None
1073
1099
1074 def _gethgpath():
1100 def _gethgpath():
1075 """Return the path to the mercurial package that is actually found by
1101 """Return the path to the mercurial package that is actually found by
1076 the current Python interpreter."""
1102 the current Python interpreter."""
1077 global _hgpath
1103 global _hgpath
1078 if _hgpath is not None:
1104 if _hgpath is not None:
1079 return _hgpath
1105 return _hgpath
1080
1106
1081 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1107 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1082 pipe = os.popen(cmd % PYTHON)
1108 pipe = os.popen(cmd % PYTHON)
1083 try:
1109 try:
1084 _hgpath = pipe.read().strip()
1110 _hgpath = pipe.read().strip()
1085 finally:
1111 finally:
1086 pipe.close()
1112 pipe.close()
1087 return _hgpath
1113 return _hgpath
1088
1114
1089 def _checkhglib(verb):
1115 def _checkhglib(verb):
1090 """Ensure that the 'mercurial' package imported by python is
1116 """Ensure that the 'mercurial' package imported by python is
1091 the one we expect it to be. If not, print a warning to stderr."""
1117 the one we expect it to be. If not, print a warning to stderr."""
1092 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1118 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1093 actualhg = _gethgpath()
1119 actualhg = _gethgpath()
1094 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1120 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1095 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1121 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1096 ' (expected %s)\n'
1122 ' (expected %s)\n'
1097 % (verb, actualhg, expecthg))
1123 % (verb, actualhg, expecthg))
1098
1124
1099 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1125 results = {'.':[], '!':[], '~': [], 's':[], 'i':[]}
1100 times = []
1126 times = []
1101 iolock = threading.Lock()
1127 iolock = threading.Lock()
1102 abort = False
1128 abort = False
1103
1129
1104 def scheduletests(options, tests):
1130 def scheduletests(options, tests):
1105 jobs = options.jobs
1131 jobs = options.jobs
1106 done = queue.Queue()
1132 done = queue.Queue()
1107 running = 0
1133 running = 0
1108 count = 0
1134 count = 0
1109 global abort
1135 global abort
1110
1136
1111 def job(test, count):
1137 def job(test, count):
1112 try:
1138 try:
1113 done.put(runone(options, test, count))
1139 done.put(runone(options, test, count))
1114 except KeyboardInterrupt:
1140 except KeyboardInterrupt:
1115 pass
1141 pass
1116 except: # re-raises
1142 except: # re-raises
1117 done.put(('!', test, 'run-test raised an error, see traceback'))
1143 done.put(('!', test, 'run-test raised an error, see traceback'))
1118 raise
1144 raise
1119
1145
1120 try:
1146 try:
1121 while tests or running:
1147 while tests or running:
1122 if not done.empty() or running == jobs or not tests:
1148 if not done.empty() or running == jobs or not tests:
1123 try:
1149 try:
1124 code, test, msg = done.get(True, 1)
1150 code, test, msg = done.get(True, 1)
1125 results[code].append((test, msg))
1151 results[code].append((test, msg))
1126 if options.first and code not in '.si':
1152 if options.first and code not in '.si':
1127 break
1153 break
1128 except queue.Empty:
1154 except queue.Empty:
1129 continue
1155 continue
1130 running -= 1
1156 running -= 1
1131 if tests and not running == jobs:
1157 if tests and not running == jobs:
1132 test = tests.pop(0)
1158 test = tests.pop(0)
1133 if options.loop:
1159 if options.loop:
1134 tests.append(test)
1160 tests.append(test)
1135 t = threading.Thread(target=job, name=test, args=(test, count))
1161 t = threading.Thread(target=job, name=test, args=(test, count))
1136 t.start()
1162 t.start()
1137 running += 1
1163 running += 1
1138 count += 1
1164 count += 1
1139 except KeyboardInterrupt:
1165 except KeyboardInterrupt:
1140 abort = True
1166 abort = True
1141
1167
1142 def runtests(options, tests):
1168 def runtests(options, tests):
1143 try:
1169 try:
1144 if INST:
1170 if INST:
1145 installhg(options)
1171 installhg(options)
1146 _checkhglib("Testing")
1172 _checkhglib("Testing")
1147 else:
1173 else:
1148 usecorrectpython()
1174 usecorrectpython()
1149
1175
1150 if options.restart:
1176 if options.restart:
1151 orig = list(tests)
1177 orig = list(tests)
1152 while tests:
1178 while tests:
1153 if os.path.exists(tests[0] + ".err"):
1179 if os.path.exists(tests[0] + ".err"):
1154 break
1180 break
1155 tests.pop(0)
1181 tests.pop(0)
1156 if not tests:
1182 if not tests:
1157 print "running all tests"
1183 print "running all tests"
1158 tests = orig
1184 tests = orig
1159
1185
1160 scheduletests(options, tests)
1186 scheduletests(options, tests)
1161
1187
1162 failed = len(results['!'])
1188 failed = len(results['!'])
1163 warned = len(results['~'])
1189 warned = len(results['~'])
1164 tested = len(results['.']) + failed + warned
1190 tested = len(results['.']) + failed + warned
1165 skipped = len(results['s'])
1191 skipped = len(results['s'])
1166 ignored = len(results['i'])
1192 ignored = len(results['i'])
1167
1193
1168 print
1194 print
1169 if not options.noskips:
1195 if not options.noskips:
1170 for s in results['s']:
1196 for s in results['s']:
1171 print "Skipped %s: %s" % s
1197 print "Skipped %s: %s" % s
1172 for s in results['~']:
1198 for s in results['~']:
1173 print "Warned %s: %s" % s
1199 print "Warned %s: %s" % s
1174 for s in results['!']:
1200 for s in results['!']:
1175 print "Failed %s: %s" % s
1201 print "Failed %s: %s" % s
1176 _checkhglib("Tested")
1202 _checkhglib("Tested")
1177 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1203 print "# Ran %d tests, %d skipped, %d warned, %d failed." % (
1178 tested, skipped + ignored, warned, failed)
1204 tested, skipped + ignored, warned, failed)
1179 if results['!']:
1205 if results['!']:
1180 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1206 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1181 if options.time:
1207 if options.time:
1182 outputtimes(options)
1208 outputtimes(options)
1183
1209
1184 if options.anycoverage:
1210 if options.anycoverage:
1185 outputcoverage(options)
1211 outputcoverage(options)
1186 except KeyboardInterrupt:
1212 except KeyboardInterrupt:
1187 failed = True
1213 failed = True
1188 print "\ninterrupted!"
1214 print "\ninterrupted!"
1189
1215
1190 if failed:
1216 if failed:
1191 return 1
1217 return 1
1192 if warned:
1218 if warned:
1193 return 80
1219 return 80
1194
1220
1195 testtypes = [('.py', pytest, '.out'),
1221 testtypes = [('.py', PythonTest, '.out'),
1196 ('.t', tsttest, '')]
1222 ('.t', TTest, '')]
1197
1223
1198 def main(args, parser=None):
1224 def main(args, parser=None):
1199 parser = parser or getparser()
1225 parser = parser or getparser()
1200 (options, args) = parseargs(args, parser)
1226 (options, args) = parseargs(args, parser)
1201 os.umask(022)
1227 os.umask(022)
1202
1228
1203 checktools()
1229 checktools()
1204
1230
1205 if not args:
1231 if not args:
1206 if options.changed:
1232 if options.changed:
1207 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1233 proc = Popen4('hg st --rev "%s" -man0 .' % options.changed,
1208 None, 0)
1234 None, 0)
1209 stdout, stderr = proc.communicate()
1235 stdout, stderr = proc.communicate()
1210 args = stdout.strip('\0').split('\0')
1236 args = stdout.strip('\0').split('\0')
1211 else:
1237 else:
1212 args = os.listdir(".")
1238 args = os.listdir(".")
1213
1239
1214 tests = [t for t in args
1240 tests = [t for t in args
1215 if os.path.basename(t).startswith("test-")
1241 if os.path.basename(t).startswith("test-")
1216 and (t.endswith(".py") or t.endswith(".t"))]
1242 and (t.endswith(".py") or t.endswith(".t"))]
1217
1243
1218 if options.random:
1244 if options.random:
1219 random.shuffle(tests)
1245 random.shuffle(tests)
1220 else:
1246 else:
1221 # keywords for slow tests
1247 # keywords for slow tests
1222 slow = 'svn gendoc check-code-hg'.split()
1248 slow = 'svn gendoc check-code-hg'.split()
1223 def sortkey(f):
1249 def sortkey(f):
1224 # run largest tests first, as they tend to take the longest
1250 # run largest tests first, as they tend to take the longest
1225 try:
1251 try:
1226 val = -os.stat(f).st_size
1252 val = -os.stat(f).st_size
1227 except OSError, e:
1253 except OSError, e:
1228 if e.errno != errno.ENOENT:
1254 if e.errno != errno.ENOENT:
1229 raise
1255 raise
1230 return -1e9 # file does not exist, tell early
1256 return -1e9 # file does not exist, tell early
1231 for kw in slow:
1257 for kw in slow:
1232 if kw in f:
1258 if kw in f:
1233 val *= 10
1259 val *= 10
1234 return val
1260 return val
1235 tests.sort(key=sortkey)
1261 tests.sort(key=sortkey)
1236
1262
1237 if 'PYTHONHASHSEED' not in os.environ:
1263 if 'PYTHONHASHSEED' not in os.environ:
1238 # use a random python hash seed all the time
1264 # use a random python hash seed all the time
1239 # we do the randomness ourself to know what seed is used
1265 # we do the randomness ourself to know what seed is used
1240 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1266 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1241
1267
1242 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1268 global TESTDIR, HGTMP, INST, BINDIR, TMPBINDIR, PYTHONDIR, COVERAGE_FILE
1243 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1269 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1244 if options.tmpdir:
1270 if options.tmpdir:
1245 options.keep_tmpdir = True
1271 options.keep_tmpdir = True
1246 tmpdir = options.tmpdir
1272 tmpdir = options.tmpdir
1247 if os.path.exists(tmpdir):
1273 if os.path.exists(tmpdir):
1248 # Meaning of tmpdir has changed since 1.3: we used to create
1274 # Meaning of tmpdir has changed since 1.3: we used to create
1249 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1275 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1250 # tmpdir already exists.
1276 # tmpdir already exists.
1251 print "error: temp dir %r already exists" % tmpdir
1277 print "error: temp dir %r already exists" % tmpdir
1252 return 1
1278 return 1
1253
1279
1254 # Automatically removing tmpdir sounds convenient, but could
1280 # Automatically removing tmpdir sounds convenient, but could
1255 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1281 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1256 # or "--tmpdir=$HOME".
1282 # or "--tmpdir=$HOME".
1257 #vlog("# Removing temp dir", tmpdir)
1283 #vlog("# Removing temp dir", tmpdir)
1258 #shutil.rmtree(tmpdir)
1284 #shutil.rmtree(tmpdir)
1259 os.makedirs(tmpdir)
1285 os.makedirs(tmpdir)
1260 else:
1286 else:
1261 d = None
1287 d = None
1262 if os.name == 'nt':
1288 if os.name == 'nt':
1263 # without this, we get the default temp dir location, but
1289 # without this, we get the default temp dir location, but
1264 # in all lowercase, which causes troubles with paths (issue3490)
1290 # in all lowercase, which causes troubles with paths (issue3490)
1265 d = os.getenv('TMP')
1291 d = os.getenv('TMP')
1266 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1292 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1267 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1293 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1268
1294
1269 if options.with_hg:
1295 if options.with_hg:
1270 INST = None
1296 INST = None
1271 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1297 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1272 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1298 TMPBINDIR = os.path.join(HGTMP, 'install', 'bin')
1273 os.makedirs(TMPBINDIR)
1299 os.makedirs(TMPBINDIR)
1274
1300
1275 # This looks redundant with how Python initializes sys.path from
1301 # This looks redundant with how Python initializes sys.path from
1276 # the location of the script being executed. Needed because the
1302 # the location of the script being executed. Needed because the
1277 # "hg" specified by --with-hg is not the only Python script
1303 # "hg" specified by --with-hg is not the only Python script
1278 # executed in the test suite that needs to import 'mercurial'
1304 # executed in the test suite that needs to import 'mercurial'
1279 # ... which means it's not really redundant at all.
1305 # ... which means it's not really redundant at all.
1280 PYTHONDIR = BINDIR
1306 PYTHONDIR = BINDIR
1281 else:
1307 else:
1282 INST = os.path.join(HGTMP, "install")
1308 INST = os.path.join(HGTMP, "install")
1283 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1309 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1284 TMPBINDIR = BINDIR
1310 TMPBINDIR = BINDIR
1285 PYTHONDIR = os.path.join(INST, "lib", "python")
1311 PYTHONDIR = os.path.join(INST, "lib", "python")
1286
1312
1287 os.environ["BINDIR"] = BINDIR
1313 os.environ["BINDIR"] = BINDIR
1288 os.environ["PYTHON"] = PYTHON
1314 os.environ["PYTHON"] = PYTHON
1289
1315
1290 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1316 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1291 if TMPBINDIR != BINDIR:
1317 if TMPBINDIR != BINDIR:
1292 path = [TMPBINDIR] + path
1318 path = [TMPBINDIR] + path
1293 os.environ["PATH"] = os.pathsep.join(path)
1319 os.environ["PATH"] = os.pathsep.join(path)
1294
1320
1295 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1321 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1296 # can run .../tests/run-tests.py test-foo where test-foo
1322 # can run .../tests/run-tests.py test-foo where test-foo
1297 # adds an extension to HGRC. Also include run-test.py directory to import
1323 # adds an extension to HGRC. Also include run-test.py directory to import
1298 # modules like heredoctest.
1324 # modules like heredoctest.
1299 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1325 pypath = [PYTHONDIR, TESTDIR, os.path.abspath(os.path.dirname(__file__))]
1300 # We have to augment PYTHONPATH, rather than simply replacing
1326 # We have to augment PYTHONPATH, rather than simply replacing
1301 # it, in case external libraries are only available via current
1327 # it, in case external libraries are only available via current
1302 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1328 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1303 # are in /opt/subversion.)
1329 # are in /opt/subversion.)
1304 oldpypath = os.environ.get(IMPL_PATH)
1330 oldpypath = os.environ.get(IMPL_PATH)
1305 if oldpypath:
1331 if oldpypath:
1306 pypath.append(oldpypath)
1332 pypath.append(oldpypath)
1307 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1333 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1308
1334
1309 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1335 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1310
1336
1311 vlog("# Using TESTDIR", TESTDIR)
1337 vlog("# Using TESTDIR", TESTDIR)
1312 vlog("# Using HGTMP", HGTMP)
1338 vlog("# Using HGTMP", HGTMP)
1313 vlog("# Using PATH", os.environ["PATH"])
1339 vlog("# Using PATH", os.environ["PATH"])
1314 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1340 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1315
1341
1316 try:
1342 try:
1317 return runtests(options, tests) or 0
1343 return runtests(options, tests) or 0
1318 finally:
1344 finally:
1319 time.sleep(.1)
1345 time.sleep(.1)
1320 cleanup(options)
1346 cleanup(options)
1321
1347
1322 if __name__ == '__main__':
1348 if __name__ == '__main__':
1323 sys.exit(main(sys.argv[1:]))
1349 sys.exit(main(sys.argv[1:]))
General Comments 0
You need to be logged in to leave comments. Login now