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