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