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