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