##// END OF EJS Templates
tests: python hash seed is only relevant after failures - otherwise keep quiet
Mads Kiilerich -
r20046:6a03695f default
parent child Browse files
Show More
@@ -1,1264 +1,1265 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 Queue as queue
59 import Queue as queue
60
60
61 processlock = threading.Lock()
61 processlock = threading.Lock()
62
62
63 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
63 # subprocess._cleanup can race with any Popen.wait or Popen.poll on py24
64 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
64 # http://bugs.python.org/issue1731717 for details. We shouldn't be producing
65 # zombies but it's pretty harmless even if we do.
65 # zombies but it's pretty harmless even if we do.
66 if sys.version_info[1] < 5:
66 if sys.version_info[1] < 5:
67 subprocess._cleanup = lambda: None
67 subprocess._cleanup = lambda: None
68
68
69 closefds = os.name == 'posix'
69 closefds = os.name == 'posix'
70 def Popen4(cmd, wd, timeout, env=None):
70 def Popen4(cmd, wd, timeout, env=None):
71 processlock.acquire()
71 processlock.acquire()
72 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
72 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd, env=env,
73 close_fds=closefds,
73 close_fds=closefds,
74 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
74 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
75 stderr=subprocess.STDOUT)
75 stderr=subprocess.STDOUT)
76 processlock.release()
76 processlock.release()
77
77
78 p.fromchild = p.stdout
78 p.fromchild = p.stdout
79 p.tochild = p.stdin
79 p.tochild = p.stdin
80 p.childerr = p.stderr
80 p.childerr = p.stderr
81
81
82 p.timeout = False
82 p.timeout = False
83 if timeout:
83 if timeout:
84 def t():
84 def t():
85 start = time.time()
85 start = time.time()
86 while time.time() - start < timeout and p.returncode is None:
86 while time.time() - start < timeout and p.returncode is None:
87 time.sleep(.1)
87 time.sleep(.1)
88 p.timeout = True
88 p.timeout = True
89 if p.returncode is None:
89 if p.returncode is None:
90 terminate(p)
90 terminate(p)
91 threading.Thread(target=t).start()
91 threading.Thread(target=t).start()
92
92
93 return p
93 return p
94
94
95 # reserved exit code to skip test (used by hghave)
95 # reserved exit code to skip test (used by hghave)
96 SKIPPED_STATUS = 80
96 SKIPPED_STATUS = 80
97 SKIPPED_PREFIX = 'skipped: '
97 SKIPPED_PREFIX = 'skipped: '
98 FAILED_PREFIX = 'hghave check failed: '
98 FAILED_PREFIX = 'hghave check failed: '
99 PYTHON = sys.executable.replace('\\', '/')
99 PYTHON = sys.executable.replace('\\', '/')
100 IMPL_PATH = 'PYTHONPATH'
100 IMPL_PATH = 'PYTHONPATH'
101 if 'java' in sys.platform:
101 if 'java' in sys.platform:
102 IMPL_PATH = 'JYTHONPATH'
102 IMPL_PATH = 'JYTHONPATH'
103
103
104 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
104 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
105 "gunzip", "bunzip2", "sed"]
105 "gunzip", "bunzip2", "sed"]
106 createdfiles = []
106 createdfiles = []
107
107
108 defaults = {
108 defaults = {
109 'jobs': ('HGTEST_JOBS', 1),
109 'jobs': ('HGTEST_JOBS', 1),
110 'timeout': ('HGTEST_TIMEOUT', 180),
110 'timeout': ('HGTEST_TIMEOUT', 180),
111 'port': ('HGTEST_PORT', 20059),
111 'port': ('HGTEST_PORT', 20059),
112 'shell': ('HGTEST_SHELL', 'sh'),
112 'shell': ('HGTEST_SHELL', 'sh'),
113 }
113 }
114
114
115 def parselistfiles(files, listtype, warn=True):
115 def parselistfiles(files, listtype, warn=True):
116 entries = dict()
116 entries = dict()
117 for filename in files:
117 for filename in files:
118 try:
118 try:
119 path = os.path.expanduser(os.path.expandvars(filename))
119 path = os.path.expanduser(os.path.expandvars(filename))
120 f = open(path, "r")
120 f = open(path, "r")
121 except IOError, err:
121 except IOError, err:
122 if err.errno != errno.ENOENT:
122 if err.errno != errno.ENOENT:
123 raise
123 raise
124 if warn:
124 if warn:
125 print "warning: no such %s file: %s" % (listtype, filename)
125 print "warning: no such %s file: %s" % (listtype, filename)
126 continue
126 continue
127
127
128 for line in f.readlines():
128 for line in f.readlines():
129 line = line.split('#', 1)[0].strip()
129 line = line.split('#', 1)[0].strip()
130 if line:
130 if line:
131 entries[line] = filename
131 entries[line] = filename
132
132
133 f.close()
133 f.close()
134 return entries
134 return entries
135
135
136 def parseargs():
136 def parseargs():
137 parser = optparse.OptionParser("%prog [options] [tests]")
137 parser = optparse.OptionParser("%prog [options] [tests]")
138
138
139 # keep these sorted
139 # keep these sorted
140 parser.add_option("--blacklist", action="append",
140 parser.add_option("--blacklist", action="append",
141 help="skip tests listed in the specified blacklist file")
141 help="skip tests listed in the specified blacklist file")
142 parser.add_option("--whitelist", action="append",
142 parser.add_option("--whitelist", action="append",
143 help="always run tests listed in the specified whitelist file")
143 help="always run tests listed in the specified whitelist file")
144 parser.add_option("-C", "--annotate", action="store_true",
144 parser.add_option("-C", "--annotate", action="store_true",
145 help="output files annotated with coverage")
145 help="output files annotated with coverage")
146 parser.add_option("-c", "--cover", action="store_true",
146 parser.add_option("-c", "--cover", action="store_true",
147 help="print a test coverage report")
147 help="print a test coverage report")
148 parser.add_option("-d", "--debug", action="store_true",
148 parser.add_option("-d", "--debug", action="store_true",
149 help="debug mode: write output of test scripts to console"
149 help="debug mode: write output of test scripts to console"
150 " rather than capturing and diff'ing it (disables timeout)")
150 " rather than capturing and diff'ing it (disables timeout)")
151 parser.add_option("-f", "--first", action="store_true",
151 parser.add_option("-f", "--first", action="store_true",
152 help="exit on the first test failure")
152 help="exit on the first test failure")
153 parser.add_option("-H", "--htmlcov", action="store_true",
153 parser.add_option("-H", "--htmlcov", action="store_true",
154 help="create an HTML report of the coverage of the files")
154 help="create an HTML report of the coverage of the files")
155 parser.add_option("--inotify", action="store_true",
155 parser.add_option("--inotify", action="store_true",
156 help="enable inotify extension when running tests")
156 help="enable inotify extension when running tests")
157 parser.add_option("-i", "--interactive", action="store_true",
157 parser.add_option("-i", "--interactive", action="store_true",
158 help="prompt to accept changed output")
158 help="prompt to accept changed output")
159 parser.add_option("-j", "--jobs", type="int",
159 parser.add_option("-j", "--jobs", type="int",
160 help="number of jobs to run in parallel"
160 help="number of jobs to run in parallel"
161 " (default: $%s or %d)" % defaults['jobs'])
161 " (default: $%s or %d)" % defaults['jobs'])
162 parser.add_option("--keep-tmpdir", action="store_true",
162 parser.add_option("--keep-tmpdir", action="store_true",
163 help="keep temporary directory after running tests")
163 help="keep temporary directory after running tests")
164 parser.add_option("-k", "--keywords",
164 parser.add_option("-k", "--keywords",
165 help="run tests matching keywords")
165 help="run tests matching keywords")
166 parser.add_option("-l", "--local", action="store_true",
166 parser.add_option("-l", "--local", action="store_true",
167 help="shortcut for --with-hg=<testdir>/../hg")
167 help="shortcut for --with-hg=<testdir>/../hg")
168 parser.add_option("--loop", action="store_true",
168 parser.add_option("--loop", action="store_true",
169 help="loop tests repeatedly")
169 help="loop tests repeatedly")
170 parser.add_option("-n", "--nodiff", action="store_true",
170 parser.add_option("-n", "--nodiff", action="store_true",
171 help="skip showing test changes")
171 help="skip showing test changes")
172 parser.add_option("-p", "--port", type="int",
172 parser.add_option("-p", "--port", type="int",
173 help="port on which servers should listen"
173 help="port on which servers should listen"
174 " (default: $%s or %d)" % defaults['port'])
174 " (default: $%s or %d)" % defaults['port'])
175 parser.add_option("--compiler", type="string",
175 parser.add_option("--compiler", type="string",
176 help="compiler to build with")
176 help="compiler to build with")
177 parser.add_option("--pure", action="store_true",
177 parser.add_option("--pure", action="store_true",
178 help="use pure Python code instead of C extensions")
178 help="use pure Python code instead of C extensions")
179 parser.add_option("-R", "--restart", action="store_true",
179 parser.add_option("-R", "--restart", action="store_true",
180 help="restart at last error")
180 help="restart at last error")
181 parser.add_option("-r", "--retest", action="store_true",
181 parser.add_option("-r", "--retest", action="store_true",
182 help="retest failed tests")
182 help="retest failed tests")
183 parser.add_option("-S", "--noskips", action="store_true",
183 parser.add_option("-S", "--noskips", action="store_true",
184 help="don't report skip tests verbosely")
184 help="don't report skip tests verbosely")
185 parser.add_option("--shell", type="string",
185 parser.add_option("--shell", type="string",
186 help="shell to use (default: $%s or %s)" % defaults['shell'])
186 help="shell to use (default: $%s or %s)" % defaults['shell'])
187 parser.add_option("-t", "--timeout", type="int",
187 parser.add_option("-t", "--timeout", type="int",
188 help="kill errant tests after TIMEOUT seconds"
188 help="kill errant tests after TIMEOUT seconds"
189 " (default: $%s or %d)" % defaults['timeout'])
189 " (default: $%s or %d)" % defaults['timeout'])
190 parser.add_option("--time", action="store_true",
190 parser.add_option("--time", action="store_true",
191 help="time how long each test takes")
191 help="time how long each test takes")
192 parser.add_option("--tmpdir", type="string",
192 parser.add_option("--tmpdir", type="string",
193 help="run tests in the given temporary directory"
193 help="run tests in the given temporary directory"
194 " (implies --keep-tmpdir)")
194 " (implies --keep-tmpdir)")
195 parser.add_option("-v", "--verbose", action="store_true",
195 parser.add_option("-v", "--verbose", action="store_true",
196 help="output verbose messages")
196 help="output verbose messages")
197 parser.add_option("--view", type="string",
197 parser.add_option("--view", type="string",
198 help="external diff viewer")
198 help="external diff viewer")
199 parser.add_option("--with-hg", type="string",
199 parser.add_option("--with-hg", type="string",
200 metavar="HG",
200 metavar="HG",
201 help="test using specified hg script rather than a "
201 help="test using specified hg script rather than a "
202 "temporary installation")
202 "temporary installation")
203 parser.add_option("-3", "--py3k-warnings", action="store_true",
203 parser.add_option("-3", "--py3k-warnings", action="store_true",
204 help="enable Py3k warnings on Python 2.6+")
204 help="enable Py3k warnings on Python 2.6+")
205 parser.add_option('--extra-config-opt', action="append",
205 parser.add_option('--extra-config-opt', action="append",
206 help='set the given config opt in the test hgrc')
206 help='set the given config opt in the test hgrc')
207 parser.add_option('--random', action="store_true",
207 parser.add_option('--random', action="store_true",
208 help='run tests in random order')
208 help='run tests in random order')
209
209
210 for option, (envvar, default) in defaults.items():
210 for option, (envvar, default) in defaults.items():
211 defaults[option] = type(default)(os.environ.get(envvar, default))
211 defaults[option] = type(default)(os.environ.get(envvar, default))
212 parser.set_defaults(**defaults)
212 parser.set_defaults(**defaults)
213 (options, args) = parser.parse_args()
213 (options, args) = parser.parse_args()
214
214
215 # jython is always pure
215 # jython is always pure
216 if 'java' in sys.platform or '__pypy__' in sys.modules:
216 if 'java' in sys.platform or '__pypy__' in sys.modules:
217 options.pure = True
217 options.pure = True
218
218
219 if options.with_hg:
219 if options.with_hg:
220 options.with_hg = os.path.expanduser(options.with_hg)
220 options.with_hg = os.path.expanduser(options.with_hg)
221 if not (os.path.isfile(options.with_hg) and
221 if not (os.path.isfile(options.with_hg) and
222 os.access(options.with_hg, os.X_OK)):
222 os.access(options.with_hg, os.X_OK)):
223 parser.error('--with-hg must specify an executable hg script')
223 parser.error('--with-hg must specify an executable hg script')
224 if not os.path.basename(options.with_hg) == 'hg':
224 if not os.path.basename(options.with_hg) == 'hg':
225 sys.stderr.write('warning: --with-hg should specify an hg script\n')
225 sys.stderr.write('warning: --with-hg should specify an hg script\n')
226 if options.local:
226 if options.local:
227 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
227 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
228 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
228 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
229 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
229 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
230 parser.error('--local specified, but %r not found or not executable'
230 parser.error('--local specified, but %r not found or not executable'
231 % hgbin)
231 % hgbin)
232 options.with_hg = hgbin
232 options.with_hg = hgbin
233
233
234 options.anycoverage = options.cover or options.annotate or options.htmlcov
234 options.anycoverage = options.cover or options.annotate or options.htmlcov
235 if options.anycoverage:
235 if options.anycoverage:
236 try:
236 try:
237 import coverage
237 import coverage
238 covver = version.StrictVersion(coverage.__version__).version
238 covver = version.StrictVersion(coverage.__version__).version
239 if covver < (3, 3):
239 if covver < (3, 3):
240 parser.error('coverage options require coverage 3.3 or later')
240 parser.error('coverage options require coverage 3.3 or later')
241 except ImportError:
241 except ImportError:
242 parser.error('coverage options now require the coverage package')
242 parser.error('coverage options now require the coverage package')
243
243
244 if options.anycoverage and options.local:
244 if options.anycoverage and options.local:
245 # this needs some path mangling somewhere, I guess
245 # this needs some path mangling somewhere, I guess
246 parser.error("sorry, coverage options do not work when --local "
246 parser.error("sorry, coverage options do not work when --local "
247 "is specified")
247 "is specified")
248
248
249 global verbose
249 global verbose
250 if options.verbose:
250 if options.verbose:
251 verbose = ''
251 verbose = ''
252
252
253 if options.tmpdir:
253 if options.tmpdir:
254 options.tmpdir = os.path.expanduser(options.tmpdir)
254 options.tmpdir = os.path.expanduser(options.tmpdir)
255
255
256 if options.jobs < 1:
256 if options.jobs < 1:
257 parser.error('--jobs must be positive')
257 parser.error('--jobs must be positive')
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.py3k_warnings:
265 if options.py3k_warnings:
266 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
266 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
267 parser.error('--py3k-warnings can only be used on Python 2.6+')
267 parser.error('--py3k-warnings can only be used on Python 2.6+')
268 if options.blacklist:
268 if options.blacklist:
269 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
269 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
270 if options.whitelist:
270 if options.whitelist:
271 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
271 options.whitelisted = parselistfiles(options.whitelist, 'whitelist')
272 else:
272 else:
273 options.whitelisted = {}
273 options.whitelisted = {}
274
274
275 return (options, args)
275 return (options, args)
276
276
277 def rename(src, dst):
277 def rename(src, dst):
278 """Like os.rename(), trade atomicity and opened files friendliness
278 """Like os.rename(), trade atomicity and opened files friendliness
279 for existing destination support.
279 for existing destination support.
280 """
280 """
281 shutil.copy(src, dst)
281 shutil.copy(src, dst)
282 os.remove(src)
282 os.remove(src)
283
283
284 def parsehghaveoutput(lines):
284 def parsehghaveoutput(lines):
285 '''Parse hghave log lines.
285 '''Parse hghave log lines.
286 Return tuple of lists (missing, failed):
286 Return tuple of lists (missing, failed):
287 * the missing/unknown features
287 * the missing/unknown features
288 * the features for which existence check failed'''
288 * the features for which existence check failed'''
289 missing = []
289 missing = []
290 failed = []
290 failed = []
291 for line in lines:
291 for line in lines:
292 if line.startswith(SKIPPED_PREFIX):
292 if line.startswith(SKIPPED_PREFIX):
293 line = line.splitlines()[0]
293 line = line.splitlines()[0]
294 missing.append(line[len(SKIPPED_PREFIX):])
294 missing.append(line[len(SKIPPED_PREFIX):])
295 elif line.startswith(FAILED_PREFIX):
295 elif line.startswith(FAILED_PREFIX):
296 line = line.splitlines()[0]
296 line = line.splitlines()[0]
297 failed.append(line[len(FAILED_PREFIX):])
297 failed.append(line[len(FAILED_PREFIX):])
298
298
299 return missing, failed
299 return missing, failed
300
300
301 def showdiff(expected, output, ref, err):
301 def showdiff(expected, output, ref, err):
302 print
302 print
303 for line in difflib.unified_diff(expected, output, ref, err):
303 for line in difflib.unified_diff(expected, output, ref, err):
304 sys.stdout.write(line)
304 sys.stdout.write(line)
305
305
306 verbose = False
306 verbose = False
307 def vlog(*msg):
307 def vlog(*msg):
308 if verbose is not False:
308 if verbose is not False:
309 iolock.acquire()
309 iolock.acquire()
310 if verbose:
310 if verbose:
311 print verbose,
311 print verbose,
312 for m in msg:
312 for m in msg:
313 print m,
313 print m,
314 print
314 print
315 sys.stdout.flush()
315 sys.stdout.flush()
316 iolock.release()
316 iolock.release()
317
317
318 def log(*msg):
318 def log(*msg):
319 iolock.acquire()
319 iolock.acquire()
320 if verbose:
320 if verbose:
321 print verbose,
321 print verbose,
322 for m in msg:
322 for m in msg:
323 print m,
323 print m,
324 print
324 print
325 sys.stdout.flush()
325 sys.stdout.flush()
326 iolock.release()
326 iolock.release()
327
327
328 def findprogram(program):
328 def findprogram(program):
329 """Search PATH for a executable program"""
329 """Search PATH for a executable program"""
330 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
330 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
331 name = os.path.join(p, program)
331 name = os.path.join(p, program)
332 if os.name == 'nt' or os.access(name, os.X_OK):
332 if os.name == 'nt' or os.access(name, os.X_OK):
333 return name
333 return name
334 return None
334 return None
335
335
336 def createhgrc(path, options):
336 def createhgrc(path, options):
337 # create a fresh hgrc
337 # create a fresh hgrc
338 hgrc = open(path, 'w')
338 hgrc = open(path, 'w')
339 hgrc.write('[ui]\n')
339 hgrc.write('[ui]\n')
340 hgrc.write('slash = True\n')
340 hgrc.write('slash = True\n')
341 hgrc.write('interactive = False\n')
341 hgrc.write('interactive = False\n')
342 hgrc.write('[defaults]\n')
342 hgrc.write('[defaults]\n')
343 hgrc.write('backout = -d "0 0"\n')
343 hgrc.write('backout = -d "0 0"\n')
344 hgrc.write('commit = -d "0 0"\n')
344 hgrc.write('commit = -d "0 0"\n')
345 hgrc.write('shelve = --date "0 0"\n')
345 hgrc.write('shelve = --date "0 0"\n')
346 hgrc.write('tag = -d "0 0"\n')
346 hgrc.write('tag = -d "0 0"\n')
347 if options.inotify:
347 if options.inotify:
348 hgrc.write('[extensions]\n')
348 hgrc.write('[extensions]\n')
349 hgrc.write('inotify=\n')
349 hgrc.write('inotify=\n')
350 hgrc.write('[inotify]\n')
350 hgrc.write('[inotify]\n')
351 hgrc.write('pidfile=daemon.pids')
351 hgrc.write('pidfile=daemon.pids')
352 hgrc.write('appendpid=True\n')
352 hgrc.write('appendpid=True\n')
353 if options.extra_config_opt:
353 if options.extra_config_opt:
354 for opt in options.extra_config_opt:
354 for opt in options.extra_config_opt:
355 section, key = opt.split('.', 1)
355 section, key = opt.split('.', 1)
356 assert '=' in key, ('extra config opt %s must '
356 assert '=' in key, ('extra config opt %s must '
357 'have an = for assignment' % opt)
357 'have an = for assignment' % opt)
358 hgrc.write('[%s]\n%s\n' % (section, key))
358 hgrc.write('[%s]\n%s\n' % (section, key))
359 hgrc.close()
359 hgrc.close()
360
360
361 def createenv(options, testtmp, threadtmp, port):
361 def createenv(options, testtmp, threadtmp, port):
362 env = os.environ.copy()
362 env = os.environ.copy()
363 env['TESTTMP'] = testtmp
363 env['TESTTMP'] = testtmp
364 env['HOME'] = testtmp
364 env['HOME'] = testtmp
365 env["HGPORT"] = str(port)
365 env["HGPORT"] = str(port)
366 env["HGPORT1"] = str(port + 1)
366 env["HGPORT1"] = str(port + 1)
367 env["HGPORT2"] = str(port + 2)
367 env["HGPORT2"] = str(port + 2)
368 env["HGRCPATH"] = os.path.join(threadtmp, '.hgrc')
368 env["HGRCPATH"] = os.path.join(threadtmp, '.hgrc')
369 env["DAEMON_PIDS"] = os.path.join(threadtmp, 'daemon.pids')
369 env["DAEMON_PIDS"] = os.path.join(threadtmp, 'daemon.pids')
370 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
370 env["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
371 env["HGMERGE"] = "internal:merge"
371 env["HGMERGE"] = "internal:merge"
372 env["HGUSER"] = "test"
372 env["HGUSER"] = "test"
373 env["HGENCODING"] = "ascii"
373 env["HGENCODING"] = "ascii"
374 env["HGENCODINGMODE"] = "strict"
374 env["HGENCODINGMODE"] = "strict"
375
375
376 # Reset some environment variables to well-known values so that
376 # Reset some environment variables to well-known values so that
377 # the tests produce repeatable output.
377 # the tests produce repeatable output.
378 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
378 env['LANG'] = env['LC_ALL'] = env['LANGUAGE'] = 'C'
379 env['TZ'] = 'GMT'
379 env['TZ'] = 'GMT'
380 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
380 env["EMAIL"] = "Foo Bar <foo.bar@example.com>"
381 env['COLUMNS'] = '80'
381 env['COLUMNS'] = '80'
382 env['TERM'] = 'xterm'
382 env['TERM'] = 'xterm'
383
383
384 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
384 for k in ('HG HGPROF CDPATH GREP_OPTIONS http_proxy no_proxy ' +
385 'NO_PROXY').split():
385 'NO_PROXY').split():
386 if k in env:
386 if k in env:
387 del env[k]
387 del env[k]
388
388
389 # unset env related to hooks
389 # unset env related to hooks
390 for k in env.keys():
390 for k in env.keys():
391 if k.startswith('HG_'):
391 if k.startswith('HG_'):
392 del env[k]
392 del env[k]
393
393
394 return env
394 return env
395
395
396 def checktools():
396 def checktools():
397 # Before we go any further, check for pre-requisite tools
397 # Before we go any further, check for pre-requisite tools
398 # stuff from coreutils (cat, rm, etc) are not tested
398 # stuff from coreutils (cat, rm, etc) are not tested
399 for p in requiredtools:
399 for p in requiredtools:
400 if os.name == 'nt' and not p.endswith('.exe'):
400 if os.name == 'nt' and not p.endswith('.exe'):
401 p += '.exe'
401 p += '.exe'
402 found = findprogram(p)
402 found = findprogram(p)
403 if found:
403 if found:
404 vlog("# Found prerequisite", p, "at", found)
404 vlog("# Found prerequisite", p, "at", found)
405 else:
405 else:
406 print "WARNING: Did not find prerequisite tool: "+p
406 print "WARNING: Did not find prerequisite tool: "+p
407
407
408 def terminate(proc):
408 def terminate(proc):
409 """Terminate subprocess (with fallback for Python versions < 2.6)"""
409 """Terminate subprocess (with fallback for Python versions < 2.6)"""
410 vlog('# Terminating process %d' % proc.pid)
410 vlog('# Terminating process %d' % proc.pid)
411 try:
411 try:
412 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
412 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
413 except OSError:
413 except OSError:
414 pass
414 pass
415
415
416 def killdaemons(pidfile):
416 def killdaemons(pidfile):
417 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
417 return killmod.killdaemons(pidfile, tryhard=False, remove=True,
418 logfn=vlog)
418 logfn=vlog)
419
419
420 def cleanup(options):
420 def cleanup(options):
421 if not options.keep_tmpdir:
421 if not options.keep_tmpdir:
422 vlog("# Cleaning up HGTMP", HGTMP)
422 vlog("# Cleaning up HGTMP", HGTMP)
423 shutil.rmtree(HGTMP, True)
423 shutil.rmtree(HGTMP, True)
424 for f in createdfiles:
424 for f in createdfiles:
425 try:
425 try:
426 os.remove(f)
426 os.remove(f)
427 except OSError:
427 except OSError:
428 pass
428 pass
429
429
430 def usecorrectpython():
430 def usecorrectpython():
431 # some tests run python interpreter. they must use same
431 # some tests run python interpreter. they must use same
432 # interpreter we use or bad things will happen.
432 # interpreter we use or bad things will happen.
433 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
433 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
434 if getattr(os, 'symlink', None):
434 if getattr(os, 'symlink', None):
435 vlog("# Making python executable in test path a symlink to '%s'" %
435 vlog("# Making python executable in test path a symlink to '%s'" %
436 sys.executable)
436 sys.executable)
437 mypython = os.path.join(BINDIR, pyexename)
437 mypython = os.path.join(BINDIR, pyexename)
438 try:
438 try:
439 if os.readlink(mypython) == sys.executable:
439 if os.readlink(mypython) == sys.executable:
440 return
440 return
441 os.unlink(mypython)
441 os.unlink(mypython)
442 except OSError, err:
442 except OSError, err:
443 if err.errno != errno.ENOENT:
443 if err.errno != errno.ENOENT:
444 raise
444 raise
445 if findprogram(pyexename) != sys.executable:
445 if findprogram(pyexename) != sys.executable:
446 try:
446 try:
447 os.symlink(sys.executable, mypython)
447 os.symlink(sys.executable, mypython)
448 createdfiles.append(mypython)
448 createdfiles.append(mypython)
449 except OSError, err:
449 except OSError, err:
450 # child processes may race, which is harmless
450 # child processes may race, which is harmless
451 if err.errno != errno.EEXIST:
451 if err.errno != errno.EEXIST:
452 raise
452 raise
453 else:
453 else:
454 exedir, exename = os.path.split(sys.executable)
454 exedir, exename = os.path.split(sys.executable)
455 vlog("# Modifying search path to find %s as %s in '%s'" %
455 vlog("# Modifying search path to find %s as %s in '%s'" %
456 (exename, pyexename, exedir))
456 (exename, pyexename, exedir))
457 path = os.environ['PATH'].split(os.pathsep)
457 path = os.environ['PATH'].split(os.pathsep)
458 while exedir in path:
458 while exedir in path:
459 path.remove(exedir)
459 path.remove(exedir)
460 os.environ['PATH'] = os.pathsep.join([exedir] + path)
460 os.environ['PATH'] = os.pathsep.join([exedir] + path)
461 if not findprogram(pyexename):
461 if not findprogram(pyexename):
462 print "WARNING: Cannot find %s in search path" % pyexename
462 print "WARNING: Cannot find %s in search path" % pyexename
463
463
464 def installhg(options):
464 def installhg(options):
465 vlog("# Performing temporary installation of HG")
465 vlog("# Performing temporary installation of HG")
466 installerrs = os.path.join("tests", "install.err")
466 installerrs = os.path.join("tests", "install.err")
467 compiler = ''
467 compiler = ''
468 if options.compiler:
468 if options.compiler:
469 compiler = '--compiler ' + options.compiler
469 compiler = '--compiler ' + options.compiler
470 pure = options.pure and "--pure" or ""
470 pure = options.pure and "--pure" or ""
471 py3 = ''
471 py3 = ''
472 if sys.version_info[0] == 3:
472 if sys.version_info[0] == 3:
473 py3 = '--c2to3'
473 py3 = '--c2to3'
474
474
475 # Run installer in hg root
475 # Run installer in hg root
476 script = os.path.realpath(sys.argv[0])
476 script = os.path.realpath(sys.argv[0])
477 hgroot = os.path.dirname(os.path.dirname(script))
477 hgroot = os.path.dirname(os.path.dirname(script))
478 os.chdir(hgroot)
478 os.chdir(hgroot)
479 nohome = '--home=""'
479 nohome = '--home=""'
480 if os.name == 'nt':
480 if os.name == 'nt':
481 # The --home="" trick works only on OS where os.sep == '/'
481 # The --home="" trick works only on OS where os.sep == '/'
482 # because of a distutils convert_path() fast-path. Avoid it at
482 # because of a distutils convert_path() fast-path. Avoid it at
483 # least on Windows for now, deal with .pydistutils.cfg bugs
483 # least on Windows for now, deal with .pydistutils.cfg bugs
484 # when they happen.
484 # when they happen.
485 nohome = ''
485 nohome = ''
486 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
486 cmd = ('%(exe)s setup.py %(py3)s %(pure)s clean --all'
487 ' build %(compiler)s --build-base="%(base)s"'
487 ' build %(compiler)s --build-base="%(base)s"'
488 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
488 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
489 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
489 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
490 % dict(exe=sys.executable, py3=py3, pure=pure, compiler=compiler,
490 % dict(exe=sys.executable, py3=py3, pure=pure, compiler=compiler,
491 base=os.path.join(HGTMP, "build"),
491 base=os.path.join(HGTMP, "build"),
492 prefix=INST, libdir=PYTHONDIR, bindir=BINDIR,
492 prefix=INST, libdir=PYTHONDIR, bindir=BINDIR,
493 nohome=nohome, logfile=installerrs))
493 nohome=nohome, logfile=installerrs))
494 vlog("# Running", cmd)
494 vlog("# Running", cmd)
495 if os.system(cmd) == 0:
495 if os.system(cmd) == 0:
496 if not options.verbose:
496 if not options.verbose:
497 os.remove(installerrs)
497 os.remove(installerrs)
498 else:
498 else:
499 f = open(installerrs)
499 f = open(installerrs)
500 for line in f:
500 for line in f:
501 print line,
501 print line,
502 f.close()
502 f.close()
503 sys.exit(1)
503 sys.exit(1)
504 os.chdir(TESTDIR)
504 os.chdir(TESTDIR)
505
505
506 usecorrectpython()
506 usecorrectpython()
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.altsep:
607 if os.altsep:
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.altsep:
623 elif c == '/' and os.altsep:
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 name = wd + '.sh'
760 name = wd + '.sh'
761 f = open(name, 'w')
761 f = open(name, 'w')
762 for l in script:
762 for l in script:
763 f.write(l)
763 f.write(l)
764 f.close()
764 f.close()
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
773
774 # Merge the script output back into a unified test
774 # Merge the script output back into a unified test
775
775
776 pos = -1
776 pos = -1
777 postout = []
777 postout = []
778 ret = 0
778 ret = 0
779 for l in output:
779 for l in output:
780 lout, lcmd = l, None
780 lout, lcmd = l, None
781 if salt in l:
781 if salt in l:
782 lout, lcmd = l.split(salt, 1)
782 lout, lcmd = l.split(salt, 1)
783
783
784 if lout:
784 if lout:
785 if not lout.endswith('\n'):
785 if not lout.endswith('\n'):
786 lout += ' (no-eol)\n'
786 lout += ' (no-eol)\n'
787
787
788 # find the expected output at the current position
788 # find the expected output at the current position
789 el = None
789 el = None
790 if pos in expected and expected[pos]:
790 if pos in expected and expected[pos]:
791 el = expected[pos].pop(0)
791 el = expected[pos].pop(0)
792
792
793 if linematch(el, lout):
793 if linematch(el, lout):
794 postout.append(" " + el)
794 postout.append(" " + el)
795 else:
795 else:
796 if needescape(lout):
796 if needescape(lout):
797 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
797 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
798 postout.append(" " + lout) # let diff deal with it
798 postout.append(" " + lout) # let diff deal with it
799
799
800 if lcmd:
800 if lcmd:
801 # add on last return code
801 # add on last return code
802 ret = int(lcmd.split()[1])
802 ret = int(lcmd.split()[1])
803 if ret != 0:
803 if ret != 0:
804 postout.append(" [%s]\n" % ret)
804 postout.append(" [%s]\n" % ret)
805 if pos in after:
805 if pos in after:
806 # merge in non-active test bits
806 # merge in non-active test bits
807 postout += after.pop(pos)
807 postout += after.pop(pos)
808 pos = int(lcmd.split()[0])
808 pos = int(lcmd.split()[0])
809
809
810 if pos in after:
810 if pos in after:
811 postout += after.pop(pos)
811 postout += after.pop(pos)
812
812
813 return exitcode, postout
813 return exitcode, postout
814
814
815 wifexited = getattr(os, "WIFEXITED", lambda x: False)
815 wifexited = getattr(os, "WIFEXITED", lambda x: False)
816 def run(cmd, wd, options, replacements, env):
816 def run(cmd, wd, options, replacements, env):
817 """Run command in a sub-process, capturing the output (stdout and stderr).
817 """Run command in a sub-process, capturing the output (stdout and stderr).
818 Return a tuple (exitcode, output). output is None in debug mode."""
818 Return a tuple (exitcode, output). output is None in debug mode."""
819 # TODO: Use subprocess.Popen if we're running on Python 2.4
819 # TODO: Use subprocess.Popen if we're running on Python 2.4
820 if options.debug:
820 if options.debug:
821 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
821 proc = subprocess.Popen(cmd, shell=True, cwd=wd, env=env)
822 ret = proc.wait()
822 ret = proc.wait()
823 return (ret, None)
823 return (ret, None)
824
824
825 proc = Popen4(cmd, wd, options.timeout, env)
825 proc = Popen4(cmd, wd, options.timeout, env)
826 def cleanup():
826 def cleanup():
827 terminate(proc)
827 terminate(proc)
828 ret = proc.wait()
828 ret = proc.wait()
829 if ret == 0:
829 if ret == 0:
830 ret = signal.SIGTERM << 8
830 ret = signal.SIGTERM << 8
831 killdaemons(env['DAEMON_PIDS'])
831 killdaemons(env['DAEMON_PIDS'])
832 return ret
832 return ret
833
833
834 output = ''
834 output = ''
835 proc.tochild.close()
835 proc.tochild.close()
836
836
837 try:
837 try:
838 output = proc.fromchild.read()
838 output = proc.fromchild.read()
839 except KeyboardInterrupt:
839 except KeyboardInterrupt:
840 vlog('# Handling keyboard interrupt')
840 vlog('# Handling keyboard interrupt')
841 cleanup()
841 cleanup()
842 raise
842 raise
843
843
844 ret = proc.wait()
844 ret = proc.wait()
845 if wifexited(ret):
845 if wifexited(ret):
846 ret = os.WEXITSTATUS(ret)
846 ret = os.WEXITSTATUS(ret)
847
847
848 if proc.timeout:
848 if proc.timeout:
849 ret = 'timeout'
849 ret = 'timeout'
850
850
851 if ret:
851 if ret:
852 killdaemons(env['DAEMON_PIDS'])
852 killdaemons(env['DAEMON_PIDS'])
853
853
854 if abort:
854 if abort:
855 raise KeyboardInterrupt()
855 raise KeyboardInterrupt()
856
856
857 for s, r in replacements:
857 for s, r in replacements:
858 output = re.sub(s, r, output)
858 output = re.sub(s, r, output)
859 return ret, output.splitlines(True)
859 return ret, output.splitlines(True)
860
860
861 def runone(options, test, count):
861 def runone(options, test, count):
862 '''returns a result element: (code, test, msg)'''
862 '''returns a result element: (code, test, msg)'''
863
863
864 def skip(msg):
864 def skip(msg):
865 if options.verbose:
865 if options.verbose:
866 log("\nSkipping %s: %s" % (testpath, msg))
866 log("\nSkipping %s: %s" % (testpath, msg))
867 return 's', test, msg
867 return 's', test, msg
868
868
869 def fail(msg, ret):
869 def fail(msg, ret):
870 if not options.nodiff:
870 if not options.nodiff:
871 log("\nERROR: %s %s" % (testpath, msg))
871 log("\nERROR: %s %s" % (testpath, msg))
872 if (not ret and options.interactive
872 if (not ret and options.interactive
873 and os.path.exists(testpath + ".err")):
873 and os.path.exists(testpath + ".err")):
874 iolock.acquire()
874 iolock.acquire()
875 print "Accept this change? [n] ",
875 print "Accept this change? [n] ",
876 answer = sys.stdin.readline().strip()
876 answer = sys.stdin.readline().strip()
877 iolock.release()
877 iolock.release()
878 if answer.lower() in "y yes".split():
878 if answer.lower() in "y yes".split():
879 if test.endswith(".t"):
879 if test.endswith(".t"):
880 rename(testpath + ".err", testpath)
880 rename(testpath + ".err", testpath)
881 else:
881 else:
882 rename(testpath + ".err", testpath + ".out")
882 rename(testpath + ".err", testpath + ".out")
883 return '.', test, ''
883 return '.', test, ''
884 return '!', test, msg
884 return '!', test, msg
885
885
886 def success():
886 def success():
887 return '.', test, ''
887 return '.', test, ''
888
888
889 def ignore(msg):
889 def ignore(msg):
890 return 'i', test, msg
890 return 'i', test, msg
891
891
892 def describe(ret):
892 def describe(ret):
893 if ret < 0:
893 if ret < 0:
894 return 'killed by signal %d' % -ret
894 return 'killed by signal %d' % -ret
895 return 'returned error code %d' % ret
895 return 'returned error code %d' % ret
896
896
897 testpath = os.path.join(TESTDIR, test)
897 testpath = os.path.join(TESTDIR, test)
898 err = os.path.join(TESTDIR, test + ".err")
898 err = os.path.join(TESTDIR, test + ".err")
899 lctest = test.lower()
899 lctest = test.lower()
900
900
901 if not os.path.exists(testpath):
901 if not os.path.exists(testpath):
902 return skip("doesn't exist")
902 return skip("doesn't exist")
903
903
904 if not (options.whitelisted and test in options.whitelisted):
904 if not (options.whitelisted and test in options.whitelisted):
905 if options.blacklist and test in options.blacklist:
905 if options.blacklist and test in options.blacklist:
906 return skip("blacklisted")
906 return skip("blacklisted")
907
907
908 if options.retest and not os.path.exists(test + ".err"):
908 if options.retest and not os.path.exists(test + ".err"):
909 return ignore("not retesting")
909 return ignore("not retesting")
910
910
911 if options.keywords:
911 if options.keywords:
912 fp = open(test)
912 fp = open(test)
913 t = fp.read().lower() + test.lower()
913 t = fp.read().lower() + test.lower()
914 fp.close()
914 fp.close()
915 for k in options.keywords.lower().split():
915 for k in options.keywords.lower().split():
916 if k in t:
916 if k in t:
917 break
917 break
918 else:
918 else:
919 return ignore("doesn't match keyword")
919 return ignore("doesn't match keyword")
920
920
921 if not lctest.startswith("test-"):
921 if not lctest.startswith("test-"):
922 return skip("not a test file")
922 return skip("not a test file")
923 for ext, func, out in testtypes:
923 for ext, func, out in testtypes:
924 if lctest.endswith(ext):
924 if lctest.endswith(ext):
925 runner = func
925 runner = func
926 ref = os.path.join(TESTDIR, test + out)
926 ref = os.path.join(TESTDIR, test + out)
927 break
927 break
928 else:
928 else:
929 return skip("unknown test type")
929 return skip("unknown test type")
930
930
931 vlog("# Test", test)
931 vlog("# Test", test)
932
932
933 if os.path.exists(err):
933 if os.path.exists(err):
934 os.remove(err) # Remove any previous output files
934 os.remove(err) # Remove any previous output files
935
935
936 # Make a tmp subdirectory to work in
936 # Make a tmp subdirectory to work in
937 threadtmp = os.path.join(HGTMP, "child%d" % count)
937 threadtmp = os.path.join(HGTMP, "child%d" % count)
938 testtmp = os.path.join(threadtmp, os.path.basename(test))
938 testtmp = os.path.join(threadtmp, os.path.basename(test))
939 os.mkdir(threadtmp)
939 os.mkdir(threadtmp)
940 os.mkdir(testtmp)
940 os.mkdir(testtmp)
941
941
942 port = options.port + count * 3
942 port = options.port + count * 3
943 replacements = [
943 replacements = [
944 (r':%s\b' % port, ':$HGPORT'),
944 (r':%s\b' % port, ':$HGPORT'),
945 (r':%s\b' % (port + 1), ':$HGPORT1'),
945 (r':%s\b' % (port + 1), ':$HGPORT1'),
946 (r':%s\b' % (port + 2), ':$HGPORT2'),
946 (r':%s\b' % (port + 2), ':$HGPORT2'),
947 ]
947 ]
948 if os.name == 'nt':
948 if os.name == 'nt':
949 replacements.append(
949 replacements.append(
950 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
950 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
951 c in '/\\' and r'[/\\]' or
951 c in '/\\' and r'[/\\]' or
952 c.isdigit() and c or
952 c.isdigit() and c or
953 '\\' + c
953 '\\' + c
954 for c in testtmp), '$TESTTMP'))
954 for c in testtmp), '$TESTTMP'))
955 else:
955 else:
956 replacements.append((re.escape(testtmp), '$TESTTMP'))
956 replacements.append((re.escape(testtmp), '$TESTTMP'))
957
957
958 env = createenv(options, testtmp, threadtmp, port)
958 env = createenv(options, testtmp, threadtmp, port)
959 createhgrc(env['HGRCPATH'], options)
959 createhgrc(env['HGRCPATH'], options)
960
960
961 starttime = time.time()
961 starttime = time.time()
962 try:
962 try:
963 ret, out = runner(testpath, testtmp, options, replacements, env)
963 ret, out = runner(testpath, testtmp, options, replacements, env)
964 except KeyboardInterrupt:
964 except KeyboardInterrupt:
965 endtime = time.time()
965 endtime = time.time()
966 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
966 log('INTERRUPTED: %s (after %d seconds)' % (test, endtime - starttime))
967 raise
967 raise
968 endtime = time.time()
968 endtime = time.time()
969 times.append((test, endtime - starttime))
969 times.append((test, endtime - starttime))
970 vlog("# Ret was:", ret)
970 vlog("# Ret was:", ret)
971
971
972 killdaemons(env['DAEMON_PIDS'])
972 killdaemons(env['DAEMON_PIDS'])
973
973
974 skipped = (ret == SKIPPED_STATUS)
974 skipped = (ret == SKIPPED_STATUS)
975
975
976 # If we're not in --debug mode and reference output file exists,
976 # If we're not in --debug mode and reference output file exists,
977 # check test output against it.
977 # check test output against it.
978 if options.debug:
978 if options.debug:
979 refout = None # to match "out is None"
979 refout = None # to match "out is None"
980 elif os.path.exists(ref):
980 elif os.path.exists(ref):
981 f = open(ref, "r")
981 f = open(ref, "r")
982 refout = f.read().splitlines(True)
982 refout = f.read().splitlines(True)
983 f.close()
983 f.close()
984 else:
984 else:
985 refout = []
985 refout = []
986
986
987 if (ret != 0 or out != refout) and not skipped and not options.debug:
987 if (ret != 0 or out != refout) and not skipped and not options.debug:
988 # Save errors to a file for diagnosis
988 # Save errors to a file for diagnosis
989 f = open(err, "wb")
989 f = open(err, "wb")
990 for line in out:
990 for line in out:
991 f.write(line)
991 f.write(line)
992 f.close()
992 f.close()
993
993
994 if skipped:
994 if skipped:
995 if out is None: # debug mode: nothing to parse
995 if out is None: # debug mode: nothing to parse
996 missing = ['unknown']
996 missing = ['unknown']
997 failed = None
997 failed = None
998 else:
998 else:
999 missing, failed = parsehghaveoutput(out)
999 missing, failed = parsehghaveoutput(out)
1000 if not missing:
1000 if not missing:
1001 missing = ['irrelevant']
1001 missing = ['irrelevant']
1002 if failed:
1002 if failed:
1003 result = fail("hghave failed checking for %s" % failed[-1], ret)
1003 result = fail("hghave failed checking for %s" % failed[-1], ret)
1004 skipped = False
1004 skipped = False
1005 else:
1005 else:
1006 result = skip(missing[-1])
1006 result = skip(missing[-1])
1007 elif ret == 'timeout':
1007 elif ret == 'timeout':
1008 result = fail("timed out", ret)
1008 result = fail("timed out", ret)
1009 elif out != refout:
1009 elif out != refout:
1010 if not options.nodiff:
1010 if not options.nodiff:
1011 iolock.acquire()
1011 iolock.acquire()
1012 if options.view:
1012 if options.view:
1013 os.system("%s %s %s" % (options.view, ref, err))
1013 os.system("%s %s %s" % (options.view, ref, err))
1014 else:
1014 else:
1015 showdiff(refout, out, ref, err)
1015 showdiff(refout, out, ref, err)
1016 iolock.release()
1016 iolock.release()
1017 if ret:
1017 if ret:
1018 result = fail("output changed and " + describe(ret), ret)
1018 result = fail("output changed and " + describe(ret), ret)
1019 else:
1019 else:
1020 result = fail("output changed", ret)
1020 result = fail("output changed", ret)
1021 elif ret:
1021 elif ret:
1022 result = fail(describe(ret), ret)
1022 result = fail(describe(ret), ret)
1023 else:
1023 else:
1024 result = success()
1024 result = success()
1025
1025
1026 if not options.verbose:
1026 if not options.verbose:
1027 iolock.acquire()
1027 iolock.acquire()
1028 sys.stdout.write(result[0])
1028 sys.stdout.write(result[0])
1029 sys.stdout.flush()
1029 sys.stdout.flush()
1030 iolock.release()
1030 iolock.release()
1031
1031
1032 if not options.keep_tmpdir:
1032 if not options.keep_tmpdir:
1033 shutil.rmtree(threadtmp, True)
1033 shutil.rmtree(threadtmp, True)
1034 return result
1034 return result
1035
1035
1036 _hgpath = None
1036 _hgpath = None
1037
1037
1038 def _gethgpath():
1038 def _gethgpath():
1039 """Return the path to the mercurial package that is actually found by
1039 """Return the path to the mercurial package that is actually found by
1040 the current Python interpreter."""
1040 the current Python interpreter."""
1041 global _hgpath
1041 global _hgpath
1042 if _hgpath is not None:
1042 if _hgpath is not None:
1043 return _hgpath
1043 return _hgpath
1044
1044
1045 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1045 cmd = '%s -c "import mercurial; print (mercurial.__path__[0])"'
1046 pipe = os.popen(cmd % PYTHON)
1046 pipe = os.popen(cmd % PYTHON)
1047 try:
1047 try:
1048 _hgpath = pipe.read().strip()
1048 _hgpath = pipe.read().strip()
1049 finally:
1049 finally:
1050 pipe.close()
1050 pipe.close()
1051 return _hgpath
1051 return _hgpath
1052
1052
1053 def _checkhglib(verb):
1053 def _checkhglib(verb):
1054 """Ensure that the 'mercurial' package imported by python is
1054 """Ensure that the 'mercurial' package imported by python is
1055 the one we expect it to be. If not, print a warning to stderr."""
1055 the one we expect it to be. If not, print a warning to stderr."""
1056 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1056 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1057 actualhg = _gethgpath()
1057 actualhg = _gethgpath()
1058 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1058 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1059 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1059 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1060 ' (expected %s)\n'
1060 ' (expected %s)\n'
1061 % (verb, actualhg, expecthg))
1061 % (verb, actualhg, expecthg))
1062
1062
1063 results = {'.':[], '!':[], 's':[], 'i':[]}
1063 results = {'.':[], '!':[], 's':[], 'i':[]}
1064 times = []
1064 times = []
1065 iolock = threading.Lock()
1065 iolock = threading.Lock()
1066 abort = False
1066 abort = False
1067
1067
1068 def scheduletests(options, tests):
1068 def scheduletests(options, tests):
1069 jobs = options.jobs
1069 jobs = options.jobs
1070 done = queue.Queue()
1070 done = queue.Queue()
1071 running = 0
1071 running = 0
1072 count = 0
1072 count = 0
1073 global abort
1073 global abort
1074
1074
1075 def job(test, count):
1075 def job(test, count):
1076 try:
1076 try:
1077 done.put(runone(options, test, count))
1077 done.put(runone(options, test, count))
1078 except KeyboardInterrupt:
1078 except KeyboardInterrupt:
1079 pass
1079 pass
1080
1080
1081 try:
1081 try:
1082 while tests or running:
1082 while tests or running:
1083 if not done.empty() or running == jobs or not tests:
1083 if not done.empty() or running == jobs or not tests:
1084 try:
1084 try:
1085 code, test, msg = done.get(True, 1)
1085 code, test, msg = done.get(True, 1)
1086 results[code].append((test, msg))
1086 results[code].append((test, msg))
1087 if options.first and code not in '.si':
1087 if options.first and code not in '.si':
1088 break
1088 break
1089 except queue.Empty:
1089 except queue.Empty:
1090 continue
1090 continue
1091 running -= 1
1091 running -= 1
1092 if tests and not running == jobs:
1092 if tests and not running == jobs:
1093 test = tests.pop(0)
1093 test = tests.pop(0)
1094 if options.loop:
1094 if options.loop:
1095 tests.append(test)
1095 tests.append(test)
1096 t = threading.Thread(target=job, args=(test, count))
1096 t = threading.Thread(target=job, args=(test, count))
1097 t.start()
1097 t.start()
1098 running += 1
1098 running += 1
1099 count += 1
1099 count += 1
1100 except KeyboardInterrupt:
1100 except KeyboardInterrupt:
1101 abort = True
1101 abort = True
1102
1102
1103 def runtests(options, tests):
1103 def runtests(options, tests):
1104 try:
1104 try:
1105 if INST:
1105 if INST:
1106 installhg(options)
1106 installhg(options)
1107 _checkhglib("Testing")
1107 _checkhglib("Testing")
1108 else:
1108 else:
1109 usecorrectpython()
1109 usecorrectpython()
1110
1110
1111 if options.restart:
1111 if options.restart:
1112 orig = list(tests)
1112 orig = list(tests)
1113 while tests:
1113 while tests:
1114 if os.path.exists(tests[0] + ".err"):
1114 if os.path.exists(tests[0] + ".err"):
1115 break
1115 break
1116 tests.pop(0)
1116 tests.pop(0)
1117 if not tests:
1117 if not tests:
1118 print "running all tests"
1118 print "running all tests"
1119 tests = orig
1119 tests = orig
1120
1120
1121 scheduletests(options, tests)
1121 scheduletests(options, tests)
1122
1122
1123 failed = len(results['!'])
1123 failed = len(results['!'])
1124 tested = len(results['.']) + failed
1124 tested = len(results['.']) + failed
1125 skipped = len(results['s'])
1125 skipped = len(results['s'])
1126 ignored = len(results['i'])
1126 ignored = len(results['i'])
1127
1127
1128 print
1128 print
1129 if not options.noskips:
1129 if not options.noskips:
1130 for s in results['s']:
1130 for s in results['s']:
1131 print "Skipped %s: %s" % s
1131 print "Skipped %s: %s" % s
1132 for s in results['!']:
1132 for s in results['!']:
1133 print "Failed %s: %s" % s
1133 print "Failed %s: %s" % s
1134 _checkhglib("Tested")
1134 _checkhglib("Tested")
1135 print "# Ran %d tests, %d skipped, %d failed." % (
1135 print "# Ran %d tests, %d skipped, %d failed." % (
1136 tested, skipped + ignored, failed)
1136 tested, skipped + ignored, failed)
1137 if results['!']:
1138 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1137 if options.time:
1139 if options.time:
1138 outputtimes(options)
1140 outputtimes(options)
1139
1141
1140 if options.anycoverage:
1142 if options.anycoverage:
1141 outputcoverage(options)
1143 outputcoverage(options)
1142 except KeyboardInterrupt:
1144 except KeyboardInterrupt:
1143 failed = True
1145 failed = True
1144 print "\ninterrupted!"
1146 print "\ninterrupted!"
1145
1147
1146 if failed:
1148 if failed:
1147 sys.exit(1)
1149 sys.exit(1)
1148
1150
1149 testtypes = [('.py', pytest, '.out'),
1151 testtypes = [('.py', pytest, '.out'),
1150 ('.t', tsttest, '')]
1152 ('.t', tsttest, '')]
1151
1153
1152 def main():
1154 def main():
1153 (options, args) = parseargs()
1155 (options, args) = parseargs()
1154 os.umask(022)
1156 os.umask(022)
1155
1157
1156 checktools()
1158 checktools()
1157
1159
1158 if len(args) == 0:
1160 if len(args) == 0:
1159 args = [t for t in os.listdir(".")
1161 args = [t for t in os.listdir(".")
1160 if t.startswith("test-")
1162 if t.startswith("test-")
1161 and (t.endswith(".py") or t.endswith(".t"))]
1163 and (t.endswith(".py") or t.endswith(".t"))]
1162
1164
1163 tests = args
1165 tests = args
1164
1166
1165 if options.random:
1167 if options.random:
1166 random.shuffle(tests)
1168 random.shuffle(tests)
1167 else:
1169 else:
1168 # keywords for slow tests
1170 # keywords for slow tests
1169 slow = 'svn gendoc check-code-hg'.split()
1171 slow = 'svn gendoc check-code-hg'.split()
1170 def sortkey(f):
1172 def sortkey(f):
1171 # run largest tests first, as they tend to take the longest
1173 # run largest tests first, as they tend to take the longest
1172 try:
1174 try:
1173 val = -os.stat(f).st_size
1175 val = -os.stat(f).st_size
1174 except OSError, e:
1176 except OSError, e:
1175 if e.errno != errno.ENOENT:
1177 if e.errno != errno.ENOENT:
1176 raise
1178 raise
1177 return -1e9 # file does not exist, tell early
1179 return -1e9 # file does not exist, tell early
1178 for kw in slow:
1180 for kw in slow:
1179 if kw in f:
1181 if kw in f:
1180 val *= 10
1182 val *= 10
1181 return val
1183 return val
1182 tests.sort(key=sortkey)
1184 tests.sort(key=sortkey)
1183
1185
1184 if 'PYTHONHASHSEED' not in os.environ:
1186 if 'PYTHONHASHSEED' not in os.environ:
1185 # use a random python hash seed all the time
1187 # use a random python hash seed all the time
1186 # we do the randomness ourself to know what seed is used
1188 # we do the randomness ourself to know what seed is used
1187 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1189 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1188 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1189
1190
1190 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1191 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1191 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1192 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1192 if options.tmpdir:
1193 if options.tmpdir:
1193 options.keep_tmpdir = True
1194 options.keep_tmpdir = True
1194 tmpdir = options.tmpdir
1195 tmpdir = options.tmpdir
1195 if os.path.exists(tmpdir):
1196 if os.path.exists(tmpdir):
1196 # Meaning of tmpdir has changed since 1.3: we used to create
1197 # Meaning of tmpdir has changed since 1.3: we used to create
1197 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1198 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1198 # tmpdir already exists.
1199 # tmpdir already exists.
1199 sys.exit("error: temp dir %r already exists" % tmpdir)
1200 sys.exit("error: temp dir %r already exists" % tmpdir)
1200
1201
1201 # Automatically removing tmpdir sounds convenient, but could
1202 # Automatically removing tmpdir sounds convenient, but could
1202 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1203 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1203 # or "--tmpdir=$HOME".
1204 # or "--tmpdir=$HOME".
1204 #vlog("# Removing temp dir", tmpdir)
1205 #vlog("# Removing temp dir", tmpdir)
1205 #shutil.rmtree(tmpdir)
1206 #shutil.rmtree(tmpdir)
1206 os.makedirs(tmpdir)
1207 os.makedirs(tmpdir)
1207 else:
1208 else:
1208 d = None
1209 d = None
1209 if os.name == 'nt':
1210 if os.name == 'nt':
1210 # without this, we get the default temp dir location, but
1211 # without this, we get the default temp dir location, but
1211 # in all lowercase, which causes troubles with paths (issue3490)
1212 # in all lowercase, which causes troubles with paths (issue3490)
1212 d = os.getenv('TMP')
1213 d = os.getenv('TMP')
1213 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1214 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1214 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1215 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1215
1216
1216 if options.with_hg:
1217 if options.with_hg:
1217 INST = None
1218 INST = None
1218 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1219 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1219
1220
1220 # This looks redundant with how Python initializes sys.path from
1221 # This looks redundant with how Python initializes sys.path from
1221 # the location of the script being executed. Needed because the
1222 # the location of the script being executed. Needed because the
1222 # "hg" specified by --with-hg is not the only Python script
1223 # "hg" specified by --with-hg is not the only Python script
1223 # executed in the test suite that needs to import 'mercurial'
1224 # executed in the test suite that needs to import 'mercurial'
1224 # ... which means it's not really redundant at all.
1225 # ... which means it's not really redundant at all.
1225 PYTHONDIR = BINDIR
1226 PYTHONDIR = BINDIR
1226 else:
1227 else:
1227 INST = os.path.join(HGTMP, "install")
1228 INST = os.path.join(HGTMP, "install")
1228 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1229 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1229 PYTHONDIR = os.path.join(INST, "lib", "python")
1230 PYTHONDIR = os.path.join(INST, "lib", "python")
1230
1231
1231 os.environ["BINDIR"] = BINDIR
1232 os.environ["BINDIR"] = BINDIR
1232 os.environ["PYTHON"] = PYTHON
1233 os.environ["PYTHON"] = PYTHON
1233
1234
1234 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1235 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1235 os.environ["PATH"] = os.pathsep.join(path)
1236 os.environ["PATH"] = os.pathsep.join(path)
1236
1237
1237 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1238 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1238 # can run .../tests/run-tests.py test-foo where test-foo
1239 # can run .../tests/run-tests.py test-foo where test-foo
1239 # adds an extension to HGRC
1240 # adds an extension to HGRC
1240 pypath = [PYTHONDIR, TESTDIR]
1241 pypath = [PYTHONDIR, TESTDIR]
1241 # We have to augment PYTHONPATH, rather than simply replacing
1242 # We have to augment PYTHONPATH, rather than simply replacing
1242 # it, in case external libraries are only available via current
1243 # it, in case external libraries are only available via current
1243 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1244 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1244 # are in /opt/subversion.)
1245 # are in /opt/subversion.)
1245 oldpypath = os.environ.get(IMPL_PATH)
1246 oldpypath = os.environ.get(IMPL_PATH)
1246 if oldpypath:
1247 if oldpypath:
1247 pypath.append(oldpypath)
1248 pypath.append(oldpypath)
1248 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1249 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1249
1250
1250 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1251 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1251
1252
1252 vlog("# Using TESTDIR", TESTDIR)
1253 vlog("# Using TESTDIR", TESTDIR)
1253 vlog("# Using HGTMP", HGTMP)
1254 vlog("# Using HGTMP", HGTMP)
1254 vlog("# Using PATH", os.environ["PATH"])
1255 vlog("# Using PATH", os.environ["PATH"])
1255 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1256 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1256
1257
1257 try:
1258 try:
1258 runtests(options, tests)
1259 runtests(options, tests)
1259 finally:
1260 finally:
1260 time.sleep(.1)
1261 time.sleep(.1)
1261 cleanup(options)
1262 cleanup(options)
1262
1263
1263 if __name__ == '__main__':
1264 if __name__ == '__main__':
1264 main()
1265 main()
General Comments 0
You need to be logged in to leave comments. Login now