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