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