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