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