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