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