##// END OF EJS Templates
run-tests: fix error when timeout occurs
Simon Heimberg -
r8116:c11636f0 default
parent child Browse files
Show More
@@ -1,709 +1,709
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
7 # This software may be used and distributed according to the terms
8 # of the GNU General Public License, incorporated herein by reference.
8 # of the GNU General Public License, incorporated herein by reference.
9
9
10 import difflib
10 import difflib
11 import errno
11 import errno
12 import optparse
12 import optparse
13 import os
13 import os
14 try:
14 try:
15 import subprocess
15 import subprocess
16 subprocess.Popen # trigger ImportError early
16 subprocess.Popen # trigger ImportError early
17 closefds = os.name == 'posix'
17 closefds = os.name == 'posix'
18 def Popen4(cmd, bufsize=-1):
18 def Popen4(cmd, bufsize=-1):
19 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
19 p = subprocess.Popen(cmd, shell=True, bufsize=bufsize,
20 close_fds=closefds,
20 close_fds=closefds,
21 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
21 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
22 stderr=subprocess.STDOUT)
22 stderr=subprocess.STDOUT)
23 p.fromchild = p.stdout
23 p.fromchild = p.stdout
24 p.tochild = p.stdin
24 p.tochild = p.stdin
25 p.childerr = p.stderr
25 p.childerr = p.stderr
26 return p
26 return p
27 except ImportError:
27 except ImportError:
28 subprocess = None
28 subprocess = None
29 from popen2 import Popen4
29 from popen2 import Popen4
30 import shutil
30 import shutil
31 import signal
31 import signal
32 import sys
32 import sys
33 import tempfile
33 import tempfile
34 import time
34 import time
35
35
36 # reserved exit code to skip test (used by hghave)
36 # reserved exit code to skip test (used by hghave)
37 SKIPPED_STATUS = 80
37 SKIPPED_STATUS = 80
38 SKIPPED_PREFIX = 'skipped: '
38 SKIPPED_PREFIX = 'skipped: '
39 FAILED_PREFIX = 'hghave check failed: '
39 FAILED_PREFIX = 'hghave check failed: '
40 PYTHON = sys.executable
40 PYTHON = sys.executable
41
41
42 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
42 requiredtools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed"]
43
43
44 defaults = {
44 defaults = {
45 'jobs': ('HGTEST_JOBS', 1),
45 'jobs': ('HGTEST_JOBS', 1),
46 'timeout': ('HGTEST_TIMEOUT', 180),
46 'timeout': ('HGTEST_TIMEOUT', 180),
47 'port': ('HGTEST_PORT', 20059),
47 'port': ('HGTEST_PORT', 20059),
48 }
48 }
49
49
50 def parseargs():
50 def parseargs():
51 parser = optparse.OptionParser("%prog [options] [tests]")
51 parser = optparse.OptionParser("%prog [options] [tests]")
52 parser.add_option("-C", "--annotate", action="store_true",
52 parser.add_option("-C", "--annotate", action="store_true",
53 help="output files annotated with coverage")
53 help="output files annotated with coverage")
54 parser.add_option("--child", type="int",
54 parser.add_option("--child", type="int",
55 help="run as child process, summary to given fd")
55 help="run as child process, summary to given fd")
56 parser.add_option("-c", "--cover", action="store_true",
56 parser.add_option("-c", "--cover", action="store_true",
57 help="print a test coverage report")
57 help="print a test coverage report")
58 parser.add_option("-f", "--first", action="store_true",
58 parser.add_option("-f", "--first", action="store_true",
59 help="exit on the first test failure")
59 help="exit on the first test failure")
60 parser.add_option("-i", "--interactive", action="store_true",
60 parser.add_option("-i", "--interactive", action="store_true",
61 help="prompt to accept changed output")
61 help="prompt to accept changed output")
62 parser.add_option("-j", "--jobs", type="int",
62 parser.add_option("-j", "--jobs", type="int",
63 help="number of jobs to run in parallel"
63 help="number of jobs to run in parallel"
64 " (default: $%s or %d)" % defaults['jobs'])
64 " (default: $%s or %d)" % defaults['jobs'])
65 parser.add_option("--keep-tmpdir", action="store_true",
65 parser.add_option("--keep-tmpdir", action="store_true",
66 help="keep temporary directory after running tests"
66 help="keep temporary directory after running tests"
67 " (best used with --tmpdir)")
67 " (best used with --tmpdir)")
68 parser.add_option("-R", "--restart", action="store_true",
68 parser.add_option("-R", "--restart", action="store_true",
69 help="restart at last error")
69 help="restart at last error")
70 parser.add_option("-p", "--port", type="int",
70 parser.add_option("-p", "--port", type="int",
71 help="port on which servers should listen"
71 help="port on which servers should listen"
72 " (default: $%s or %d)" % defaults['port'])
72 " (default: $%s or %d)" % defaults['port'])
73 parser.add_option("-r", "--retest", action="store_true",
73 parser.add_option("-r", "--retest", action="store_true",
74 help="retest failed tests")
74 help="retest failed tests")
75 parser.add_option("-s", "--cover_stdlib", action="store_true",
75 parser.add_option("-s", "--cover_stdlib", action="store_true",
76 help="print a test coverage report inc. standard libraries")
76 help="print a test coverage report inc. standard libraries")
77 parser.add_option("-t", "--timeout", type="int",
77 parser.add_option("-t", "--timeout", type="int",
78 help="kill errant tests after TIMEOUT seconds"
78 help="kill errant tests after TIMEOUT seconds"
79 " (default: $%s or %d)" % defaults['timeout'])
79 " (default: $%s or %d)" % defaults['timeout'])
80 parser.add_option("--tmpdir", type="string",
80 parser.add_option("--tmpdir", type="string",
81 help="run tests in the given temporary directory")
81 help="run tests in the given temporary directory")
82 parser.add_option("-v", "--verbose", action="store_true",
82 parser.add_option("-v", "--verbose", action="store_true",
83 help="output verbose messages")
83 help="output verbose messages")
84 parser.add_option("-n", "--nodiff", action="store_true",
84 parser.add_option("-n", "--nodiff", action="store_true",
85 help="skip showing test changes")
85 help="skip showing test changes")
86 parser.add_option("--with-hg", type="string",
86 parser.add_option("--with-hg", type="string",
87 help="test existing install at given location")
87 help="test existing install at given location")
88 parser.add_option("--pure", action="store_true",
88 parser.add_option("--pure", action="store_true",
89 help="use pure Python code instead of C extensions")
89 help="use pure Python code instead of C extensions")
90
90
91 for option, default in defaults.items():
91 for option, default in defaults.items():
92 defaults[option] = int(os.environ.get(*default))
92 defaults[option] = int(os.environ.get(*default))
93 parser.set_defaults(**defaults)
93 parser.set_defaults(**defaults)
94 (options, args) = parser.parse_args()
94 (options, args) = parser.parse_args()
95
95
96 global vlog
96 global vlog
97 options.anycoverage = (options.cover or
97 options.anycoverage = (options.cover or
98 options.cover_stdlib or
98 options.cover_stdlib or
99 options.annotate)
99 options.annotate)
100
100
101 if options.verbose:
101 if options.verbose:
102 def vlog(*msg):
102 def vlog(*msg):
103 for m in msg:
103 for m in msg:
104 print m,
104 print m,
105 print
105 print
106 else:
106 else:
107 vlog = lambda *msg: None
107 vlog = lambda *msg: None
108
108
109 if options.jobs < 1:
109 if options.jobs < 1:
110 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
110 print >> sys.stderr, 'ERROR: -j/--jobs must be positive'
111 sys.exit(1)
111 sys.exit(1)
112 if options.interactive and options.jobs > 1:
112 if options.interactive and options.jobs > 1:
113 print '(--interactive overrides --jobs)'
113 print '(--interactive overrides --jobs)'
114 options.jobs = 1
114 options.jobs = 1
115
115
116 return (options, args)
116 return (options, args)
117
117
118 def rename(src, dst):
118 def rename(src, dst):
119 """Like os.rename(), trade atomicity and opened files friendliness
119 """Like os.rename(), trade atomicity and opened files friendliness
120 for existing destination support.
120 for existing destination support.
121 """
121 """
122 shutil.copy(src, dst)
122 shutil.copy(src, dst)
123 os.remove(src)
123 os.remove(src)
124
124
125 def splitnewlines(text):
125 def splitnewlines(text):
126 '''like str.splitlines, but only split on newlines.
126 '''like str.splitlines, but only split on newlines.
127 keep line endings.'''
127 keep line endings.'''
128 i = 0
128 i = 0
129 lines = []
129 lines = []
130 while True:
130 while True:
131 n = text.find('\n', i)
131 n = text.find('\n', i)
132 if n == -1:
132 if n == -1:
133 last = text[i:]
133 last = text[i:]
134 if last:
134 if last:
135 lines.append(last)
135 lines.append(last)
136 return lines
136 return lines
137 lines.append(text[i:n+1])
137 lines.append(text[i:n+1])
138 i = n + 1
138 i = n + 1
139
139
140 def parsehghaveoutput(lines):
140 def parsehghaveoutput(lines):
141 '''Parse hghave log lines.
141 '''Parse hghave log lines.
142 Return tuple of lists (missing, failed):
142 Return tuple of lists (missing, failed):
143 * the missing/unknown features
143 * the missing/unknown features
144 * the features for which existence check failed'''
144 * the features for which existence check failed'''
145 missing = []
145 missing = []
146 failed = []
146 failed = []
147 for line in lines:
147 for line in lines:
148 if line.startswith(SKIPPED_PREFIX):
148 if line.startswith(SKIPPED_PREFIX):
149 line = line.splitlines()[0]
149 line = line.splitlines()[0]
150 missing.append(line[len(SKIPPED_PREFIX):])
150 missing.append(line[len(SKIPPED_PREFIX):])
151 elif line.startswith(FAILED_PREFIX):
151 elif line.startswith(FAILED_PREFIX):
152 line = line.splitlines()[0]
152 line = line.splitlines()[0]
153 failed.append(line[len(FAILED_PREFIX):])
153 failed.append(line[len(FAILED_PREFIX):])
154
154
155 return missing, failed
155 return missing, failed
156
156
157 def showdiff(expected, output):
157 def showdiff(expected, output):
158 for line in difflib.unified_diff(expected, output,
158 for line in difflib.unified_diff(expected, output,
159 "Expected output", "Test output"):
159 "Expected output", "Test output"):
160 sys.stdout.write(line)
160 sys.stdout.write(line)
161
161
162 def findprogram(program):
162 def findprogram(program):
163 """Search PATH for a executable program"""
163 """Search PATH for a executable program"""
164 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
164 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
165 name = os.path.join(p, program)
165 name = os.path.join(p, program)
166 if os.access(name, os.X_OK):
166 if os.access(name, os.X_OK):
167 return name
167 return name
168 return None
168 return None
169
169
170 def checktools():
170 def checktools():
171 # Before we go any further, check for pre-requisite tools
171 # Before we go any further, check for pre-requisite tools
172 # stuff from coreutils (cat, rm, etc) are not tested
172 # stuff from coreutils (cat, rm, etc) are not tested
173 for p in requiredtools:
173 for p in requiredtools:
174 if os.name == 'nt':
174 if os.name == 'nt':
175 p += '.exe'
175 p += '.exe'
176 found = findprogram(p)
176 found = findprogram(p)
177 if found:
177 if found:
178 vlog("# Found prerequisite", p, "at", found)
178 vlog("# Found prerequisite", p, "at", found)
179 else:
179 else:
180 print "WARNING: Did not find prerequisite tool: "+p
180 print "WARNING: Did not find prerequisite tool: "+p
181
181
182 def cleanup(options):
182 def cleanup(options):
183 if not options.keep_tmpdir:
183 if not options.keep_tmpdir:
184 if options.verbose:
184 if options.verbose:
185 print "# Cleaning up HGTMP", HGTMP
185 print "# Cleaning up HGTMP", HGTMP
186 shutil.rmtree(HGTMP, True)
186 shutil.rmtree(HGTMP, True)
187
187
188 def usecorrectpython():
188 def usecorrectpython():
189 # some tests run python interpreter. they must use same
189 # some tests run python interpreter. they must use same
190 # interpreter we use or bad things will happen.
190 # interpreter we use or bad things will happen.
191 exedir, exename = os.path.split(sys.executable)
191 exedir, exename = os.path.split(sys.executable)
192 if exename == 'python':
192 if exename == 'python':
193 path = findprogram('python')
193 path = findprogram('python')
194 if os.path.dirname(path) == exedir:
194 if os.path.dirname(path) == exedir:
195 return
195 return
196 vlog('# Making python executable in test path use correct Python')
196 vlog('# Making python executable in test path use correct Python')
197 mypython = os.path.join(BINDIR, 'python')
197 mypython = os.path.join(BINDIR, 'python')
198 try:
198 try:
199 os.symlink(sys.executable, mypython)
199 os.symlink(sys.executable, mypython)
200 except AttributeError:
200 except AttributeError:
201 # windows fallback
201 # windows fallback
202 shutil.copyfile(sys.executable, mypython)
202 shutil.copyfile(sys.executable, mypython)
203 shutil.copymode(sys.executable, mypython)
203 shutil.copymode(sys.executable, mypython)
204
204
205 def installhg(options):
205 def installhg(options):
206 global PYTHON
206 global PYTHON
207 vlog("# Performing temporary installation of HG")
207 vlog("# Performing temporary installation of HG")
208 installerrs = os.path.join("tests", "install.err")
208 installerrs = os.path.join("tests", "install.err")
209 pure = options.pure and "--pure" or ""
209 pure = options.pure and "--pure" or ""
210
210
211 # Run installer in hg root
211 # Run installer in hg root
212 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
212 os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..'))
213 cmd = ('%s setup.py %s clean --all'
213 cmd = ('%s setup.py %s clean --all'
214 ' install --force --prefix="%s" --install-lib="%s"'
214 ' install --force --prefix="%s" --install-lib="%s"'
215 ' --install-scripts="%s" >%s 2>&1'
215 ' --install-scripts="%s" >%s 2>&1'
216 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
216 % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs))
217 vlog("# Running", cmd)
217 vlog("# Running", cmd)
218 if os.system(cmd) == 0:
218 if os.system(cmd) == 0:
219 if not options.verbose:
219 if not options.verbose:
220 os.remove(installerrs)
220 os.remove(installerrs)
221 else:
221 else:
222 f = open(installerrs)
222 f = open(installerrs)
223 for line in f:
223 for line in f:
224 print line,
224 print line,
225 f.close()
225 f.close()
226 sys.exit(1)
226 sys.exit(1)
227 os.chdir(TESTDIR)
227 os.chdir(TESTDIR)
228
228
229 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
229 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
230
230
231 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
231 pydir = os.pathsep.join([PYTHONDIR, TESTDIR])
232 pythonpath = os.environ.get("PYTHONPATH")
232 pythonpath = os.environ.get("PYTHONPATH")
233 if pythonpath:
233 if pythonpath:
234 pythonpath = pydir + os.pathsep + pythonpath
234 pythonpath = pydir + os.pathsep + pythonpath
235 else:
235 else:
236 pythonpath = pydir
236 pythonpath = pydir
237 os.environ["PYTHONPATH"] = pythonpath
237 os.environ["PYTHONPATH"] = pythonpath
238
238
239 usecorrectpython()
239 usecorrectpython()
240 global hgpkg
240 global hgpkg
241 hgpkg = _hgpath()
241 hgpkg = _hgpath()
242
242
243 vlog("# Installing dummy diffstat")
243 vlog("# Installing dummy diffstat")
244 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
244 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
245 f.write('#!' + sys.executable + '\n'
245 f.write('#!' + sys.executable + '\n'
246 'import sys\n'
246 'import sys\n'
247 'files = 0\n'
247 'files = 0\n'
248 'for line in sys.stdin:\n'
248 'for line in sys.stdin:\n'
249 ' if line.startswith("diff "):\n'
249 ' if line.startswith("diff "):\n'
250 ' files += 1\n'
250 ' files += 1\n'
251 'sys.stdout.write("files patched: %d\\n" % files)\n')
251 'sys.stdout.write("files patched: %d\\n" % files)\n')
252 f.close()
252 f.close()
253 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
253 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
254
254
255 if options.anycoverage:
255 if options.anycoverage:
256 vlog("# Installing coverage wrapper")
256 vlog("# Installing coverage wrapper")
257 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
257 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
258 if os.path.exists(COVERAGE_FILE):
258 if os.path.exists(COVERAGE_FILE):
259 os.unlink(COVERAGE_FILE)
259 os.unlink(COVERAGE_FILE)
260 # Create a wrapper script to invoke hg via coverage.py
260 # Create a wrapper script to invoke hg via coverage.py
261 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
261 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
262 f = open(os.path.join(BINDIR, 'hg'), 'w')
262 f = open(os.path.join(BINDIR, 'hg'), 'w')
263 f.write('#!' + sys.executable + '\n')
263 f.write('#!' + sys.executable + '\n')
264 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
264 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '
265 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
265 '"%s", "-x", "%s"] + sys.argv[1:])\n' %
266 (os.path.join(TESTDIR, 'coverage.py'),
266 (os.path.join(TESTDIR, 'coverage.py'),
267 os.path.join(BINDIR, '_hg.py')))
267 os.path.join(BINDIR, '_hg.py')))
268 f.close()
268 f.close()
269 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
269 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
270 PYTHON = '"%s" "%s" -x' % (sys.executable,
270 PYTHON = '"%s" "%s" -x' % (sys.executable,
271 os.path.join(TESTDIR,'coverage.py'))
271 os.path.join(TESTDIR,'coverage.py'))
272
272
273 def _hgpath():
273 def _hgpath():
274 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
274 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
275 hgpath = os.popen(cmd % PYTHON)
275 hgpath = os.popen(cmd % PYTHON)
276 path = hgpath.read().strip()
276 path = hgpath.read().strip()
277 hgpath.close()
277 hgpath.close()
278 return path
278 return path
279
279
280 def outputcoverage(options):
280 def outputcoverage(options):
281 vlog("# Producing coverage report")
281 vlog("# Producing coverage report")
282 omit = [BINDIR, TESTDIR, PYTHONDIR]
282 omit = [BINDIR, TESTDIR, PYTHONDIR]
283 if not options.cover_stdlib:
283 if not options.cover_stdlib:
284 # Exclude as system paths (ignoring empty strings seen on win)
284 # Exclude as system paths (ignoring empty strings seen on win)
285 omit += [x for x in sys.path if x != '']
285 omit += [x for x in sys.path if x != '']
286 omit = ','.join(omit)
286 omit = ','.join(omit)
287 os.chdir(PYTHONDIR)
287 os.chdir(PYTHONDIR)
288 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
288 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
289 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
289 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
290 vlog("# Running: "+cmd)
290 vlog("# Running: "+cmd)
291 os.system(cmd)
291 os.system(cmd)
292 if options.annotate:
292 if options.annotate:
293 adir = os.path.join(TESTDIR, 'annotated')
293 adir = os.path.join(TESTDIR, 'annotated')
294 if not os.path.isdir(adir):
294 if not os.path.isdir(adir):
295 os.mkdir(adir)
295 os.mkdir(adir)
296 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
296 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
297 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
297 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
298 adir, omit)
298 adir, omit)
299 vlog("# Running: "+cmd)
299 vlog("# Running: "+cmd)
300 os.system(cmd)
300 os.system(cmd)
301
301
302 class Timeout(Exception):
302 class Timeout(Exception):
303 pass
303 pass
304
304
305 def alarmed(signum, frame):
305 def alarmed(signum, frame):
306 raise Timeout
306 raise Timeout
307
307
308 def run(cmd):
308 def run(cmd, options):
309 """Run command in a sub-process, capturing the output (stdout and stderr).
309 """Run command in a sub-process, capturing the output (stdout and stderr).
310 Return the exist code, and output."""
310 Return the exist code, and output."""
311 # TODO: Use subprocess.Popen if we're running on Python 2.4
311 # TODO: Use subprocess.Popen if we're running on Python 2.4
312 if os.name == 'nt' or sys.platform.startswith('java'):
312 if os.name == 'nt' or sys.platform.startswith('java'):
313 tochild, fromchild = os.popen4(cmd)
313 tochild, fromchild = os.popen4(cmd)
314 tochild.close()
314 tochild.close()
315 output = fromchild.read()
315 output = fromchild.read()
316 ret = fromchild.close()
316 ret = fromchild.close()
317 if ret == None:
317 if ret == None:
318 ret = 0
318 ret = 0
319 else:
319 else:
320 proc = Popen4(cmd)
320 proc = Popen4(cmd)
321 try:
321 try:
322 output = ''
322 output = ''
323 proc.tochild.close()
323 proc.tochild.close()
324 output = proc.fromchild.read()
324 output = proc.fromchild.read()
325 ret = proc.wait()
325 ret = proc.wait()
326 if os.WIFEXITED(ret):
326 if os.WIFEXITED(ret):
327 ret = os.WEXITSTATUS(ret)
327 ret = os.WEXITSTATUS(ret)
328 except Timeout:
328 except Timeout:
329 vlog('# Process %d timed out - killing it' % proc.pid)
329 vlog('# Process %d timed out - killing it' % proc.pid)
330 os.kill(proc.pid, signal.SIGTERM)
330 os.kill(proc.pid, signal.SIGTERM)
331 ret = proc.wait()
331 ret = proc.wait()
332 if ret == 0:
332 if ret == 0:
333 ret = signal.SIGTERM << 8
333 ret = signal.SIGTERM << 8
334 output += ("\n### Abort: timeout after %d seconds.\n"
334 output += ("\n### Abort: timeout after %d seconds.\n"
335 % options.timeout)
335 % options.timeout)
336 return ret, splitnewlines(output)
336 return ret, splitnewlines(output)
337
337
338 def runone(options, test, skips, fails):
338 def runone(options, test, skips, fails):
339 '''tristate output:
339 '''tristate output:
340 None -> skipped
340 None -> skipped
341 True -> passed
341 True -> passed
342 False -> failed'''
342 False -> failed'''
343
343
344 def skip(msg):
344 def skip(msg):
345 if not options.verbose:
345 if not options.verbose:
346 skips.append((test, msg))
346 skips.append((test, msg))
347 else:
347 else:
348 print "\nSkipping %s: %s" % (test, msg)
348 print "\nSkipping %s: %s" % (test, msg)
349 return None
349 return None
350
350
351 def fail(msg):
351 def fail(msg):
352 fails.append((test, msg))
352 fails.append((test, msg))
353 if not options.nodiff:
353 if not options.nodiff:
354 print "\nERROR: %s %s" % (test, msg)
354 print "\nERROR: %s %s" % (test, msg)
355 return None
355 return None
356
356
357 vlog("# Test", test)
357 vlog("# Test", test)
358
358
359 # create a fresh hgrc
359 # create a fresh hgrc
360 hgrc = file(HGRCPATH, 'w+')
360 hgrc = file(HGRCPATH, 'w+')
361 hgrc.write('[ui]\n')
361 hgrc.write('[ui]\n')
362 hgrc.write('slash = True\n')
362 hgrc.write('slash = True\n')
363 hgrc.write('[defaults]\n')
363 hgrc.write('[defaults]\n')
364 hgrc.write('backout = -d "0 0"\n')
364 hgrc.write('backout = -d "0 0"\n')
365 hgrc.write('commit = -d "0 0"\n')
365 hgrc.write('commit = -d "0 0"\n')
366 hgrc.write('debugrawcommit = -d "0 0"\n')
366 hgrc.write('debugrawcommit = -d "0 0"\n')
367 hgrc.write('tag = -d "0 0"\n')
367 hgrc.write('tag = -d "0 0"\n')
368 hgrc.close()
368 hgrc.close()
369
369
370 err = os.path.join(TESTDIR, test+".err")
370 err = os.path.join(TESTDIR, test+".err")
371 ref = os.path.join(TESTDIR, test+".out")
371 ref = os.path.join(TESTDIR, test+".out")
372 testpath = os.path.join(TESTDIR, test)
372 testpath = os.path.join(TESTDIR, test)
373
373
374 if os.path.exists(err):
374 if os.path.exists(err):
375 os.remove(err) # Remove any previous output files
375 os.remove(err) # Remove any previous output files
376
376
377 # Make a tmp subdirectory to work in
377 # Make a tmp subdirectory to work in
378 tmpd = os.path.join(HGTMP, test)
378 tmpd = os.path.join(HGTMP, test)
379 os.mkdir(tmpd)
379 os.mkdir(tmpd)
380 os.chdir(tmpd)
380 os.chdir(tmpd)
381
381
382 try:
382 try:
383 tf = open(testpath)
383 tf = open(testpath)
384 firstline = tf.readline().rstrip()
384 firstline = tf.readline().rstrip()
385 tf.close()
385 tf.close()
386 except:
386 except:
387 firstline = ''
387 firstline = ''
388 lctest = test.lower()
388 lctest = test.lower()
389
389
390 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
390 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
391 cmd = '%s "%s"' % (PYTHON, testpath)
391 cmd = '%s "%s"' % (PYTHON, testpath)
392 elif lctest.endswith('.bat'):
392 elif lctest.endswith('.bat'):
393 # do not run batch scripts on non-windows
393 # do not run batch scripts on non-windows
394 if os.name != 'nt':
394 if os.name != 'nt':
395 return skip("batch script")
395 return skip("batch script")
396 # To reliably get the error code from batch files on WinXP,
396 # To reliably get the error code from batch files on WinXP,
397 # the "cmd /c call" prefix is needed. Grrr
397 # the "cmd /c call" prefix is needed. Grrr
398 cmd = 'cmd /c call "%s"' % testpath
398 cmd = 'cmd /c call "%s"' % testpath
399 else:
399 else:
400 # do not run shell scripts on windows
400 # do not run shell scripts on windows
401 if os.name == 'nt':
401 if os.name == 'nt':
402 return skip("shell script")
402 return skip("shell script")
403 # do not try to run non-executable programs
403 # do not try to run non-executable programs
404 if not os.path.exists(testpath):
404 if not os.path.exists(testpath):
405 return fail("does not exist")
405 return fail("does not exist")
406 elif not os.access(testpath, os.X_OK):
406 elif not os.access(testpath, os.X_OK):
407 return skip("not executable")
407 return skip("not executable")
408 cmd = '"%s"' % testpath
408 cmd = '"%s"' % testpath
409
409
410 if options.timeout > 0:
410 if options.timeout > 0:
411 signal.alarm(options.timeout)
411 signal.alarm(options.timeout)
412
412
413 vlog("# Running", cmd)
413 vlog("# Running", cmd)
414 ret, out = run(cmd)
414 ret, out = run(cmd, options)
415 vlog("# Ret was:", ret)
415 vlog("# Ret was:", ret)
416
416
417 if options.timeout > 0:
417 if options.timeout > 0:
418 signal.alarm(0)
418 signal.alarm(0)
419
419
420 mark = '.'
420 mark = '.'
421
421
422 skipped = (ret == SKIPPED_STATUS)
422 skipped = (ret == SKIPPED_STATUS)
423 # If reference output file exists, check test output against it
423 # If reference output file exists, check test output against it
424 if os.path.exists(ref):
424 if os.path.exists(ref):
425 f = open(ref, "r")
425 f = open(ref, "r")
426 refout = splitnewlines(f.read())
426 refout = splitnewlines(f.read())
427 f.close()
427 f.close()
428 else:
428 else:
429 refout = []
429 refout = []
430 if skipped:
430 if skipped:
431 mark = 's'
431 mark = 's'
432 missing, failed = parsehghaveoutput(out)
432 missing, failed = parsehghaveoutput(out)
433 if not missing:
433 if not missing:
434 missing = ['irrelevant']
434 missing = ['irrelevant']
435 if failed:
435 if failed:
436 fail("hghave failed checking for %s" % failed[-1])
436 fail("hghave failed checking for %s" % failed[-1])
437 skipped = False
437 skipped = False
438 else:
438 else:
439 skip(missing[-1])
439 skip(missing[-1])
440 elif out != refout:
440 elif out != refout:
441 mark = '!'
441 mark = '!'
442 if ret:
442 if ret:
443 fail("output changed and returned error code %d" % ret)
443 fail("output changed and returned error code %d" % ret)
444 else:
444 else:
445 fail("output changed")
445 fail("output changed")
446 if not options.nodiff:
446 if not options.nodiff:
447 showdiff(refout, out)
447 showdiff(refout, out)
448 ret = 1
448 ret = 1
449 elif ret:
449 elif ret:
450 mark = '!'
450 mark = '!'
451 fail("returned error code %d" % ret)
451 fail("returned error code %d" % ret)
452
452
453 if not options.verbose:
453 if not options.verbose:
454 sys.stdout.write(mark)
454 sys.stdout.write(mark)
455 sys.stdout.flush()
455 sys.stdout.flush()
456
456
457 if ret != 0 and not skipped:
457 if ret != 0 and not skipped:
458 # Save errors to a file for diagnosis
458 # Save errors to a file for diagnosis
459 f = open(err, "wb")
459 f = open(err, "wb")
460 for line in out:
460 for line in out:
461 f.write(line)
461 f.write(line)
462 f.close()
462 f.close()
463
463
464 # Kill off any leftover daemon processes
464 # Kill off any leftover daemon processes
465 try:
465 try:
466 fp = file(DAEMON_PIDS)
466 fp = file(DAEMON_PIDS)
467 for line in fp:
467 for line in fp:
468 try:
468 try:
469 pid = int(line)
469 pid = int(line)
470 except ValueError:
470 except ValueError:
471 continue
471 continue
472 try:
472 try:
473 os.kill(pid, 0)
473 os.kill(pid, 0)
474 vlog('# Killing daemon process %d' % pid)
474 vlog('# Killing daemon process %d' % pid)
475 os.kill(pid, signal.SIGTERM)
475 os.kill(pid, signal.SIGTERM)
476 time.sleep(0.25)
476 time.sleep(0.25)
477 os.kill(pid, 0)
477 os.kill(pid, 0)
478 vlog('# Daemon process %d is stuck - really killing it' % pid)
478 vlog('# Daemon process %d is stuck - really killing it' % pid)
479 os.kill(pid, signal.SIGKILL)
479 os.kill(pid, signal.SIGKILL)
480 except OSError, err:
480 except OSError, err:
481 if err.errno != errno.ESRCH:
481 if err.errno != errno.ESRCH:
482 raise
482 raise
483 fp.close()
483 fp.close()
484 os.unlink(DAEMON_PIDS)
484 os.unlink(DAEMON_PIDS)
485 except IOError:
485 except IOError:
486 pass
486 pass
487
487
488 os.chdir(TESTDIR)
488 os.chdir(TESTDIR)
489 if not options.keep_tmpdir:
489 if not options.keep_tmpdir:
490 shutil.rmtree(tmpd, True)
490 shutil.rmtree(tmpd, True)
491 if skipped:
491 if skipped:
492 return None
492 return None
493 return ret == 0
493 return ret == 0
494
494
495 def runchildren(options, expecthg, tests):
495 def runchildren(options, expecthg, tests):
496 if not options.with_hg:
496 if not options.with_hg:
497 installhg(options)
497 installhg(options)
498 if hgpkg != expecthg:
498 if hgpkg != expecthg:
499 print '# Testing unexpected mercurial: %s' % hgpkg
499 print '# Testing unexpected mercurial: %s' % hgpkg
500
500
501 optcopy = dict(options.__dict__)
501 optcopy = dict(options.__dict__)
502 optcopy['jobs'] = 1
502 optcopy['jobs'] = 1
503 optcopy['with_hg'] = INST
503 optcopy['with_hg'] = INST
504 opts = []
504 opts = []
505 for opt, value in optcopy.iteritems():
505 for opt, value in optcopy.iteritems():
506 name = '--' + opt.replace('_', '-')
506 name = '--' + opt.replace('_', '-')
507 if value is True:
507 if value is True:
508 opts.append(name)
508 opts.append(name)
509 elif value is not None:
509 elif value is not None:
510 opts.append(name + '=' + str(value))
510 opts.append(name + '=' + str(value))
511
511
512 tests.reverse()
512 tests.reverse()
513 jobs = [[] for j in xrange(options.jobs)]
513 jobs = [[] for j in xrange(options.jobs)]
514 while tests:
514 while tests:
515 for j in xrange(options.jobs):
515 for j in xrange(options.jobs):
516 if not tests: break
516 if not tests: break
517 jobs[j].append(tests.pop())
517 jobs[j].append(tests.pop())
518 fps = {}
518 fps = {}
519 for j in xrange(len(jobs)):
519 for j in xrange(len(jobs)):
520 job = jobs[j]
520 job = jobs[j]
521 if not job:
521 if not job:
522 continue
522 continue
523 rfd, wfd = os.pipe()
523 rfd, wfd = os.pipe()
524 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
524 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
525 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
525 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
526 vlog(' '.join(cmdline))
526 vlog(' '.join(cmdline))
527 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
527 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
528 os.close(wfd)
528 os.close(wfd)
529 failures = 0
529 failures = 0
530 tested, skipped, failed = 0, 0, 0
530 tested, skipped, failed = 0, 0, 0
531 skips = []
531 skips = []
532 fails = []
532 fails = []
533 while fps:
533 while fps:
534 pid, status = os.wait()
534 pid, status = os.wait()
535 fp = fps.pop(pid)
535 fp = fps.pop(pid)
536 l = fp.read().splitlines()
536 l = fp.read().splitlines()
537 test, skip, fail = map(int, l[:3])
537 test, skip, fail = map(int, l[:3])
538 split = -fail or len(l)
538 split = -fail or len(l)
539 for s in l[3:split]:
539 for s in l[3:split]:
540 skips.append(s.split(" ", 1))
540 skips.append(s.split(" ", 1))
541 for s in l[split:]:
541 for s in l[split:]:
542 fails.append(s.split(" ", 1))
542 fails.append(s.split(" ", 1))
543 tested += test
543 tested += test
544 skipped += skip
544 skipped += skip
545 failed += fail
545 failed += fail
546 vlog('pid %d exited, status %d' % (pid, status))
546 vlog('pid %d exited, status %d' % (pid, status))
547 failures |= status
547 failures |= status
548 print
548 print
549 for s in skips:
549 for s in skips:
550 print "Skipped %s: %s" % (s[0], s[1])
550 print "Skipped %s: %s" % (s[0], s[1])
551 for s in fails:
551 for s in fails:
552 print "Failed %s: %s" % (s[0], s[1])
552 print "Failed %s: %s" % (s[0], s[1])
553
553
554 if hgpkg != expecthg:
554 if hgpkg != expecthg:
555 print '# Tested unexpected mercurial: %s' % hgpkg
555 print '# Tested unexpected mercurial: %s' % hgpkg
556 print "# Ran %d tests, %d skipped, %d failed." % (
556 print "# Ran %d tests, %d skipped, %d failed." % (
557 tested, skipped, failed)
557 tested, skipped, failed)
558 sys.exit(failures != 0)
558 sys.exit(failures != 0)
559
559
560 def runtests(options, expecthg, tests):
560 def runtests(options, expecthg, tests):
561 global DAEMON_PIDS, HGRCPATH
561 global DAEMON_PIDS, HGRCPATH
562 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
562 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
563 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
563 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
564
564
565 try:
565 try:
566 if not options.with_hg:
566 if not options.with_hg:
567 installhg(options)
567 installhg(options)
568
568
569 if hgpkg != expecthg:
569 if hgpkg != expecthg:
570 print '# Testing unexpected mercurial: %s' % hgpkg
570 print '# Testing unexpected mercurial: %s' % hgpkg
571
571
572 if options.timeout > 0:
572 if options.timeout > 0:
573 try:
573 try:
574 signal.signal(signal.SIGALRM, alarmed)
574 signal.signal(signal.SIGALRM, alarmed)
575 vlog('# Running tests with %d-second timeout' %
575 vlog('# Running tests with %d-second timeout' %
576 options.timeout)
576 options.timeout)
577 except AttributeError:
577 except AttributeError:
578 print 'WARNING: cannot run tests with timeouts'
578 print 'WARNING: cannot run tests with timeouts'
579 options.timeout = 0
579 options.timeout = 0
580
580
581 tested = 0
581 tested = 0
582 failed = 0
582 failed = 0
583 skipped = 0
583 skipped = 0
584
584
585 if options.restart:
585 if options.restart:
586 orig = list(tests)
586 orig = list(tests)
587 while tests:
587 while tests:
588 if os.path.exists(tests[0] + ".err"):
588 if os.path.exists(tests[0] + ".err"):
589 break
589 break
590 tests.pop(0)
590 tests.pop(0)
591 if not tests:
591 if not tests:
592 print "running all tests"
592 print "running all tests"
593 tests = orig
593 tests = orig
594
594
595 skips = []
595 skips = []
596 fails = []
596 fails = []
597 for test in tests:
597 for test in tests:
598 if options.retest and not os.path.exists(test + ".err"):
598 if options.retest and not os.path.exists(test + ".err"):
599 skipped += 1
599 skipped += 1
600 continue
600 continue
601 ret = runone(options, test, skips, fails)
601 ret = runone(options, test, skips, fails)
602 if ret is None:
602 if ret is None:
603 skipped += 1
603 skipped += 1
604 elif not ret:
604 elif not ret:
605 if options.interactive:
605 if options.interactive:
606 print "Accept this change? [n] ",
606 print "Accept this change? [n] ",
607 answer = sys.stdin.readline().strip()
607 answer = sys.stdin.readline().strip()
608 if answer.lower() in "y yes".split():
608 if answer.lower() in "y yes".split():
609 rename(test + ".err", test + ".out")
609 rename(test + ".err", test + ".out")
610 tested += 1
610 tested += 1
611 fails.pop()
611 fails.pop()
612 continue
612 continue
613 failed += 1
613 failed += 1
614 if options.first:
614 if options.first:
615 break
615 break
616 tested += 1
616 tested += 1
617
617
618 if options.child:
618 if options.child:
619 fp = os.fdopen(options.child, 'w')
619 fp = os.fdopen(options.child, 'w')
620 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
620 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
621 for s in skips:
621 for s in skips:
622 fp.write("%s %s\n" % s)
622 fp.write("%s %s\n" % s)
623 for s in fails:
623 for s in fails:
624 fp.write("%s %s\n" % s)
624 fp.write("%s %s\n" % s)
625 fp.close()
625 fp.close()
626 else:
626 else:
627 print
627 print
628 for s in skips:
628 for s in skips:
629 print "Skipped %s: %s" % s
629 print "Skipped %s: %s" % s
630 for s in fails:
630 for s in fails:
631 print "Failed %s: %s" % s
631 print "Failed %s: %s" % s
632 if hgpkg != expecthg:
632 if hgpkg != expecthg:
633 print '# Tested unexpected mercurial: %s' % hgpkg
633 print '# Tested unexpected mercurial: %s' % hgpkg
634 print "# Ran %d tests, %d skipped, %d failed." % (
634 print "# Ran %d tests, %d skipped, %d failed." % (
635 tested, skipped, failed)
635 tested, skipped, failed)
636
636
637 if options.anycoverage:
637 if options.anycoverage:
638 outputcoverage(options)
638 outputcoverage(options)
639 except KeyboardInterrupt:
639 except KeyboardInterrupt:
640 failed = True
640 failed = True
641 print "\ninterrupted!"
641 print "\ninterrupted!"
642
642
643 if failed:
643 if failed:
644 sys.exit(1)
644 sys.exit(1)
645
645
646 def main():
646 def main():
647 (options, args) = parseargs()
647 (options, args) = parseargs()
648 if not options.child:
648 if not options.child:
649 os.umask(022)
649 os.umask(022)
650
650
651 checktools()
651 checktools()
652
652
653 # Reset some environment variables to well-known values so that
653 # Reset some environment variables to well-known values so that
654 # the tests produce repeatable output.
654 # the tests produce repeatable output.
655 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
655 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
656 os.environ['TZ'] = 'GMT'
656 os.environ['TZ'] = 'GMT'
657 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
657 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
658 os.environ['CDPATH'] = ''
658 os.environ['CDPATH'] = ''
659
659
660 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
660 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
661 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
661 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
662 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
662 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
663 options.tmpdir))
663 options.tmpdir))
664 DAEMON_PIDS = None
664 DAEMON_PIDS = None
665 HGRCPATH = None
665 HGRCPATH = None
666
666
667 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
667 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
668 os.environ["HGMERGE"] = "internal:merge"
668 os.environ["HGMERGE"] = "internal:merge"
669 os.environ["HGUSER"] = "test"
669 os.environ["HGUSER"] = "test"
670 os.environ["HGENCODING"] = "ascii"
670 os.environ["HGENCODING"] = "ascii"
671 os.environ["HGENCODINGMODE"] = "strict"
671 os.environ["HGENCODINGMODE"] = "strict"
672 os.environ["HGPORT"] = str(options.port)
672 os.environ["HGPORT"] = str(options.port)
673 os.environ["HGPORT1"] = str(options.port + 1)
673 os.environ["HGPORT1"] = str(options.port + 1)
674 os.environ["HGPORT2"] = str(options.port + 2)
674 os.environ["HGPORT2"] = str(options.port + 2)
675
675
676 if options.with_hg:
676 if options.with_hg:
677 INST = options.with_hg
677 INST = options.with_hg
678 else:
678 else:
679 INST = os.path.join(HGTMP, "install")
679 INST = os.path.join(HGTMP, "install")
680 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
680 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
681 PYTHONDIR = os.path.join(INST, "lib", "python")
681 PYTHONDIR = os.path.join(INST, "lib", "python")
682 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
682 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
683
683
684 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
684 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
685 hgpkg = None
685 hgpkg = None
686
686
687 if len(args) == 0:
687 if len(args) == 0:
688 args = os.listdir(".")
688 args = os.listdir(".")
689 args.sort()
689 args.sort()
690
690
691 tests = []
691 tests = []
692 for test in args:
692 for test in args:
693 if (test.startswith("test-") and '~' not in test and
693 if (test.startswith("test-") and '~' not in test and
694 ('.' not in test or test.endswith('.py') or
694 ('.' not in test or test.endswith('.py') or
695 test.endswith('.bat'))):
695 test.endswith('.bat'))):
696 tests.append(test)
696 tests.append(test)
697
697
698 vlog("# Using TESTDIR", TESTDIR)
698 vlog("# Using TESTDIR", TESTDIR)
699 vlog("# Using HGTMP", HGTMP)
699 vlog("# Using HGTMP", HGTMP)
700
700
701 try:
701 try:
702 if len(tests) > 1 and options.jobs > 1:
702 if len(tests) > 1 and options.jobs > 1:
703 runchildren(options, expecthg, tests)
703 runchildren(options, expecthg, tests)
704 else:
704 else:
705 runtests(options, expecthg, tests)
705 runtests(options, expecthg, tests)
706 finally:
706 finally:
707 cleanup(options)
707 cleanup(options)
708
708
709 main()
709 main()
General Comments 0
You need to be logged in to leave comments. Login now