##// END OF EJS Templates
runtest: do not start testing when there is no test
Simon Heimberg -
r8592:cc22b416 default
parent child Browse files
Show More
@@ -1,703 +1,706
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", "%s"] + sys.argv[1:])\n' %
262 '"%s", "-x", "%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' % (sys.executable,
267 PYTHON = '"%s" "%s" -x' % (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 vlog("# Producing coverage report")
278 vlog("# Producing coverage report")
279 omit = [BINDIR, TESTDIR, PYTHONDIR]
279 omit = [BINDIR, TESTDIR, PYTHONDIR]
280 if not options.cover_stdlib:
280 if not options.cover_stdlib:
281 # Exclude as system paths (ignoring empty strings seen on win)
281 # Exclude as system paths (ignoring empty strings seen on win)
282 omit += [x for x in sys.path if x != '']
282 omit += [x for x in sys.path if x != '']
283 omit = ','.join(omit)
283 omit = ','.join(omit)
284 os.chdir(PYTHONDIR)
284 os.chdir(PYTHONDIR)
285 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
285 cmd = '"%s" "%s" -i -r "--omit=%s"' % (
286 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
286 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
287 vlog("# Running: "+cmd)
287 vlog("# Running: "+cmd)
288 os.system(cmd)
288 os.system(cmd)
289 if options.annotate:
289 if options.annotate:
290 adir = os.path.join(TESTDIR, 'annotated')
290 adir = os.path.join(TESTDIR, 'annotated')
291 if not os.path.isdir(adir):
291 if not os.path.isdir(adir):
292 os.mkdir(adir)
292 os.mkdir(adir)
293 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
293 cmd = '"%s" "%s" -i -a "--directory=%s" "--omit=%s"' % (
294 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
294 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
295 adir, omit)
295 adir, omit)
296 vlog("# Running: "+cmd)
296 vlog("# Running: "+cmd)
297 os.system(cmd)
297 os.system(cmd)
298
298
299 class Timeout(Exception):
299 class Timeout(Exception):
300 pass
300 pass
301
301
302 def alarmed(signum, frame):
302 def alarmed(signum, frame):
303 raise Timeout
303 raise Timeout
304
304
305 def run(cmd, options):
305 def run(cmd, options):
306 """Run command in a sub-process, capturing the output (stdout and stderr).
306 """Run command in a sub-process, capturing the output (stdout and stderr).
307 Return the exist code, and output."""
307 Return the exist code, and output."""
308 # TODO: Use subprocess.Popen if we're running on Python 2.4
308 # TODO: Use subprocess.Popen if we're running on Python 2.4
309 if os.name == 'nt' or sys.platform.startswith('java'):
309 if os.name == 'nt' or sys.platform.startswith('java'):
310 tochild, fromchild = os.popen4(cmd)
310 tochild, fromchild = os.popen4(cmd)
311 tochild.close()
311 tochild.close()
312 output = fromchild.read()
312 output = fromchild.read()
313 ret = fromchild.close()
313 ret = fromchild.close()
314 if ret == None:
314 if ret == None:
315 ret = 0
315 ret = 0
316 else:
316 else:
317 proc = Popen4(cmd)
317 proc = Popen4(cmd)
318 try:
318 try:
319 output = ''
319 output = ''
320 proc.tochild.close()
320 proc.tochild.close()
321 output = proc.fromchild.read()
321 output = proc.fromchild.read()
322 ret = proc.wait()
322 ret = proc.wait()
323 if os.WIFEXITED(ret):
323 if os.WIFEXITED(ret):
324 ret = os.WEXITSTATUS(ret)
324 ret = os.WEXITSTATUS(ret)
325 except Timeout:
325 except Timeout:
326 vlog('# Process %d timed out - killing it' % proc.pid)
326 vlog('# Process %d timed out - killing it' % proc.pid)
327 os.kill(proc.pid, signal.SIGTERM)
327 os.kill(proc.pid, signal.SIGTERM)
328 ret = proc.wait()
328 ret = proc.wait()
329 if ret == 0:
329 if ret == 0:
330 ret = signal.SIGTERM << 8
330 ret = signal.SIGTERM << 8
331 output += ("\n### Abort: timeout after %d seconds.\n"
331 output += ("\n### Abort: timeout after %d seconds.\n"
332 % options.timeout)
332 % options.timeout)
333 return ret, splitnewlines(output)
333 return ret, splitnewlines(output)
334
334
335 def runone(options, test, skips, fails):
335 def runone(options, test, skips, fails):
336 '''tristate output:
336 '''tristate output:
337 None -> skipped
337 None -> skipped
338 True -> passed
338 True -> passed
339 False -> failed'''
339 False -> failed'''
340
340
341 def skip(msg):
341 def skip(msg):
342 if not options.verbose:
342 if not options.verbose:
343 skips.append((test, msg))
343 skips.append((test, msg))
344 else:
344 else:
345 print "\nSkipping %s: %s" % (test, msg)
345 print "\nSkipping %s: %s" % (test, msg)
346 return None
346 return None
347
347
348 def fail(msg):
348 def fail(msg):
349 fails.append((test, msg))
349 fails.append((test, msg))
350 if not options.nodiff:
350 if not options.nodiff:
351 print "\nERROR: %s %s" % (test, msg)
351 print "\nERROR: %s %s" % (test, msg)
352 return None
352 return None
353
353
354 vlog("# Test", test)
354 vlog("# Test", test)
355
355
356 # create a fresh hgrc
356 # create a fresh hgrc
357 hgrc = file(HGRCPATH, 'w+')
357 hgrc = file(HGRCPATH, 'w+')
358 hgrc.write('[ui]\n')
358 hgrc.write('[ui]\n')
359 hgrc.write('slash = True\n')
359 hgrc.write('slash = True\n')
360 hgrc.write('[defaults]\n')
360 hgrc.write('[defaults]\n')
361 hgrc.write('backout = -d "0 0"\n')
361 hgrc.write('backout = -d "0 0"\n')
362 hgrc.write('commit = -d "0 0"\n')
362 hgrc.write('commit = -d "0 0"\n')
363 hgrc.write('tag = -d "0 0"\n')
363 hgrc.write('tag = -d "0 0"\n')
364 hgrc.close()
364 hgrc.close()
365
365
366 err = os.path.join(TESTDIR, test+".err")
366 err = os.path.join(TESTDIR, test+".err")
367 ref = os.path.join(TESTDIR, test+".out")
367 ref = os.path.join(TESTDIR, test+".out")
368 testpath = os.path.join(TESTDIR, test)
368 testpath = os.path.join(TESTDIR, test)
369
369
370 if os.path.exists(err):
370 if os.path.exists(err):
371 os.remove(err) # Remove any previous output files
371 os.remove(err) # Remove any previous output files
372
372
373 # Make a tmp subdirectory to work in
373 # Make a tmp subdirectory to work in
374 tmpd = os.path.join(HGTMP, test)
374 tmpd = os.path.join(HGTMP, test)
375 os.mkdir(tmpd)
375 os.mkdir(tmpd)
376 os.chdir(tmpd)
376 os.chdir(tmpd)
377
377
378 try:
378 try:
379 tf = open(testpath)
379 tf = open(testpath)
380 firstline = tf.readline().rstrip()
380 firstline = tf.readline().rstrip()
381 tf.close()
381 tf.close()
382 except:
382 except:
383 firstline = ''
383 firstline = ''
384 lctest = test.lower()
384 lctest = test.lower()
385
385
386 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
386 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
387 cmd = '%s "%s"' % (PYTHON, testpath)
387 cmd = '%s "%s"' % (PYTHON, testpath)
388 elif lctest.endswith('.bat'):
388 elif lctest.endswith('.bat'):
389 # do not run batch scripts on non-windows
389 # do not run batch scripts on non-windows
390 if os.name != 'nt':
390 if os.name != 'nt':
391 return skip("batch script")
391 return skip("batch script")
392 # To reliably get the error code from batch files on WinXP,
392 # To reliably get the error code from batch files on WinXP,
393 # the "cmd /c call" prefix is needed. Grrr
393 # the "cmd /c call" prefix is needed. Grrr
394 cmd = 'cmd /c call "%s"' % testpath
394 cmd = 'cmd /c call "%s"' % testpath
395 else:
395 else:
396 # do not run shell scripts on windows
396 # do not run shell scripts on windows
397 if os.name == 'nt':
397 if os.name == 'nt':
398 return skip("shell script")
398 return skip("shell script")
399 # do not try to run non-executable programs
399 # do not try to run non-executable programs
400 if not os.path.exists(testpath):
400 if not os.path.exists(testpath):
401 return fail("does not exist")
401 return fail("does not exist")
402 elif not os.access(testpath, os.X_OK):
402 elif not os.access(testpath, os.X_OK):
403 return skip("not executable")
403 return skip("not executable")
404 cmd = '"%s"' % testpath
404 cmd = '"%s"' % testpath
405
405
406 if options.timeout > 0:
406 if options.timeout > 0:
407 signal.alarm(options.timeout)
407 signal.alarm(options.timeout)
408
408
409 vlog("# Running", cmd)
409 vlog("# Running", cmd)
410 ret, out = run(cmd, options)
410 ret, out = run(cmd, options)
411 vlog("# Ret was:", ret)
411 vlog("# Ret was:", ret)
412
412
413 if options.timeout > 0:
413 if options.timeout > 0:
414 signal.alarm(0)
414 signal.alarm(0)
415
415
416 mark = '.'
416 mark = '.'
417
417
418 skipped = (ret == SKIPPED_STATUS)
418 skipped = (ret == SKIPPED_STATUS)
419 # If reference output file exists, check test output against it
419 # If reference output file exists, check test output against it
420 if os.path.exists(ref):
420 if os.path.exists(ref):
421 f = open(ref, "r")
421 f = open(ref, "r")
422 refout = splitnewlines(f.read())
422 refout = splitnewlines(f.read())
423 f.close()
423 f.close()
424 else:
424 else:
425 refout = []
425 refout = []
426 if skipped:
426 if skipped:
427 mark = 's'
427 mark = 's'
428 missing, failed = parsehghaveoutput(out)
428 missing, failed = parsehghaveoutput(out)
429 if not missing:
429 if not missing:
430 missing = ['irrelevant']
430 missing = ['irrelevant']
431 if failed:
431 if failed:
432 fail("hghave failed checking for %s" % failed[-1])
432 fail("hghave failed checking for %s" % failed[-1])
433 skipped = False
433 skipped = False
434 else:
434 else:
435 skip(missing[-1])
435 skip(missing[-1])
436 elif out != refout:
436 elif out != refout:
437 mark = '!'
437 mark = '!'
438 if ret:
438 if ret:
439 fail("output changed and returned error code %d" % ret)
439 fail("output changed and returned error code %d" % ret)
440 else:
440 else:
441 fail("output changed")
441 fail("output changed")
442 if not options.nodiff:
442 if not options.nodiff:
443 showdiff(refout, out)
443 showdiff(refout, out)
444 ret = 1
444 ret = 1
445 elif ret:
445 elif ret:
446 mark = '!'
446 mark = '!'
447 fail("returned error code %d" % ret)
447 fail("returned error code %d" % ret)
448
448
449 if not options.verbose:
449 if not options.verbose:
450 sys.stdout.write(mark)
450 sys.stdout.write(mark)
451 sys.stdout.flush()
451 sys.stdout.flush()
452
452
453 if ret != 0 and not skipped:
453 if ret != 0 and not skipped:
454 # Save errors to a file for diagnosis
454 # Save errors to a file for diagnosis
455 f = open(err, "wb")
455 f = open(err, "wb")
456 for line in out:
456 for line in out:
457 f.write(line)
457 f.write(line)
458 f.close()
458 f.close()
459
459
460 # Kill off any leftover daemon processes
460 # Kill off any leftover daemon processes
461 try:
461 try:
462 fp = file(DAEMON_PIDS)
462 fp = file(DAEMON_PIDS)
463 for line in fp:
463 for line in fp:
464 try:
464 try:
465 pid = int(line)
465 pid = int(line)
466 except ValueError:
466 except ValueError:
467 continue
467 continue
468 try:
468 try:
469 os.kill(pid, 0)
469 os.kill(pid, 0)
470 vlog('# Killing daemon process %d' % pid)
470 vlog('# Killing daemon process %d' % pid)
471 os.kill(pid, signal.SIGTERM)
471 os.kill(pid, signal.SIGTERM)
472 time.sleep(0.25)
472 time.sleep(0.25)
473 os.kill(pid, 0)
473 os.kill(pid, 0)
474 vlog('# Daemon process %d is stuck - really killing it' % pid)
474 vlog('# Daemon process %d is stuck - really killing it' % pid)
475 os.kill(pid, signal.SIGKILL)
475 os.kill(pid, signal.SIGKILL)
476 except OSError, err:
476 except OSError, err:
477 if err.errno != errno.ESRCH:
477 if err.errno != errno.ESRCH:
478 raise
478 raise
479 fp.close()
479 fp.close()
480 os.unlink(DAEMON_PIDS)
480 os.unlink(DAEMON_PIDS)
481 except IOError:
481 except IOError:
482 pass
482 pass
483
483
484 os.chdir(TESTDIR)
484 os.chdir(TESTDIR)
485 if not options.keep_tmpdir:
485 if not options.keep_tmpdir:
486 shutil.rmtree(tmpd, True)
486 shutil.rmtree(tmpd, True)
487 if skipped:
487 if skipped:
488 return None
488 return None
489 return ret == 0
489 return ret == 0
490
490
491 def runchildren(options, expecthg, tests):
491 def runchildren(options, expecthg, tests):
492 if not options.with_hg:
492 if not options.with_hg:
493 installhg(options)
493 installhg(options)
494 if hgpkg != expecthg:
494 if hgpkg != expecthg:
495 print '# Testing unexpected mercurial: %s' % hgpkg
495 print '# Testing unexpected mercurial: %s' % hgpkg
496
496
497 optcopy = dict(options.__dict__)
497 optcopy = dict(options.__dict__)
498 optcopy['jobs'] = 1
498 optcopy['jobs'] = 1
499 optcopy['with_hg'] = INST
499 optcopy['with_hg'] = INST
500 opts = []
500 opts = []
501 for opt, value in optcopy.iteritems():
501 for opt, value in optcopy.iteritems():
502 name = '--' + opt.replace('_', '-')
502 name = '--' + opt.replace('_', '-')
503 if value is True:
503 if value is True:
504 opts.append(name)
504 opts.append(name)
505 elif value is not None:
505 elif value is not None:
506 opts.append(name + '=' + str(value))
506 opts.append(name + '=' + str(value))
507
507
508 tests.reverse()
508 tests.reverse()
509 jobs = [[] for j in xrange(options.jobs)]
509 jobs = [[] for j in xrange(options.jobs)]
510 while tests:
510 while tests:
511 for job in jobs:
511 for job in jobs:
512 if not tests: break
512 if not tests: break
513 job.append(tests.pop())
513 job.append(tests.pop())
514 fps = {}
514 fps = {}
515 for j, job in enumerate(jobs):
515 for j, job in enumerate(jobs):
516 if not job:
516 if not job:
517 continue
517 continue
518 rfd, wfd = os.pipe()
518 rfd, wfd = os.pipe()
519 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
519 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
520 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
520 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
521 vlog(' '.join(cmdline))
521 vlog(' '.join(cmdline))
522 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
522 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
523 os.close(wfd)
523 os.close(wfd)
524 failures = 0
524 failures = 0
525 tested, skipped, failed = 0, 0, 0
525 tested, skipped, failed = 0, 0, 0
526 skips = []
526 skips = []
527 fails = []
527 fails = []
528 while fps:
528 while fps:
529 pid, status = os.wait()
529 pid, status = os.wait()
530 fp = fps.pop(pid)
530 fp = fps.pop(pid)
531 l = fp.read().splitlines()
531 l = fp.read().splitlines()
532 test, skip, fail = map(int, l[:3])
532 test, skip, fail = map(int, l[:3])
533 split = -fail or len(l)
533 split = -fail or len(l)
534 for s in l[3:split]:
534 for s in l[3:split]:
535 skips.append(s.split(" ", 1))
535 skips.append(s.split(" ", 1))
536 for s in l[split:]:
536 for s in l[split:]:
537 fails.append(s.split(" ", 1))
537 fails.append(s.split(" ", 1))
538 tested += test
538 tested += test
539 skipped += skip
539 skipped += skip
540 failed += fail
540 failed += fail
541 vlog('pid %d exited, status %d' % (pid, status))
541 vlog('pid %d exited, status %d' % (pid, status))
542 failures |= status
542 failures |= status
543 print
543 print
544 for s in skips:
544 for s in skips:
545 print "Skipped %s: %s" % (s[0], s[1])
545 print "Skipped %s: %s" % (s[0], s[1])
546 for s in fails:
546 for s in fails:
547 print "Failed %s: %s" % (s[0], s[1])
547 print "Failed %s: %s" % (s[0], s[1])
548
548
549 if hgpkg != expecthg:
549 if hgpkg != expecthg:
550 print '# Tested unexpected mercurial: %s' % hgpkg
550 print '# Tested unexpected mercurial: %s' % hgpkg
551 print "# Ran %d tests, %d skipped, %d failed." % (
551 print "# Ran %d tests, %d skipped, %d failed." % (
552 tested, skipped, failed)
552 tested, skipped, failed)
553 sys.exit(failures != 0)
553 sys.exit(failures != 0)
554
554
555 def runtests(options, expecthg, tests):
555 def runtests(options, expecthg, tests):
556 global DAEMON_PIDS, HGRCPATH
556 global DAEMON_PIDS, HGRCPATH
557 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
557 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
558 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
558 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
559
559
560 try:
560 try:
561 if not options.with_hg:
561 if not options.with_hg:
562 installhg(options)
562 installhg(options)
563
563
564 if hgpkg != expecthg:
564 if hgpkg != expecthg:
565 print '# Testing unexpected mercurial: %s' % hgpkg
565 print '# Testing unexpected mercurial: %s' % hgpkg
566
566
567 if options.timeout > 0:
567 if options.timeout > 0:
568 try:
568 try:
569 signal.signal(signal.SIGALRM, alarmed)
569 signal.signal(signal.SIGALRM, alarmed)
570 vlog('# Running tests with %d-second timeout' %
570 vlog('# Running tests with %d-second timeout' %
571 options.timeout)
571 options.timeout)
572 except AttributeError:
572 except AttributeError:
573 print 'WARNING: cannot run tests with timeouts'
573 print 'WARNING: cannot run tests with timeouts'
574 options.timeout = 0
574 options.timeout = 0
575
575
576 tested = 0
576 tested = 0
577 failed = 0
577 failed = 0
578 skipped = 0
578 skipped = 0
579
579
580 if options.restart:
580 if options.restart:
581 orig = list(tests)
581 orig = list(tests)
582 while tests:
582 while tests:
583 if os.path.exists(tests[0] + ".err"):
583 if os.path.exists(tests[0] + ".err"):
584 break
584 break
585 tests.pop(0)
585 tests.pop(0)
586 if not tests:
586 if not tests:
587 print "running all tests"
587 print "running all tests"
588 tests = orig
588 tests = orig
589
589
590 skips = []
590 skips = []
591 fails = []
591 fails = []
592 for test in tests:
592 for test in tests:
593 if options.retest and not os.path.exists(test + ".err"):
593 if options.retest and not os.path.exists(test + ".err"):
594 skipped += 1
594 skipped += 1
595 continue
595 continue
596 ret = runone(options, test, skips, fails)
596 ret = runone(options, test, skips, fails)
597 if ret is None:
597 if ret is None:
598 skipped += 1
598 skipped += 1
599 elif not ret:
599 elif not ret:
600 if options.interactive:
600 if options.interactive:
601 print "Accept this change? [n] ",
601 print "Accept this change? [n] ",
602 answer = sys.stdin.readline().strip()
602 answer = sys.stdin.readline().strip()
603 if answer.lower() in "y yes".split():
603 if answer.lower() in "y yes".split():
604 rename(test + ".err", test + ".out")
604 rename(test + ".err", test + ".out")
605 tested += 1
605 tested += 1
606 fails.pop()
606 fails.pop()
607 continue
607 continue
608 failed += 1
608 failed += 1
609 if options.first:
609 if options.first:
610 break
610 break
611 tested += 1
611 tested += 1
612
612
613 if options.child:
613 if options.child:
614 fp = os.fdopen(options.child, 'w')
614 fp = os.fdopen(options.child, 'w')
615 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
615 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
616 for s in skips:
616 for s in skips:
617 fp.write("%s %s\n" % s)
617 fp.write("%s %s\n" % s)
618 for s in fails:
618 for s in fails:
619 fp.write("%s %s\n" % s)
619 fp.write("%s %s\n" % s)
620 fp.close()
620 fp.close()
621 else:
621 else:
622 print
622 print
623 for s in skips:
623 for s in skips:
624 print "Skipped %s: %s" % s
624 print "Skipped %s: %s" % s
625 for s in fails:
625 for s in fails:
626 print "Failed %s: %s" % s
626 print "Failed %s: %s" % s
627 if hgpkg != expecthg:
627 if hgpkg != expecthg:
628 print '# Tested unexpected mercurial: %s' % hgpkg
628 print '# Tested unexpected mercurial: %s' % hgpkg
629 print "# Ran %d tests, %d skipped, %d failed." % (
629 print "# Ran %d tests, %d skipped, %d failed." % (
630 tested, skipped, failed)
630 tested, skipped, failed)
631
631
632 if options.anycoverage:
632 if options.anycoverage:
633 outputcoverage(options)
633 outputcoverage(options)
634 except KeyboardInterrupt:
634 except KeyboardInterrupt:
635 failed = True
635 failed = True
636 print "\ninterrupted!"
636 print "\ninterrupted!"
637
637
638 if failed:
638 if failed:
639 sys.exit(1)
639 sys.exit(1)
640
640
641 def main():
641 def main():
642 (options, args) = parseargs()
642 (options, args) = parseargs()
643 if not options.child:
643 if not options.child:
644 os.umask(022)
644 os.umask(022)
645
645
646 checktools()
646 checktools()
647
647
648 # Reset some environment variables to well-known values so that
648 # Reset some environment variables to well-known values so that
649 # the tests produce repeatable output.
649 # the tests produce repeatable output.
650 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
650 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
651 os.environ['TZ'] = 'GMT'
651 os.environ['TZ'] = 'GMT'
652 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
652 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
653 os.environ['CDPATH'] = ''
653 os.environ['CDPATH'] = ''
654
654
655 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
655 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
656 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
656 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
657 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
657 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
658 options.tmpdir))
658 options.tmpdir))
659 DAEMON_PIDS = None
659 DAEMON_PIDS = None
660 HGRCPATH = None
660 HGRCPATH = None
661
661
662 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
662 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
663 os.environ["HGMERGE"] = "internal:merge"
663 os.environ["HGMERGE"] = "internal:merge"
664 os.environ["HGUSER"] = "test"
664 os.environ["HGUSER"] = "test"
665 os.environ["HGENCODING"] = "ascii"
665 os.environ["HGENCODING"] = "ascii"
666 os.environ["HGENCODINGMODE"] = "strict"
666 os.environ["HGENCODINGMODE"] = "strict"
667 os.environ["HGPORT"] = str(options.port)
667 os.environ["HGPORT"] = str(options.port)
668 os.environ["HGPORT1"] = str(options.port + 1)
668 os.environ["HGPORT1"] = str(options.port + 1)
669 os.environ["HGPORT2"] = str(options.port + 2)
669 os.environ["HGPORT2"] = str(options.port + 2)
670
670
671 if options.with_hg:
671 if options.with_hg:
672 INST = options.with_hg
672 INST = options.with_hg
673 else:
673 else:
674 INST = os.path.join(HGTMP, "install")
674 INST = os.path.join(HGTMP, "install")
675 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
675 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
676 PYTHONDIR = os.path.join(INST, "lib", "python")
676 PYTHONDIR = os.path.join(INST, "lib", "python")
677 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
677 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
678
678
679 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
679 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
680
680
681 if len(args) == 0:
681 if len(args) == 0:
682 args = os.listdir(".")
682 args = os.listdir(".")
683 args.sort()
683 args.sort()
684
684
685 tests = []
685 tests = []
686 for test in args:
686 for test in args:
687 if (test.startswith("test-") and '~' not in test and
687 if (test.startswith("test-") and '~' not in test and
688 ('.' not in test or test.endswith('.py') or
688 ('.' not in test or test.endswith('.py') or
689 test.endswith('.bat'))):
689 test.endswith('.bat'))):
690 tests.append(test)
690 tests.append(test)
691 if not tests:
692 print "# Ran 0 tests, 0 skipped, 0 failed."
693 return
691
694
692 vlog("# Using TESTDIR", TESTDIR)
695 vlog("# Using TESTDIR", TESTDIR)
693 vlog("# Using HGTMP", HGTMP)
696 vlog("# Using HGTMP", HGTMP)
694
697
695 try:
698 try:
696 if len(tests) > 1 and options.jobs > 1:
699 if len(tests) > 1 and options.jobs > 1:
697 runchildren(options, expecthg, tests)
700 runchildren(options, expecthg, tests)
698 else:
701 else:
699 runtests(options, expecthg, tests)
702 runtests(options, expecthg, tests)
700 finally:
703 finally:
701 cleanup(options)
704 cleanup(options)
702
705
703 main()
706 main()
General Comments 0
You need to be logged in to leave comments. Login now