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