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