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