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