##// END OF EJS Templates
run-tests: cosmetics
Simon Heimberg -
r8161:1bb8a75f default
parent child Browse files
Show More
@@ -1,709 +1,708 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
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, 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('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, options)
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 job in jobs:
516 if not tests: break
516 if not tests: break
517 jobs[j].append(tests.pop())
517 job.append(tests.pop())
518 fps = {}
518 fps = {}
519 for j in xrange(len(jobs)):
519 for j, job in enumerate(jobs):
520 job = jobs[j]
521 if not job:
520 if not job:
522 continue
521 continue
523 rfd, wfd = os.pipe()
522 rfd, wfd = os.pipe()
524 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
523 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
525 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
524 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
526 vlog(' '.join(cmdline))
525 vlog(' '.join(cmdline))
527 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
526 fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r')
528 os.close(wfd)
527 os.close(wfd)
529 failures = 0
528 failures = 0
530 tested, skipped, failed = 0, 0, 0
529 tested, skipped, failed = 0, 0, 0
531 skips = []
530 skips = []
532 fails = []
531 fails = []
533 while fps:
532 while fps:
534 pid, status = os.wait()
533 pid, status = os.wait()
535 fp = fps.pop(pid)
534 fp = fps.pop(pid)
536 l = fp.read().splitlines()
535 l = fp.read().splitlines()
537 test, skip, fail = map(int, l[:3])
536 test, skip, fail = map(int, l[:3])
538 split = -fail or len(l)
537 split = -fail or len(l)
539 for s in l[3:split]:
538 for s in l[3:split]:
540 skips.append(s.split(" ", 1))
539 skips.append(s.split(" ", 1))
541 for s in l[split:]:
540 for s in l[split:]:
542 fails.append(s.split(" ", 1))
541 fails.append(s.split(" ", 1))
543 tested += test
542 tested += test
544 skipped += skip
543 skipped += skip
545 failed += fail
544 failed += fail
546 vlog('pid %d exited, status %d' % (pid, status))
545 vlog('pid %d exited, status %d' % (pid, status))
547 failures |= status
546 failures |= status
548 print
547 print
549 for s in skips:
548 for s in skips:
550 print "Skipped %s: %s" % (s[0], s[1])
549 print "Skipped %s: %s" % (s[0], s[1])
551 for s in fails:
550 for s in fails:
552 print "Failed %s: %s" % (s[0], s[1])
551 print "Failed %s: %s" % (s[0], s[1])
553
552
554 if hgpkg != expecthg:
553 if hgpkg != expecthg:
555 print '# Tested unexpected mercurial: %s' % hgpkg
554 print '# Tested unexpected mercurial: %s' % hgpkg
556 print "# Ran %d tests, %d skipped, %d failed." % (
555 print "# Ran %d tests, %d skipped, %d failed." % (
557 tested, skipped, failed)
556 tested, skipped, failed)
558 sys.exit(failures != 0)
557 sys.exit(failures != 0)
559
558
560 def runtests(options, expecthg, tests):
559 def runtests(options, expecthg, tests):
561 global DAEMON_PIDS, HGRCPATH
560 global DAEMON_PIDS, HGRCPATH
562 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
561 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
563 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
562 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
564
563
565 try:
564 try:
566 if not options.with_hg:
565 if not options.with_hg:
567 installhg(options)
566 installhg(options)
568
567
569 if hgpkg != expecthg:
568 if hgpkg != expecthg:
570 print '# Testing unexpected mercurial: %s' % hgpkg
569 print '# Testing unexpected mercurial: %s' % hgpkg
571
570
572 if options.timeout > 0:
571 if options.timeout > 0:
573 try:
572 try:
574 signal.signal(signal.SIGALRM, alarmed)
573 signal.signal(signal.SIGALRM, alarmed)
575 vlog('# Running tests with %d-second timeout' %
574 vlog('# Running tests with %d-second timeout' %
576 options.timeout)
575 options.timeout)
577 except AttributeError:
576 except AttributeError:
578 print 'WARNING: cannot run tests with timeouts'
577 print 'WARNING: cannot run tests with timeouts'
579 options.timeout = 0
578 options.timeout = 0
580
579
581 tested = 0
580 tested = 0
582 failed = 0
581 failed = 0
583 skipped = 0
582 skipped = 0
584
583
585 if options.restart:
584 if options.restart:
586 orig = list(tests)
585 orig = list(tests)
587 while tests:
586 while tests:
588 if os.path.exists(tests[0] + ".err"):
587 if os.path.exists(tests[0] + ".err"):
589 break
588 break
590 tests.pop(0)
589 tests.pop(0)
591 if not tests:
590 if not tests:
592 print "running all tests"
591 print "running all tests"
593 tests = orig
592 tests = orig
594
593
595 skips = []
594 skips = []
596 fails = []
595 fails = []
597 for test in tests:
596 for test in tests:
598 if options.retest and not os.path.exists(test + ".err"):
597 if options.retest and not os.path.exists(test + ".err"):
599 skipped += 1
598 skipped += 1
600 continue
599 continue
601 ret = runone(options, test, skips, fails)
600 ret = runone(options, test, skips, fails)
602 if ret is None:
601 if ret is None:
603 skipped += 1
602 skipped += 1
604 elif not ret:
603 elif not ret:
605 if options.interactive:
604 if options.interactive:
606 print "Accept this change? [n] ",
605 print "Accept this change? [n] ",
607 answer = sys.stdin.readline().strip()
606 answer = sys.stdin.readline().strip()
608 if answer.lower() in "y yes".split():
607 if answer.lower() in "y yes".split():
609 rename(test + ".err", test + ".out")
608 rename(test + ".err", test + ".out")
610 tested += 1
609 tested += 1
611 fails.pop()
610 fails.pop()
612 continue
611 continue
613 failed += 1
612 failed += 1
614 if options.first:
613 if options.first:
615 break
614 break
616 tested += 1
615 tested += 1
617
616
618 if options.child:
617 if options.child:
619 fp = os.fdopen(options.child, 'w')
618 fp = os.fdopen(options.child, 'w')
620 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
619 fp.write('%d\n%d\n%d\n' % (tested, skipped, failed))
621 for s in skips:
620 for s in skips:
622 fp.write("%s %s\n" % s)
621 fp.write("%s %s\n" % s)
623 for s in fails:
622 for s in fails:
624 fp.write("%s %s\n" % s)
623 fp.write("%s %s\n" % s)
625 fp.close()
624 fp.close()
626 else:
625 else:
627 print
626 print
628 for s in skips:
627 for s in skips:
629 print "Skipped %s: %s" % s
628 print "Skipped %s: %s" % s
630 for s in fails:
629 for s in fails:
631 print "Failed %s: %s" % s
630 print "Failed %s: %s" % s
632 if hgpkg != expecthg:
631 if hgpkg != expecthg:
633 print '# Tested unexpected mercurial: %s' % hgpkg
632 print '# Tested unexpected mercurial: %s' % hgpkg
634 print "# Ran %d tests, %d skipped, %d failed." % (
633 print "# Ran %d tests, %d skipped, %d failed." % (
635 tested, skipped, failed)
634 tested, skipped, failed)
636
635
637 if options.anycoverage:
636 if options.anycoverage:
638 outputcoverage(options)
637 outputcoverage(options)
639 except KeyboardInterrupt:
638 except KeyboardInterrupt:
640 failed = True
639 failed = True
641 print "\ninterrupted!"
640 print "\ninterrupted!"
642
641
643 if failed:
642 if failed:
644 sys.exit(1)
643 sys.exit(1)
645
644
646 def main():
645 def main():
647 (options, args) = parseargs()
646 (options, args) = parseargs()
648 if not options.child:
647 if not options.child:
649 os.umask(022)
648 os.umask(022)
650
649
651 checktools()
650 checktools()
652
651
653 # Reset some environment variables to well-known values so that
652 # Reset some environment variables to well-known values so that
654 # the tests produce repeatable output.
653 # the tests produce repeatable output.
655 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
654 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
656 os.environ['TZ'] = 'GMT'
655 os.environ['TZ'] = 'GMT'
657 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
656 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
658 os.environ['CDPATH'] = ''
657 os.environ['CDPATH'] = ''
659
658
660 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
659 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
661 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
660 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
662 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
661 HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.',
663 options.tmpdir))
662 options.tmpdir))
664 DAEMON_PIDS = None
663 DAEMON_PIDS = None
665 HGRCPATH = None
664 HGRCPATH = None
666
665
667 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
666 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
668 os.environ["HGMERGE"] = "internal:merge"
667 os.environ["HGMERGE"] = "internal:merge"
669 os.environ["HGUSER"] = "test"
668 os.environ["HGUSER"] = "test"
670 os.environ["HGENCODING"] = "ascii"
669 os.environ["HGENCODING"] = "ascii"
671 os.environ["HGENCODINGMODE"] = "strict"
670 os.environ["HGENCODINGMODE"] = "strict"
672 os.environ["HGPORT"] = str(options.port)
671 os.environ["HGPORT"] = str(options.port)
673 os.environ["HGPORT1"] = str(options.port + 1)
672 os.environ["HGPORT1"] = str(options.port + 1)
674 os.environ["HGPORT2"] = str(options.port + 2)
673 os.environ["HGPORT2"] = str(options.port + 2)
675
674
676 if options.with_hg:
675 if options.with_hg:
677 INST = options.with_hg
676 INST = options.with_hg
678 else:
677 else:
679 INST = os.path.join(HGTMP, "install")
678 INST = os.path.join(HGTMP, "install")
680 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
679 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
681 PYTHONDIR = os.path.join(INST, "lib", "python")
680 PYTHONDIR = os.path.join(INST, "lib", "python")
682 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
681 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
683
682
684 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
683 expecthg = os.path.join(HGTMP, 'install', 'lib', 'python', 'mercurial')
685 hgpkg = None
684 hgpkg = None
686
685
687 if len(args) == 0:
686 if len(args) == 0:
688 args = os.listdir(".")
687 args = os.listdir(".")
689 args.sort()
688 args.sort()
690
689
691 tests = []
690 tests = []
692 for test in args:
691 for test in args:
693 if (test.startswith("test-") and '~' not in test and
692 if (test.startswith("test-") and '~' not in test and
694 ('.' not in test or test.endswith('.py') or
693 ('.' not in test or test.endswith('.py') or
695 test.endswith('.bat'))):
694 test.endswith('.bat'))):
696 tests.append(test)
695 tests.append(test)
697
696
698 vlog("# Using TESTDIR", TESTDIR)
697 vlog("# Using TESTDIR", TESTDIR)
699 vlog("# Using HGTMP", HGTMP)
698 vlog("# Using HGTMP", HGTMP)
700
699
701 try:
700 try:
702 if len(tests) > 1 and options.jobs > 1:
701 if len(tests) > 1 and options.jobs > 1:
703 runchildren(options, expecthg, tests)
702 runchildren(options, expecthg, tests)
704 else:
703 else:
705 runtests(options, expecthg, tests)
704 runtests(options, expecthg, tests)
706 finally:
705 finally:
707 cleanup(options)
706 cleanup(options)
708
707
709 main()
708 main()
General Comments 0
You need to be logged in to leave comments. Login now