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