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