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