##// END OF EJS Templates
run-tests: alternative way of handling \r on Windows...
Mads Kiilerich -
r17777:af7c6bc4 default
parent child Browse files
Show More
@@ -1,1297 +1,1298 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 return run(cmd, wd, options, replacements)
479 return run(cmd, wd, options, replacements)
480
480
481 def shtest(test, wd, options, replacements):
481 def shtest(test, wd, options, replacements):
482 cmd = '%s "%s"' % (options.shell, test)
482 cmd = '%s "%s"' % (options.shell, test)
483 vlog("# Running", cmd)
483 vlog("# Running", cmd)
484 return run(cmd, wd, options, replacements)
484 return run(cmd, wd, options, replacements)
485
485
486 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
486 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
487 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
487 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))
488 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
489 escapemap.update({'\\': '\\\\', '\r': r'\r'})
489 escapemap.update({'\\': '\\\\', '\r': r'\r'})
490 def escapef(m):
490 def escapef(m):
491 return escapemap[m.group(0)]
491 return escapemap[m.group(0)]
492 def stringescape(s):
492 def stringescape(s):
493 return escapesub(escapef, s)
493 return escapesub(escapef, s)
494
494
495 def rematch(el, l):
495 def rematch(el, l):
496 try:
496 try:
497 # ensure that the regex matches to the end of the string
497 # use \Z to ensure that the regex matches to the end of the string
498 return re.match(el + r'\Z', l)
498 if os.name == 'nt':
499 return re.match(el + r'\r?\n\Z', l)
500 return re.match(el + r'\n\Z', l)
499 except re.error:
501 except re.error:
500 # el is an invalid regex
502 # el is an invalid regex
501 return False
503 return False
502
504
503 def globmatch(el, l):
505 def globmatch(el, l):
504 # The only supported special characters are * and ? plus / which also
506 # The only supported special characters are * and ? plus / which also
505 # matches \ on windows. Escaping of these caracters is supported.
507 # matches \ on windows. Escaping of these caracters is supported.
506 i, n = 0, len(el)
508 i, n = 0, len(el)
507 res = ''
509 res = ''
508 while i < n:
510 while i < n:
509 c = el[i]
511 c = el[i]
510 i += 1
512 i += 1
511 if c == '\\' and el[i] in '*?\\/':
513 if c == '\\' and el[i] in '*?\\/':
512 res += el[i - 1:i + 1]
514 res += el[i - 1:i + 1]
513 i += 1
515 i += 1
514 elif c == '*':
516 elif c == '*':
515 res += '.*'
517 res += '.*'
516 elif c == '?':
518 elif c == '?':
517 res += '.'
519 res += '.'
518 elif c == '/' and os.name == 'nt':
520 elif c == '/' and os.name == 'nt':
519 res += '[/\\\\]'
521 res += '[/\\\\]'
520 else:
522 else:
521 res += re.escape(c)
523 res += re.escape(c)
522 return rematch(res, l)
524 return rematch(res, l)
523
525
524 def linematch(el, l):
526 def linematch(el, l):
525 if el == l: # perfect match (fast)
527 if el == l: # perfect match (fast)
526 return True
528 return True
527 if (el and
529 if (el and
528 (el.endswith(" (re)\n") and rematch(el[:-6] + '\n', l) or
530 (el.endswith(" (re)\n") and rematch(el[:-6], l) or
529 el.endswith(" (glob)\n") and globmatch(el[:-8] + '\n', l) or
531 el.endswith(" (glob)\n") and globmatch(el[:-8], l) or
530 el.endswith(" (esc)\n") and
532 el.endswith(" (esc)\n") and
531 (el[:-7].decode('string-escape') + '\n' == l or
533 (el[:-7].decode('string-escape') + '\n' == l or
532 el[:-7].decode('string-escape').replace('\r', '') +
534 os.name == 'nt' and
533 '\n' == l and os.name == 'nt'))):
535 el[:-7].decode('string-escape') + '\n' == l))):
534 return True
536 return True
535 return False
537 return False
536
538
537 def tsttest(test, wd, options, replacements):
539 def tsttest(test, wd, options, replacements):
538 # We generate a shell script which outputs unique markers to line
540 # We generate a shell script which outputs unique markers to line
539 # up script results with our source. These markers include input
541 # up script results with our source. These markers include input
540 # line number and the last return code
542 # line number and the last return code
541 salt = "SALT" + str(time.time())
543 salt = "SALT" + str(time.time())
542 def addsalt(line, inpython):
544 def addsalt(line, inpython):
543 if inpython:
545 if inpython:
544 script.append('%s %d 0\n' % (salt, line))
546 script.append('%s %d 0\n' % (salt, line))
545 else:
547 else:
546 script.append('echo %s %s $?\n' % (salt, line))
548 script.append('echo %s %s $?\n' % (salt, line))
547
549
548 # After we run the shell script, we re-unify the script output
550 # After we run the shell script, we re-unify the script output
549 # with non-active parts of the source, with synchronization by our
551 # with non-active parts of the source, with synchronization by our
550 # SALT line number markers. The after table contains the
552 # SALT line number markers. The after table contains the
551 # non-active components, ordered by line number
553 # non-active components, ordered by line number
552 after = {}
554 after = {}
553 pos = prepos = -1
555 pos = prepos = -1
554
556
555 # Expected shellscript output
557 # Expected shellscript output
556 expected = {}
558 expected = {}
557
559
558 # We keep track of whether or not we're in a Python block so we
560 # We keep track of whether or not we're in a Python block so we
559 # can generate the surrounding doctest magic
561 # can generate the surrounding doctest magic
560 inpython = False
562 inpython = False
561
563
562 # True or False when in a true or false conditional section
564 # True or False when in a true or false conditional section
563 skipping = None
565 skipping = None
564
566
565 def hghave(reqs):
567 def hghave(reqs):
566 # TODO: do something smarter when all other uses of hghave is gone
568 # TODO: do something smarter when all other uses of hghave is gone
567 tdir = TESTDIR.replace('\\', '/')
569 tdir = TESTDIR.replace('\\', '/')
568 proc = Popen4('%s -c "%s/hghave %s"' %
570 proc = Popen4('%s -c "%s/hghave %s"' %
569 (options.shell, tdir, ' '.join(reqs)), wd, 0)
571 (options.shell, tdir, ' '.join(reqs)), wd, 0)
570 proc.communicate()
572 proc.communicate()
571 ret = proc.wait()
573 ret = proc.wait()
572 if wifexited(ret):
574 if wifexited(ret):
573 ret = os.WEXITSTATUS(ret)
575 ret = os.WEXITSTATUS(ret)
574 return ret == 0
576 return ret == 0
575
577
576 f = open(test)
578 f = open(test)
577 t = f.readlines()
579 t = f.readlines()
578 f.close()
580 f.close()
579
581
580 script = []
582 script = []
581 if options.debug:
583 if options.debug:
582 script.append('set -x\n')
584 script.append('set -x\n')
583 if os.getenv('MSYSTEM'):
585 if os.getenv('MSYSTEM'):
584 script.append('alias pwd="pwd -W"\n')
586 script.append('alias pwd="pwd -W"\n')
585 for n, l in enumerate(t):
587 for n, l in enumerate(t):
586 if not l.endswith('\n'):
588 if not l.endswith('\n'):
587 l += '\n'
589 l += '\n'
588 if l.startswith('#if'):
590 if l.startswith('#if'):
589 if skipping is not None:
591 if skipping is not None:
590 after.setdefault(pos, []).append(' !!! nested #if\n')
592 after.setdefault(pos, []).append(' !!! nested #if\n')
591 skipping = not hghave(l.split()[1:])
593 skipping = not hghave(l.split()[1:])
592 after.setdefault(pos, []).append(l)
594 after.setdefault(pos, []).append(l)
593 elif l.startswith('#else'):
595 elif l.startswith('#else'):
594 if skipping is None:
596 if skipping is None:
595 after.setdefault(pos, []).append(' !!! missing #if\n')
597 after.setdefault(pos, []).append(' !!! missing #if\n')
596 skipping = not skipping
598 skipping = not skipping
597 after.setdefault(pos, []).append(l)
599 after.setdefault(pos, []).append(l)
598 elif l.startswith('#endif'):
600 elif l.startswith('#endif'):
599 if skipping is None:
601 if skipping is None:
600 after.setdefault(pos, []).append(' !!! missing #if\n')
602 after.setdefault(pos, []).append(' !!! missing #if\n')
601 skipping = None
603 skipping = None
602 after.setdefault(pos, []).append(l)
604 after.setdefault(pos, []).append(l)
603 elif skipping:
605 elif skipping:
604 after.setdefault(pos, []).append(l)
606 after.setdefault(pos, []).append(l)
605 elif l.startswith(' >>> '): # python inlines
607 elif l.startswith(' >>> '): # python inlines
606 after.setdefault(pos, []).append(l)
608 after.setdefault(pos, []).append(l)
607 prepos = pos
609 prepos = pos
608 pos = n
610 pos = n
609 if not inpython:
611 if not inpython:
610 # we've just entered a Python block, add the header
612 # we've just entered a Python block, add the header
611 inpython = True
613 inpython = True
612 addsalt(prepos, False) # make sure we report the exit code
614 addsalt(prepos, False) # make sure we report the exit code
613 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
615 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
614 addsalt(n, True)
616 addsalt(n, True)
615 script.append(l[2:])
617 script.append(l[2:])
616 elif l.startswith(' ... '): # python inlines
618 elif l.startswith(' ... '): # python inlines
617 after.setdefault(prepos, []).append(l)
619 after.setdefault(prepos, []).append(l)
618 script.append(l[2:])
620 script.append(l[2:])
619 elif l.startswith(' $ '): # commands
621 elif l.startswith(' $ '): # commands
620 if inpython:
622 if inpython:
621 script.append("EOF\n")
623 script.append("EOF\n")
622 inpython = False
624 inpython = False
623 after.setdefault(pos, []).append(l)
625 after.setdefault(pos, []).append(l)
624 prepos = pos
626 prepos = pos
625 pos = n
627 pos = n
626 addsalt(n, False)
628 addsalt(n, False)
627 cmd = l[4:].split()
629 cmd = l[4:].split()
628 if len(cmd) == 2 and cmd[0] == 'cd':
630 if len(cmd) == 2 and cmd[0] == 'cd':
629 l = ' $ cd %s || exit 1\n' % cmd[1]
631 l = ' $ cd %s || exit 1\n' % cmd[1]
630 script.append(l[4:])
632 script.append(l[4:])
631 elif l.startswith(' > '): # continuations
633 elif l.startswith(' > '): # continuations
632 after.setdefault(prepos, []).append(l)
634 after.setdefault(prepos, []).append(l)
633 script.append(l[4:])
635 script.append(l[4:])
634 elif l.startswith(' '): # results
636 elif l.startswith(' '): # results
635 # queue up a list of expected results
637 # queue up a list of expected results
636 expected.setdefault(pos, []).append(l[2:])
638 expected.setdefault(pos, []).append(l[2:])
637 else:
639 else:
638 if inpython:
640 if inpython:
639 script.append("EOF\n")
641 script.append("EOF\n")
640 inpython = False
642 inpython = False
641 # non-command/result - queue up for merged output
643 # non-command/result - queue up for merged output
642 after.setdefault(pos, []).append(l)
644 after.setdefault(pos, []).append(l)
643
645
644 if inpython:
646 if inpython:
645 script.append("EOF\n")
647 script.append("EOF\n")
646 if skipping is not None:
648 if skipping is not None:
647 after.setdefault(pos, []).append(' !!! missing #endif\n')
649 after.setdefault(pos, []).append(' !!! missing #endif\n')
648 addsalt(n + 1, False)
650 addsalt(n + 1, False)
649
651
650 # Write out the script and execute it
652 # Write out the script and execute it
651 fd, name = tempfile.mkstemp(suffix='hg-tst')
653 fd, name = tempfile.mkstemp(suffix='hg-tst')
652 try:
654 try:
653 for l in script:
655 for l in script:
654 os.write(fd, l)
656 os.write(fd, l)
655 os.close(fd)
657 os.close(fd)
656
658
657 cmd = '%s "%s"' % (options.shell, name)
659 cmd = '%s "%s"' % (options.shell, name)
658 vlog("# Running", cmd)
660 vlog("# Running", cmd)
659 exitcode, output = run(cmd, wd, options, replacements)
661 exitcode, output = run(cmd, wd, options, replacements)
660 # do not merge output if skipped, return hghave message instead
662 # do not merge output if skipped, return hghave message instead
661 # similarly, with --debug, output is None
663 # similarly, with --debug, output is None
662 if exitcode == SKIPPED_STATUS or output is None:
664 if exitcode == SKIPPED_STATUS or output is None:
663 return exitcode, output
665 return exitcode, output
664 finally:
666 finally:
665 os.remove(name)
667 os.remove(name)
666
668
667 # Merge the script output back into a unified test
669 # Merge the script output back into a unified test
668
670
669 pos = -1
671 pos = -1
670 postout = []
672 postout = []
671 ret = 0
673 ret = 0
672 for l in output:
674 for l in output:
673 lout, lcmd = l, None
675 lout, lcmd = l, None
674 if salt in l:
676 if salt in l:
675 lout, lcmd = l.split(salt, 1)
677 lout, lcmd = l.split(salt, 1)
676
678
677 if lout:
679 if lout:
678 if not lout.endswith('\n'):
680 if not lout.endswith('\n'):
679 lout += ' (no-eol)\n'
681 lout += ' (no-eol)\n'
680
682
681 # find the expected output at the current position
683 # find the expected output at the current position
682 el = None
684 el = None
683 if pos in expected and expected[pos]:
685 if pos in expected and expected[pos]:
684 el = expected[pos].pop(0)
686 el = expected[pos].pop(0)
685
687
686 if linematch(el, lout):
688 if linematch(el, lout):
687 postout.append(" " + el)
689 postout.append(" " + el)
688 else:
690 else:
689 if needescape(lout):
691 if needescape(lout):
690 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
692 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
691 postout.append(" " + lout) # let diff deal with it
693 postout.append(" " + lout) # let diff deal with it
692
694
693 if lcmd:
695 if lcmd:
694 # add on last return code
696 # add on last return code
695 ret = int(lcmd.split()[1])
697 ret = int(lcmd.split()[1])
696 if ret != 0:
698 if ret != 0:
697 postout.append(" [%s]\n" % ret)
699 postout.append(" [%s]\n" % ret)
698 if pos in after:
700 if pos in after:
699 # merge in non-active test bits
701 # merge in non-active test bits
700 postout += after.pop(pos)
702 postout += after.pop(pos)
701 pos = int(lcmd.split()[0])
703 pos = int(lcmd.split()[0])
702
704
703 if pos in after:
705 if pos in after:
704 postout += after.pop(pos)
706 postout += after.pop(pos)
705
707
706 return exitcode, postout
708 return exitcode, postout
707
709
708 wifexited = getattr(os, "WIFEXITED", lambda x: False)
710 wifexited = getattr(os, "WIFEXITED", lambda x: False)
709 def run(cmd, wd, options, replacements):
711 def run(cmd, wd, options, replacements):
710 """Run command in a sub-process, capturing the output (stdout and stderr).
712 """Run command in a sub-process, capturing the output (stdout and stderr).
711 Return a tuple (exitcode, output). output is None in debug mode."""
713 Return a tuple (exitcode, output). output is None in debug mode."""
712 # TODO: Use subprocess.Popen if we're running on Python 2.4
714 # TODO: Use subprocess.Popen if we're running on Python 2.4
713 if options.debug:
715 if options.debug:
714 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
716 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
715 ret = proc.wait()
717 ret = proc.wait()
716 return (ret, None)
718 return (ret, None)
717
719
718 proc = Popen4(cmd, wd, options.timeout)
720 proc = Popen4(cmd, wd, options.timeout)
719 def cleanup():
721 def cleanup():
720 terminate(proc)
722 terminate(proc)
721 ret = proc.wait()
723 ret = proc.wait()
722 if ret == 0:
724 if ret == 0:
723 ret = signal.SIGTERM << 8
725 ret = signal.SIGTERM << 8
724 killdaemons()
726 killdaemons()
725 return ret
727 return ret
726
728
727 output = ''
729 output = ''
728 proc.tochild.close()
730 proc.tochild.close()
729
731
730 try:
732 try:
731 output = proc.fromchild.read()
733 output = proc.fromchild.read()
732 except KeyboardInterrupt:
734 except KeyboardInterrupt:
733 vlog('# Handling keyboard interrupt')
735 vlog('# Handling keyboard interrupt')
734 cleanup()
736 cleanup()
735 raise
737 raise
736
738
737 ret = proc.wait()
739 ret = proc.wait()
738 if wifexited(ret):
740 if wifexited(ret):
739 ret = os.WEXITSTATUS(ret)
741 ret = os.WEXITSTATUS(ret)
740
742
741 if proc.timeout:
743 if proc.timeout:
742 ret = 'timeout'
744 ret = 'timeout'
743
745
744 if ret:
746 if ret:
745 killdaemons()
747 killdaemons()
746
748
747 for s, r in replacements:
749 for s, r in replacements:
748 output = re.sub(s, r, output)
750 output = re.sub(s, r, output)
749 return ret, output.splitlines(True)
751 return ret, output.splitlines(True)
750
752
751 def runone(options, test):
753 def runone(options, test):
752 '''tristate output:
754 '''tristate output:
753 None -> skipped
755 None -> skipped
754 True -> passed
756 True -> passed
755 False -> failed'''
757 False -> failed'''
756
758
757 global results, resultslock, iolock
759 global results, resultslock, iolock
758
760
759 testpath = os.path.join(TESTDIR, test)
761 testpath = os.path.join(TESTDIR, test)
760
762
761 def result(l, e):
763 def result(l, e):
762 resultslock.acquire()
764 resultslock.acquire()
763 results[l].append(e)
765 results[l].append(e)
764 resultslock.release()
766 resultslock.release()
765
767
766 def skip(msg):
768 def skip(msg):
767 if not options.verbose:
769 if not options.verbose:
768 result('s', (test, msg))
770 result('s', (test, msg))
769 else:
771 else:
770 iolock.acquire()
772 iolock.acquire()
771 print "\nSkipping %s: %s" % (testpath, msg)
773 print "\nSkipping %s: %s" % (testpath, msg)
772 iolock.release()
774 iolock.release()
773 return None
775 return None
774
776
775 def fail(msg, ret):
777 def fail(msg, ret):
776 if not options.nodiff:
778 if not options.nodiff:
777 iolock.acquire()
779 iolock.acquire()
778 print "\nERROR: %s %s" % (testpath, msg)
780 print "\nERROR: %s %s" % (testpath, msg)
779 iolock.release()
781 iolock.release()
780 if (not ret and options.interactive
782 if (not ret and options.interactive
781 and os.path.exists(testpath + ".err")):
783 and os.path.exists(testpath + ".err")):
782 iolock.acquire()
784 iolock.acquire()
783 print "Accept this change? [n] ",
785 print "Accept this change? [n] ",
784 answer = sys.stdin.readline().strip()
786 answer = sys.stdin.readline().strip()
785 iolock.release()
787 iolock.release()
786 if answer.lower() in "y yes".split():
788 if answer.lower() in "y yes".split():
787 if test.endswith(".t"):
789 if test.endswith(".t"):
788 rename(testpath + ".err", testpath)
790 rename(testpath + ".err", testpath)
789 else:
791 else:
790 rename(testpath + ".err", testpath + ".out")
792 rename(testpath + ".err", testpath + ".out")
791 result('p', test)
793 result('p', test)
792 return
794 return
793 result('f', (test, msg))
795 result('f', (test, msg))
794
796
795 def success():
797 def success():
796 result('p', test)
798 result('p', test)
797
799
798 def ignore(msg):
800 def ignore(msg):
799 result('i', (test, msg))
801 result('i', (test, msg))
800
802
801 if (os.path.basename(test).startswith("test-") and '~' not in test and
803 if (os.path.basename(test).startswith("test-") and '~' not in test and
802 ('.' not in test or test.endswith('.py') or
804 ('.' not in test or test.endswith('.py') or
803 test.endswith('.bat') or test.endswith('.t'))):
805 test.endswith('.bat') or test.endswith('.t'))):
804 if not os.path.exists(test):
806 if not os.path.exists(test):
805 skip("doesn't exist")
807 skip("doesn't exist")
806 return None
808 return None
807 else:
809 else:
808 vlog('# Test file', test, 'not supported, ignoring')
810 vlog('# Test file', test, 'not supported, ignoring')
809 return None # not a supported test, don't record
811 return None # not a supported test, don't record
810
812
811 if not (options.whitelisted and test in options.whitelisted):
813 if not (options.whitelisted and test in options.whitelisted):
812 if options.blacklist and test in options.blacklist:
814 if options.blacklist and test in options.blacklist:
813 skip("blacklisted")
815 skip("blacklisted")
814 return None
816 return None
815
817
816 if options.retest and not os.path.exists(test + ".err"):
818 if options.retest and not os.path.exists(test + ".err"):
817 ignore("not retesting")
819 ignore("not retesting")
818 return None
820 return None
819
821
820 if options.keywords:
822 if options.keywords:
821 fp = open(test)
823 fp = open(test)
822 t = fp.read().lower() + test.lower()
824 t = fp.read().lower() + test.lower()
823 fp.close()
825 fp.close()
824 for k in options.keywords.lower().split():
826 for k in options.keywords.lower().split():
825 if k in t:
827 if k in t:
826 break
828 break
827 else:
829 else:
828 ignore("doesn't match keyword")
830 ignore("doesn't match keyword")
829 return None
831 return None
830
832
831 vlog("# Test", test)
833 vlog("# Test", test)
832
834
833 # create a fresh hgrc
835 # create a fresh hgrc
834 hgrc = open(HGRCPATH, 'w+')
836 hgrc = open(HGRCPATH, 'w+')
835 hgrc.write('[ui]\n')
837 hgrc.write('[ui]\n')
836 hgrc.write('slash = True\n')
838 hgrc.write('slash = True\n')
837 hgrc.write('[defaults]\n')
839 hgrc.write('[defaults]\n')
838 hgrc.write('backout = -d "0 0"\n')
840 hgrc.write('backout = -d "0 0"\n')
839 hgrc.write('commit = -d "0 0"\n')
841 hgrc.write('commit = -d "0 0"\n')
840 hgrc.write('tag = -d "0 0"\n')
842 hgrc.write('tag = -d "0 0"\n')
841 if options.inotify:
843 if options.inotify:
842 hgrc.write('[extensions]\n')
844 hgrc.write('[extensions]\n')
843 hgrc.write('inotify=\n')
845 hgrc.write('inotify=\n')
844 hgrc.write('[inotify]\n')
846 hgrc.write('[inotify]\n')
845 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
847 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
846 hgrc.write('appendpid=True\n')
848 hgrc.write('appendpid=True\n')
847 if options.extra_config_opt:
849 if options.extra_config_opt:
848 for opt in options.extra_config_opt:
850 for opt in options.extra_config_opt:
849 section, key = opt.split('.', 1)
851 section, key = opt.split('.', 1)
850 assert '=' in key, ('extra config opt %s must '
852 assert '=' in key, ('extra config opt %s must '
851 'have an = for assignment' % opt)
853 'have an = for assignment' % opt)
852 hgrc.write('[%s]\n%s\n' % (section, key))
854 hgrc.write('[%s]\n%s\n' % (section, key))
853 hgrc.close()
855 hgrc.close()
854
856
855 ref = os.path.join(TESTDIR, test+".out")
857 ref = os.path.join(TESTDIR, test+".out")
856 err = os.path.join(TESTDIR, test+".err")
858 err = os.path.join(TESTDIR, test+".err")
857 if os.path.exists(err):
859 if os.path.exists(err):
858 os.remove(err) # Remove any previous output files
860 os.remove(err) # Remove any previous output files
859 try:
861 try:
860 tf = open(testpath)
862 tf = open(testpath)
861 firstline = tf.readline().rstrip()
863 firstline = tf.readline().rstrip()
862 tf.close()
864 tf.close()
863 except IOError:
865 except IOError:
864 firstline = ''
866 firstline = ''
865 lctest = test.lower()
867 lctest = test.lower()
866
868
867 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
869 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
868 runner = pytest
870 runner = pytest
869 elif lctest.endswith('.t'):
871 elif lctest.endswith('.t'):
870 runner = tsttest
872 runner = tsttest
871 ref = testpath
873 ref = testpath
872 else:
874 else:
873 # do not try to run non-executable programs
875 # do not try to run non-executable programs
874 if not os.access(testpath, os.X_OK):
876 if not os.access(testpath, os.X_OK):
875 return skip("not executable")
877 return skip("not executable")
876 runner = shtest
878 runner = shtest
877
879
878 # Make a tmp subdirectory to work in
880 # Make a tmp subdirectory to work in
879 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
881 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
880 os.path.join(HGTMP, os.path.basename(test))
882 os.path.join(HGTMP, os.path.basename(test))
881
883
882 replacements = [
884 replacements = [
883 (r':%s\b' % options.port, ':$HGPORT'),
885 (r':%s\b' % options.port, ':$HGPORT'),
884 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
886 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
885 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
887 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
886 ]
888 ]
887 if os.name == 'nt':
889 if os.name == 'nt':
888 replacements.append((r'\r\n', '\n'))
889 replacements.append(
890 replacements.append(
890 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
891 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
891 c in '/\\' and r'[/\\]' or
892 c in '/\\' and r'[/\\]' or
892 c.isdigit() and c or
893 c.isdigit() and c or
893 '\\' + c
894 '\\' + c
894 for c in testtmp), '$TESTTMP'))
895 for c in testtmp), '$TESTTMP'))
895 else:
896 else:
896 replacements.append((re.escape(testtmp), '$TESTTMP'))
897 replacements.append((re.escape(testtmp), '$TESTTMP'))
897
898
898 os.mkdir(testtmp)
899 os.mkdir(testtmp)
899 ret, out = runner(testpath, testtmp, options, replacements)
900 ret, out = runner(testpath, testtmp, options, replacements)
900 vlog("# Ret was:", ret)
901 vlog("# Ret was:", ret)
901
902
902 mark = '.'
903 mark = '.'
903
904
904 skipped = (ret == SKIPPED_STATUS)
905 skipped = (ret == SKIPPED_STATUS)
905
906
906 # If we're not in --debug mode and reference output file exists,
907 # If we're not in --debug mode and reference output file exists,
907 # check test output against it.
908 # check test output against it.
908 if options.debug:
909 if options.debug:
909 refout = None # to match "out is None"
910 refout = None # to match "out is None"
910 elif os.path.exists(ref):
911 elif os.path.exists(ref):
911 f = open(ref, "r")
912 f = open(ref, "r")
912 refout = f.read().splitlines(True)
913 refout = f.read().splitlines(True)
913 f.close()
914 f.close()
914 else:
915 else:
915 refout = []
916 refout = []
916
917
917 if (ret != 0 or out != refout) and not skipped and not options.debug:
918 if (ret != 0 or out != refout) and not skipped and not options.debug:
918 # Save errors to a file for diagnosis
919 # Save errors to a file for diagnosis
919 f = open(err, "wb")
920 f = open(err, "wb")
920 for line in out:
921 for line in out:
921 f.write(line)
922 f.write(line)
922 f.close()
923 f.close()
923
924
924 def describe(ret):
925 def describe(ret):
925 if ret < 0:
926 if ret < 0:
926 return 'killed by signal %d' % -ret
927 return 'killed by signal %d' % -ret
927 return 'returned error code %d' % ret
928 return 'returned error code %d' % ret
928
929
929 if skipped:
930 if skipped:
930 mark = 's'
931 mark = 's'
931 if out is None: # debug mode: nothing to parse
932 if out is None: # debug mode: nothing to parse
932 missing = ['unknown']
933 missing = ['unknown']
933 failed = None
934 failed = None
934 else:
935 else:
935 missing, failed = parsehghaveoutput(out)
936 missing, failed = parsehghaveoutput(out)
936 if not missing:
937 if not missing:
937 missing = ['irrelevant']
938 missing = ['irrelevant']
938 if failed:
939 if failed:
939 fail("hghave failed checking for %s" % failed[-1], ret)
940 fail("hghave failed checking for %s" % failed[-1], ret)
940 skipped = False
941 skipped = False
941 else:
942 else:
942 skip(missing[-1])
943 skip(missing[-1])
943 elif ret == 'timeout':
944 elif ret == 'timeout':
944 mark = 't'
945 mark = 't'
945 fail("timed out", ret)
946 fail("timed out", ret)
946 elif out != refout:
947 elif out != refout:
947 mark = '!'
948 mark = '!'
948 if not options.nodiff:
949 if not options.nodiff:
949 iolock.acquire()
950 iolock.acquire()
950 if options.view:
951 if options.view:
951 os.system("%s %s %s" % (options.view, ref, err))
952 os.system("%s %s %s" % (options.view, ref, err))
952 else:
953 else:
953 showdiff(refout, out, ref, err)
954 showdiff(refout, out, ref, err)
954 iolock.release()
955 iolock.release()
955 if ret:
956 if ret:
956 fail("output changed and " + describe(ret), ret)
957 fail("output changed and " + describe(ret), ret)
957 else:
958 else:
958 fail("output changed", ret)
959 fail("output changed", ret)
959 ret = 1
960 ret = 1
960 elif ret:
961 elif ret:
961 mark = '!'
962 mark = '!'
962 fail(describe(ret), ret)
963 fail(describe(ret), ret)
963 else:
964 else:
964 success()
965 success()
965
966
966 if not options.verbose:
967 if not options.verbose:
967 iolock.acquire()
968 iolock.acquire()
968 sys.stdout.write(mark)
969 sys.stdout.write(mark)
969 sys.stdout.flush()
970 sys.stdout.flush()
970 iolock.release()
971 iolock.release()
971
972
972 killdaemons()
973 killdaemons()
973
974
974 if not options.keep_tmpdir:
975 if not options.keep_tmpdir:
975 shutil.rmtree(testtmp, True)
976 shutil.rmtree(testtmp, True)
976 if skipped:
977 if skipped:
977 return None
978 return None
978 return ret == 0
979 return ret == 0
979
980
980 _hgpath = None
981 _hgpath = None
981
982
982 def _gethgpath():
983 def _gethgpath():
983 """Return the path to the mercurial package that is actually found by
984 """Return the path to the mercurial package that is actually found by
984 the current Python interpreter."""
985 the current Python interpreter."""
985 global _hgpath
986 global _hgpath
986 if _hgpath is not None:
987 if _hgpath is not None:
987 return _hgpath
988 return _hgpath
988
989
989 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
990 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
990 pipe = os.popen(cmd % PYTHON)
991 pipe = os.popen(cmd % PYTHON)
991 try:
992 try:
992 _hgpath = pipe.read().strip()
993 _hgpath = pipe.read().strip()
993 finally:
994 finally:
994 pipe.close()
995 pipe.close()
995 return _hgpath
996 return _hgpath
996
997
997 def _checkhglib(verb):
998 def _checkhglib(verb):
998 """Ensure that the 'mercurial' package imported by python is
999 """Ensure that the 'mercurial' package imported by python is
999 the one we expect it to be. If not, print a warning to stderr."""
1000 the one we expect it to be. If not, print a warning to stderr."""
1000 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1001 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1001 actualhg = _gethgpath()
1002 actualhg = _gethgpath()
1002 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1003 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1003 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1004 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1004 ' (expected %s)\n'
1005 ' (expected %s)\n'
1005 % (verb, actualhg, expecthg))
1006 % (verb, actualhg, expecthg))
1006
1007
1007 def runchildren(options, tests):
1008 def runchildren(options, tests):
1008 if INST:
1009 if INST:
1009 installhg(options)
1010 installhg(options)
1010 _checkhglib("Testing")
1011 _checkhglib("Testing")
1011
1012
1012 optcopy = dict(options.__dict__)
1013 optcopy = dict(options.__dict__)
1013 optcopy['jobs'] = 1
1014 optcopy['jobs'] = 1
1014
1015
1015 # Because whitelist has to override keyword matches, we have to
1016 # Because whitelist has to override keyword matches, we have to
1016 # actually load the whitelist in the children as well, so we allow
1017 # actually load the whitelist in the children as well, so we allow
1017 # the list of whitelist files to pass through and be parsed in the
1018 # the list of whitelist files to pass through and be parsed in the
1018 # children, but not the dict of whitelisted tests resulting from
1019 # children, but not the dict of whitelisted tests resulting from
1019 # the parse, used here to override blacklisted tests.
1020 # the parse, used here to override blacklisted tests.
1020 whitelist = optcopy['whitelisted'] or []
1021 whitelist = optcopy['whitelisted'] or []
1021 del optcopy['whitelisted']
1022 del optcopy['whitelisted']
1022
1023
1023 blacklist = optcopy['blacklist'] or []
1024 blacklist = optcopy['blacklist'] or []
1024 del optcopy['blacklist']
1025 del optcopy['blacklist']
1025 blacklisted = []
1026 blacklisted = []
1026
1027
1027 if optcopy['with_hg'] is None:
1028 if optcopy['with_hg'] is None:
1028 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1029 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1029 optcopy.pop('anycoverage', None)
1030 optcopy.pop('anycoverage', None)
1030
1031
1031 opts = []
1032 opts = []
1032 for opt, value in optcopy.iteritems():
1033 for opt, value in optcopy.iteritems():
1033 name = '--' + opt.replace('_', '-')
1034 name = '--' + opt.replace('_', '-')
1034 if value is True:
1035 if value is True:
1035 opts.append(name)
1036 opts.append(name)
1036 elif isinstance(value, list):
1037 elif isinstance(value, list):
1037 for v in value:
1038 for v in value:
1038 opts.append(name + '=' + str(v))
1039 opts.append(name + '=' + str(v))
1039 elif value is not None:
1040 elif value is not None:
1040 opts.append(name + '=' + str(value))
1041 opts.append(name + '=' + str(value))
1041
1042
1042 tests.reverse()
1043 tests.reverse()
1043 jobs = [[] for j in xrange(options.jobs)]
1044 jobs = [[] for j in xrange(options.jobs)]
1044 while tests:
1045 while tests:
1045 for job in jobs:
1046 for job in jobs:
1046 if not tests:
1047 if not tests:
1047 break
1048 break
1048 test = tests.pop()
1049 test = tests.pop()
1049 if test not in whitelist and test in blacklist:
1050 if test not in whitelist and test in blacklist:
1050 blacklisted.append(test)
1051 blacklisted.append(test)
1051 else:
1052 else:
1052 job.append(test)
1053 job.append(test)
1053 fps = {}
1054 fps = {}
1054
1055
1055 for j, job in enumerate(jobs):
1056 for j, job in enumerate(jobs):
1056 if not job:
1057 if not job:
1057 continue
1058 continue
1058 rfd, wfd = os.pipe()
1059 rfd, wfd = os.pipe()
1059 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1060 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1060 childtmp = os.path.join(HGTMP, 'child%d' % j)
1061 childtmp = os.path.join(HGTMP, 'child%d' % j)
1061 childopts += ['--tmpdir', childtmp]
1062 childopts += ['--tmpdir', childtmp]
1062 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1063 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1063 vlog(' '.join(cmdline))
1064 vlog(' '.join(cmdline))
1064 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1065 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
1065 os.close(wfd)
1066 os.close(wfd)
1066 signal.signal(signal.SIGINT, signal.SIG_IGN)
1067 signal.signal(signal.SIGINT, signal.SIG_IGN)
1067 failures = 0
1068 failures = 0
1068 tested, skipped, failed = 0, 0, 0
1069 tested, skipped, failed = 0, 0, 0
1069 skips = []
1070 skips = []
1070 fails = []
1071 fails = []
1071 while fps:
1072 while fps:
1072 pid, status = os.wait()
1073 pid, status = os.wait()
1073 fp = fps.pop(pid)
1074 fp = fps.pop(pid)
1074 l = fp.read().splitlines()
1075 l = fp.read().splitlines()
1075 try:
1076 try:
1076 test, skip, fail = map(int, l[:3])
1077 test, skip, fail = map(int, l[:3])
1077 except ValueError:
1078 except ValueError:
1078 test, skip, fail = 0, 0, 0
1079 test, skip, fail = 0, 0, 0
1079 split = -fail or len(l)
1080 split = -fail or len(l)
1080 for s in l[3:split]:
1081 for s in l[3:split]:
1081 skips.append(s.split(" ", 1))
1082 skips.append(s.split(" ", 1))
1082 for s in l[split:]:
1083 for s in l[split:]:
1083 fails.append(s.split(" ", 1))
1084 fails.append(s.split(" ", 1))
1084 tested += test
1085 tested += test
1085 skipped += skip
1086 skipped += skip
1086 failed += fail
1087 failed += fail
1087 vlog('pid %d exited, status %d' % (pid, status))
1088 vlog('pid %d exited, status %d' % (pid, status))
1088 failures |= status
1089 failures |= status
1089 print
1090 print
1090 skipped += len(blacklisted)
1091 skipped += len(blacklisted)
1091 if not options.noskips:
1092 if not options.noskips:
1092 for s in skips:
1093 for s in skips:
1093 print "Skipped %s: %s" % (s[0], s[1])
1094 print "Skipped %s: %s" % (s[0], s[1])
1094 for s in blacklisted:
1095 for s in blacklisted:
1095 print "Skipped %s: blacklisted" % s
1096 print "Skipped %s: blacklisted" % s
1096 for s in fails:
1097 for s in fails:
1097 print "Failed %s: %s" % (s[0], s[1])
1098 print "Failed %s: %s" % (s[0], s[1])
1098
1099
1099 _checkhglib("Tested")
1100 _checkhglib("Tested")
1100 print "# Ran %d tests, %d skipped, %d failed." % (
1101 print "# Ran %d tests, %d skipped, %d failed." % (
1101 tested, skipped, failed)
1102 tested, skipped, failed)
1102
1103
1103 if options.anycoverage:
1104 if options.anycoverage:
1104 outputcoverage(options)
1105 outputcoverage(options)
1105 sys.exit(failures != 0)
1106 sys.exit(failures != 0)
1106
1107
1107 results = dict(p=[], f=[], s=[], i=[])
1108 results = dict(p=[], f=[], s=[], i=[])
1108 resultslock = threading.Lock()
1109 resultslock = threading.Lock()
1109 iolock = threading.Lock()
1110 iolock = threading.Lock()
1110
1111
1111 def runqueue(options, tests, results):
1112 def runqueue(options, tests, results):
1112 for test in tests:
1113 for test in tests:
1113 ret = runone(options, test)
1114 ret = runone(options, test)
1114 if options.first and ret is not None and not ret:
1115 if options.first and ret is not None and not ret:
1115 break
1116 break
1116
1117
1117 def runtests(options, tests):
1118 def runtests(options, tests):
1118 global DAEMON_PIDS, HGRCPATH
1119 global DAEMON_PIDS, HGRCPATH
1119 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1120 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1120 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1121 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1121
1122
1122 try:
1123 try:
1123 if INST:
1124 if INST:
1124 installhg(options)
1125 installhg(options)
1125 _checkhglib("Testing")
1126 _checkhglib("Testing")
1126
1127
1127 if options.restart:
1128 if options.restart:
1128 orig = list(tests)
1129 orig = list(tests)
1129 while tests:
1130 while tests:
1130 if os.path.exists(tests[0] + ".err"):
1131 if os.path.exists(tests[0] + ".err"):
1131 break
1132 break
1132 tests.pop(0)
1133 tests.pop(0)
1133 if not tests:
1134 if not tests:
1134 print "running all tests"
1135 print "running all tests"
1135 tests = orig
1136 tests = orig
1136
1137
1137 runqueue(options, tests, results)
1138 runqueue(options, tests, results)
1138
1139
1139 failed = len(results['f'])
1140 failed = len(results['f'])
1140 tested = len(results['p']) + failed
1141 tested = len(results['p']) + failed
1141 skipped = len(results['s'])
1142 skipped = len(results['s'])
1142 ignored = len(results['i'])
1143 ignored = len(results['i'])
1143
1144
1144 if options.child:
1145 if options.child:
1145 fp = os.fdopen(options.child, 'w')
1146 fp = os.fdopen(options.child, 'w')
1146 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1147 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
1147 for s in results['s']:
1148 for s in results['s']:
1148 fp.write("%s %s\n" % s)
1149 fp.write("%s %s\n" % s)
1149 for s in results['f']:
1150 for s in results['f']:
1150 fp.write("%s %s\n" % s)
1151 fp.write("%s %s\n" % s)
1151 fp.close()
1152 fp.close()
1152 else:
1153 else:
1153 print
1154 print
1154 for s in results['s']:
1155 for s in results['s']:
1155 print "Skipped %s: %s" % s
1156 print "Skipped %s: %s" % s
1156 for s in results['f']:
1157 for s in results['f']:
1157 print "Failed %s: %s" % s
1158 print "Failed %s: %s" % s
1158 _checkhglib("Tested")
1159 _checkhglib("Tested")
1159 print "# Ran %d tests, %d skipped, %d failed." % (
1160 print "# Ran %d tests, %d skipped, %d failed." % (
1160 tested, skipped + ignored, failed)
1161 tested, skipped + ignored, failed)
1161
1162
1162 if options.anycoverage:
1163 if options.anycoverage:
1163 outputcoverage(options)
1164 outputcoverage(options)
1164 except KeyboardInterrupt:
1165 except KeyboardInterrupt:
1165 failed = True
1166 failed = True
1166 print "\ninterrupted!"
1167 print "\ninterrupted!"
1167
1168
1168 if failed:
1169 if failed:
1169 sys.exit(1)
1170 sys.exit(1)
1170
1171
1171 def main():
1172 def main():
1172 (options, args) = parseargs()
1173 (options, args) = parseargs()
1173 if not options.child:
1174 if not options.child:
1174 os.umask(022)
1175 os.umask(022)
1175
1176
1176 checktools()
1177 checktools()
1177
1178
1178 if len(args) == 0:
1179 if len(args) == 0:
1179 args = os.listdir(".")
1180 args = os.listdir(".")
1180 args.sort()
1181 args.sort()
1181
1182
1182 tests = args
1183 tests = args
1183
1184
1184 # Reset some environment variables to well-known values so that
1185 # Reset some environment variables to well-known values so that
1185 # the tests produce repeatable output.
1186 # the tests produce repeatable output.
1186 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1187 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1187 os.environ['TZ'] = 'GMT'
1188 os.environ['TZ'] = 'GMT'
1188 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1189 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1189 os.environ['CDPATH'] = ''
1190 os.environ['CDPATH'] = ''
1190 os.environ['COLUMNS'] = '80'
1191 os.environ['COLUMNS'] = '80'
1191 os.environ['GREP_OPTIONS'] = ''
1192 os.environ['GREP_OPTIONS'] = ''
1192 os.environ['http_proxy'] = ''
1193 os.environ['http_proxy'] = ''
1193 os.environ['no_proxy'] = ''
1194 os.environ['no_proxy'] = ''
1194 os.environ['NO_PROXY'] = ''
1195 os.environ['NO_PROXY'] = ''
1195 os.environ['TERM'] = 'xterm'
1196 os.environ['TERM'] = 'xterm'
1196
1197
1197 # unset env related to hooks
1198 # unset env related to hooks
1198 for k in os.environ.keys():
1199 for k in os.environ.keys():
1199 if k.startswith('HG_'):
1200 if k.startswith('HG_'):
1200 # can't remove on solaris
1201 # can't remove on solaris
1201 os.environ[k] = ''
1202 os.environ[k] = ''
1202 del os.environ[k]
1203 del os.environ[k]
1203 if 'HG' in os.environ:
1204 if 'HG' in os.environ:
1204 # can't remove on solaris
1205 # can't remove on solaris
1205 os.environ['HG'] = ''
1206 os.environ['HG'] = ''
1206 del os.environ['HG']
1207 del os.environ['HG']
1207
1208
1208 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1209 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1209 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1210 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1210 if options.tmpdir:
1211 if options.tmpdir:
1211 options.keep_tmpdir = True
1212 options.keep_tmpdir = True
1212 tmpdir = options.tmpdir
1213 tmpdir = options.tmpdir
1213 if os.path.exists(tmpdir):
1214 if os.path.exists(tmpdir):
1214 # Meaning of tmpdir has changed since 1.3: we used to create
1215 # Meaning of tmpdir has changed since 1.3: we used to create
1215 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1216 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1216 # tmpdir already exists.
1217 # tmpdir already exists.
1217 sys.exit("error: temp dir %r already exists" % tmpdir)
1218 sys.exit("error: temp dir %r already exists" % tmpdir)
1218
1219
1219 # Automatically removing tmpdir sounds convenient, but could
1220 # Automatically removing tmpdir sounds convenient, but could
1220 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1221 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1221 # or "--tmpdir=$HOME".
1222 # or "--tmpdir=$HOME".
1222 #vlog("# Removing temp dir", tmpdir)
1223 #vlog("# Removing temp dir", tmpdir)
1223 #shutil.rmtree(tmpdir)
1224 #shutil.rmtree(tmpdir)
1224 os.makedirs(tmpdir)
1225 os.makedirs(tmpdir)
1225 else:
1226 else:
1226 d = None
1227 d = None
1227 if os.name == 'nt':
1228 if os.name == 'nt':
1228 # without this, we get the default temp dir location, but
1229 # without this, we get the default temp dir location, but
1229 # in all lowercase, which causes troubles with paths (issue3490)
1230 # in all lowercase, which causes troubles with paths (issue3490)
1230 d = os.getenv('TMP')
1231 d = os.getenv('TMP')
1231 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1232 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1232 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1233 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1233 DAEMON_PIDS = None
1234 DAEMON_PIDS = None
1234 HGRCPATH = None
1235 HGRCPATH = None
1235
1236
1236 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1237 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1237 os.environ["HGMERGE"] = "internal:merge"
1238 os.environ["HGMERGE"] = "internal:merge"
1238 os.environ["HGUSER"] = "test"
1239 os.environ["HGUSER"] = "test"
1239 os.environ["HGENCODING"] = "ascii"
1240 os.environ["HGENCODING"] = "ascii"
1240 os.environ["HGENCODINGMODE"] = "strict"
1241 os.environ["HGENCODINGMODE"] = "strict"
1241 os.environ["HGPORT"] = str(options.port)
1242 os.environ["HGPORT"] = str(options.port)
1242 os.environ["HGPORT1"] = str(options.port + 1)
1243 os.environ["HGPORT1"] = str(options.port + 1)
1243 os.environ["HGPORT2"] = str(options.port + 2)
1244 os.environ["HGPORT2"] = str(options.port + 2)
1244
1245
1245 if options.with_hg:
1246 if options.with_hg:
1246 INST = None
1247 INST = None
1247 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1248 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1248
1249
1249 # This looks redundant with how Python initializes sys.path from
1250 # This looks redundant with how Python initializes sys.path from
1250 # the location of the script being executed. Needed because the
1251 # the location of the script being executed. Needed because the
1251 # "hg" specified by --with-hg is not the only Python script
1252 # "hg" specified by --with-hg is not the only Python script
1252 # executed in the test suite that needs to import 'mercurial'
1253 # executed in the test suite that needs to import 'mercurial'
1253 # ... which means it's not really redundant at all.
1254 # ... which means it's not really redundant at all.
1254 PYTHONDIR = BINDIR
1255 PYTHONDIR = BINDIR
1255 else:
1256 else:
1256 INST = os.path.join(HGTMP, "install")
1257 INST = os.path.join(HGTMP, "install")
1257 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1258 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1258 PYTHONDIR = os.path.join(INST, "lib", "python")
1259 PYTHONDIR = os.path.join(INST, "lib", "python")
1259
1260
1260 os.environ["BINDIR"] = BINDIR
1261 os.environ["BINDIR"] = BINDIR
1261 os.environ["PYTHON"] = PYTHON
1262 os.environ["PYTHON"] = PYTHON
1262
1263
1263 if not options.child:
1264 if not options.child:
1264 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1265 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1265 os.environ["PATH"] = os.pathsep.join(path)
1266 os.environ["PATH"] = os.pathsep.join(path)
1266
1267
1267 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1268 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1268 # can run .../tests/run-tests.py test-foo where test-foo
1269 # can run .../tests/run-tests.py test-foo where test-foo
1269 # adds an extension to HGRC
1270 # adds an extension to HGRC
1270 pypath = [PYTHONDIR, TESTDIR]
1271 pypath = [PYTHONDIR, TESTDIR]
1271 # We have to augment PYTHONPATH, rather than simply replacing
1272 # We have to augment PYTHONPATH, rather than simply replacing
1272 # it, in case external libraries are only available via current
1273 # it, in case external libraries are only available via current
1273 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1274 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1274 # are in /opt/subversion.)
1275 # are in /opt/subversion.)
1275 oldpypath = os.environ.get(IMPL_PATH)
1276 oldpypath = os.environ.get(IMPL_PATH)
1276 if oldpypath:
1277 if oldpypath:
1277 pypath.append(oldpypath)
1278 pypath.append(oldpypath)
1278 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1279 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1279
1280
1280 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1281 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1281
1282
1282 vlog("# Using TESTDIR", TESTDIR)
1283 vlog("# Using TESTDIR", TESTDIR)
1283 vlog("# Using HGTMP", HGTMP)
1284 vlog("# Using HGTMP", HGTMP)
1284 vlog("# Using PATH", os.environ["PATH"])
1285 vlog("# Using PATH", os.environ["PATH"])
1285 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1286 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1286
1287
1287 try:
1288 try:
1288 if len(tests) > 1 and options.jobs > 1:
1289 if len(tests) > 1 and options.jobs > 1:
1289 runchildren(options, tests)
1290 runchildren(options, tests)
1290 else:
1291 else:
1291 runtests(options, tests)
1292 runtests(options, tests)
1292 finally:
1293 finally:
1293 time.sleep(.1)
1294 time.sleep(.1)
1294 cleanup(options)
1295 cleanup(options)
1295
1296
1296 if __name__ == '__main__':
1297 if __name__ == '__main__':
1297 main()
1298 main()
@@ -1,99 +1,109 b''
1 Simple commands:
1 Simple commands:
2
2
3 $ echo foo
3 $ echo foo
4 foo
4 foo
5 $ printf 'oh no'
5 $ printf 'oh no'
6 oh no (no-eol)
6 oh no (no-eol)
7 $ printf 'bar\nbaz\n' | cat
7 $ printf 'bar\nbaz\n' | cat
8 bar
8 bar
9 baz
9 baz
10
10
11 Multi-line command:
11 Multi-line command:
12
12
13 $ foo() {
13 $ foo() {
14 > echo bar
14 > echo bar
15 > }
15 > }
16 $ foo
16 $ foo
17 bar
17 bar
18
18
19 Return codes before inline python:
19 Return codes before inline python:
20
20
21 $ sh -c 'exit 1'
21 $ sh -c 'exit 1'
22 [1]
22 [1]
23
23
24 Doctest commands:
24 Doctest commands:
25
25
26 >>> print 'foo'
26 >>> print 'foo'
27 foo
27 foo
28 $ echo interleaved
28 $ echo interleaved
29 interleaved
29 interleaved
30 >>> for c in 'xyz':
30 >>> for c in 'xyz':
31 ... print c
31 ... print c
32 x
32 x
33 y
33 y
34 z
34 z
35 >>> print
35 >>> print
36
36
37
37
38 Regular expressions:
38 Regular expressions:
39
39
40 $ echo foobarbaz
40 $ echo foobarbaz
41 foobar.* (re)
41 foobar.* (re)
42 $ echo barbazquux
42 $ echo barbazquux
43 .*quux.* (re)
43 .*quux.* (re)
44
44
45 Globs:
45 Globs:
46
46
47 $ printf '* \\foobarbaz {10}\n'
47 $ printf '* \\foobarbaz {10}\n'
48 \* \\fo?bar* {10} (glob)
48 \* \\fo?bar* {10} (glob)
49
49
50 Literal match ending in " (re)":
50 Literal match ending in " (re)":
51
51
52 $ echo 'foo (re)'
52 $ echo 'foo (re)'
53 foo (re)
53 foo (re)
54
54
55 Windows: \r\n is handled like \n and can be escaped:
56
57 #if windows
58 $ printf 'crlf\r\ncr\r\tcrlf\r\ncrcrlf\r\r\n'
59 crlf
60 cr\r (no-eol) (esc)
61 \tcrlf (esc)
62 crcrlf\r (esc)
63 #endif
64
55 testing hghave
65 testing hghave
56
66
57 $ "$TESTDIR/hghave" true
67 $ "$TESTDIR/hghave" true
58 $ "$TESTDIR/hghave" false
68 $ "$TESTDIR/hghave" false
59 skipped: missing feature: nail clipper
69 skipped: missing feature: nail clipper
60 [1]
70 [1]
61 $ "$TESTDIR/hghave" no-true
71 $ "$TESTDIR/hghave" no-true
62 skipped: system supports yak shaving
72 skipped: system supports yak shaving
63 [1]
73 [1]
64 $ "$TESTDIR/hghave" no-false
74 $ "$TESTDIR/hghave" no-false
65
75
66 Conditional sections based on hghave:
76 Conditional sections based on hghave:
67
77
68 #if true
78 #if true
69 $ echo tested
79 $ echo tested
70 tested
80 tested
71 #else
81 #else
72 $ echo skipped
82 $ echo skipped
73 #endif
83 #endif
74
84
75 #if false
85 #if false
76 $ echo skipped
86 $ echo skipped
77 #else
87 #else
78 $ echo tested
88 $ echo tested
79 tested
89 tested
80 #endif
90 #endif
81
91
82 #if no-false
92 #if no-false
83 $ echo tested
93 $ echo tested
84 tested
94 tested
85 #else
95 #else
86 $ echo skipped
96 $ echo skipped
87 #endif
97 #endif
88
98
89 #if no-true
99 #if no-true
90 $ echo skipped
100 $ echo skipped
91 #else
101 #else
92 $ echo tested
102 $ echo tested
93 tested
103 tested
94 #endif
104 #endif
95
105
96 Exit code:
106 Exit code:
97
107
98 $ (exit 1)
108 $ (exit 1)
99 [1]
109 [1]
General Comments 0
You need to be logged in to leave comments. Login now