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