##// END OF EJS Templates
run-tests.py: skip tests that should not run....
Vadim Gelfer -
r2710:e475fe2a default
parent child Browse files
Show More
@@ -1,354 +1,377 b''
1 1 #!/usr/bin/env python
2 2 #
3 3 # run-tests.py - Run a set of tests on Mercurial
4 4 #
5 5 # Copyright 2006 Matt Mackall <mpm@selenic.com>
6 6 #
7 7 # This software may be used and distributed according to the terms
8 8 # of the GNU General Public License, incorporated herein by reference.
9 9
10 10 import difflib
11 11 import errno
12 12 import optparse
13 13 import os
14 14 import popen2
15 15 import re
16 16 import shutil
17 17 import signal
18 18 import sys
19 19 import tempfile
20 20 import time
21 21
22 22 required_tools = ["python", "diff", "grep", "unzip", "gunzip", "bunzip2", "sed", "merge"]
23 23
24 24 parser = optparse.OptionParser("%prog [options] [tests]")
25 25 parser.add_option("-v", "--verbose", action="store_true",
26 26 help="output verbose messages")
27 27 parser.add_option("-t", "--timeout", type="int",
28 28 help="kill errant tests after TIMEOUT seconds")
29 29 parser.add_option("-c", "--cover", action="store_true",
30 30 help="print a test coverage report")
31 31 parser.add_option("-s", "--cover_stdlib", action="store_true",
32 32 help="print a test coverage report inc. standard libraries")
33 33 parser.add_option("-C", "--annotate", action="store_true",
34 34 help="output files annotated with coverage")
35 35 parser.set_defaults(timeout=180)
36 36 (options, args) = parser.parse_args()
37 37 verbose = options.verbose
38 38 coverage = options.cover or options.cover_stdlib or options.annotate
39 39
40 40 def vlog(*msg):
41 41 if verbose:
42 42 for m in msg:
43 43 print m,
44 44 print
45 45
46 46 def splitnewlines(text):
47 47 '''like str.splitlines, but only split on newlines.
48 48 keep line endings.'''
49 49 i = 0
50 50 lines = []
51 51 while True:
52 52 n = text.find('\n', i)
53 53 if n == -1:
54 54 last = text[i:]
55 55 if last:
56 56 lines.append(last)
57 57 return lines
58 58 lines.append(text[i:n+1])
59 59 i = n + 1
60 60
61 61 def show_diff(expected, output):
62 62 for line in difflib.unified_diff(expected, output,
63 63 "Expected output", "Test output"):
64 64 sys.stdout.write(line)
65 65
66 66 def find_program(program):
67 67 """Search PATH for a executable program"""
68 68 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
69 69 name = os.path.join(p, program)
70 70 if os.access(name, os.X_OK):
71 71 return name
72 72 return None
73 73
74 74 def check_required_tools():
75 75 # Before we go any further, check for pre-requisite tools
76 76 # stuff from coreutils (cat, rm, etc) are not tested
77 77 for p in required_tools:
78 78 if os.name == 'nt':
79 79 p += '.exe'
80 80 found = find_program(p)
81 81 if found:
82 82 vlog("# Found prerequisite", p, "at", found)
83 83 else:
84 84 print "WARNING: Did not find prerequisite tool: "+p
85 85
86 86 def cleanup_exit():
87 87 if verbose:
88 88 print "# Cleaning up HGTMP", HGTMP
89 89 shutil.rmtree(HGTMP, True)
90 90
91 91 def use_correct_python():
92 92 # some tests run python interpreter. they must use same
93 93 # interpreter we use or bad things will happen.
94 94 exedir, exename = os.path.split(sys.executable)
95 95 if exename == 'python':
96 96 path = find_program('python')
97 97 if os.path.dirname(path) == exedir:
98 98 return
99 99 vlog('# Making python executable in test path use correct Python')
100 100 my_python = os.path.join(BINDIR, 'python')
101 101 try:
102 102 os.symlink(sys.executable, my_python)
103 103 except AttributeError:
104 104 # windows fallback
105 105 shutil.copyfile(sys.executable, my_python)
106 106 shutil.copymode(sys.executable, my_python)
107 107
108 108 def install_hg():
109 109 vlog("# Performing temporary installation of HG")
110 110 installerrs = os.path.join("tests", "install.err")
111 111
112 112 os.chdir("..") # Get back to hg root
113 113 cmd = ('%s setup.py clean --all'
114 114 ' install --force --home="%s" --install-lib="%s" >%s 2>&1'
115 115 % (sys.executable, INST, PYTHONDIR, installerrs))
116 116 vlog("# Running", cmd)
117 117 if os.system(cmd) == 0:
118 118 if not verbose:
119 119 os.remove(installerrs)
120 120 else:
121 121 f = open(installerrs)
122 122 for line in f:
123 123 print line,
124 124 f.close()
125 125 sys.exit(1)
126 126 os.chdir(TESTDIR)
127 127
128 128 os.environ["PATH"] = "%s%s%s" % (BINDIR, os.pathsep, os.environ["PATH"])
129 129 os.environ["PYTHONPATH"] = PYTHONDIR
130 130
131 131 use_correct_python()
132 132
133 133 if coverage:
134 134 vlog("# Installing coverage wrapper")
135 135 os.environ['COVERAGE_FILE'] = COVERAGE_FILE
136 136 if os.path.exists(COVERAGE_FILE):
137 137 os.unlink(COVERAGE_FILE)
138 138 # Create a wrapper script to invoke hg via coverage.py
139 139 os.rename(os.path.join(BINDIR, "hg"), os.path.join(BINDIR, "_hg.py"))
140 140 f = open(os.path.join(BINDIR, 'hg'), 'w')
141 141 f.write('#!' + sys.executable + '\n')
142 142 f.write('import sys, os; os.execv(sys.executable, [sys.executable, '+ \
143 143 '"%s", "-x", "%s"] + sys.argv[1:])\n' % (
144 144 os.path.join(TESTDIR, 'coverage.py'),
145 145 os.path.join(BINDIR, '_hg.py')))
146 146 f.close()
147 147 os.chmod(os.path.join(BINDIR, 'hg'), 0700)
148 148
149 149 def output_coverage():
150 150 vlog("# Producing coverage report")
151 151 omit = [BINDIR, TESTDIR, PYTHONDIR]
152 152 if not options.cover_stdlib:
153 153 # Exclude as system paths (ignoring empty strings seen on win)
154 154 omit += [x for x in sys.path if x != '']
155 155 omit = ','.join(omit)
156 156 os.chdir(PYTHONDIR)
157 157 cmd = '"%s" "%s" -r "--omit=%s"' % (
158 158 sys.executable, os.path.join(TESTDIR, 'coverage.py'), omit)
159 159 vlog("# Running: "+cmd)
160 160 os.system(cmd)
161 161 if options.annotate:
162 162 adir = os.path.join(TESTDIR, 'annotated')
163 163 if not os.path.isdir(adir):
164 164 os.mkdir(adir)
165 165 cmd = '"%s" "%s" -a "--directory=%s" "--omit=%s"' % (
166 166 sys.executable, os.path.join(TESTDIR, 'coverage.py'),
167 167 adir, omit)
168 168 vlog("# Running: "+cmd)
169 169 os.system(cmd)
170 170
171 171 class Timeout(Exception):
172 172 pass
173 173
174 174 def alarmed(signum, frame):
175 175 raise Timeout
176 176
177 177 def run(cmd):
178 178 """Run command in a sub-process, capturing the output (stdout and stderr).
179 179 Return the exist code, and output."""
180 180 # TODO: Use subprocess.Popen if we're running on Python 2.4
181 181 if os.name == 'nt':
182 182 tochild, fromchild = os.popen4(cmd)
183 183 tochild.close()
184 184 output = fromchild.read()
185 185 ret = fromchild.close()
186 186 if ret == None:
187 187 ret = 0
188 188 else:
189 189 proc = popen2.Popen4(cmd)
190 190 try:
191 191 output = ''
192 192 proc.tochild.close()
193 193 output = proc.fromchild.read()
194 194 ret = proc.wait()
195 195 except Timeout:
196 196 vlog('# Process %d timed out - killing it' % proc.pid)
197 197 os.kill(proc.pid, signal.SIGTERM)
198 198 ret = proc.wait()
199 199 if ret == 0:
200 200 ret = signal.SIGTERM << 8
201 201 return ret, splitnewlines(output)
202 202
203 203 def run_one(test):
204 '''tristate output:
205 None -> skipped
206 True -> passed
207 False -> failed'''
208
204 209 vlog("# Test", test)
205 210 if not verbose:
206 211 sys.stdout.write('.')
207 212 sys.stdout.flush()
208 213
209 214 err = os.path.join(TESTDIR, test+".err")
210 215 ref = os.path.join(TESTDIR, test+".out")
211 216
212 217 if os.path.exists(err):
213 218 os.remove(err) # Remove any previous output files
214 219
215 220 # Make a tmp subdirectory to work in
216 221 tmpd = os.path.join(HGTMP, test)
217 222 os.mkdir(tmpd)
218 223 os.chdir(tmpd)
219 224
220 if test.endswith(".py"):
221 cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test))
222 else:
223 cmd = '"%s"' % (os.path.join(TESTDIR, test))
225 lctest = test.lower()
224 226
225 # To reliably get the error code from batch files on WinXP,
226 # the "cmd /c call" prefix is needed. Grrr
227 if os.name == 'nt' and test.endswith(".bat"):
227 if lctest.endswith('.py'):
228 cmd = '%s "%s"' % (sys.executable, os.path.join(TESTDIR, test))
229 elif lctest.endswith('.bat'):
230 # do not run batch scripts on non-windows
231 if os.name != 'nt':
232 print '\nSkipping %s: batch script' % test
233 return None
234 # To reliably get the error code from batch files on WinXP,
235 # the "cmd /c call" prefix is needed. Grrr
228 236 cmd = 'cmd /c call "%s"' % (os.path.join(TESTDIR, test))
237 else:
238 # do not run shell scripts on windows
239 if os.name == 'nt':
240 print '\nSkipping %s: shell script' % test
241 return None
242 # do not try to run non-executable programs
243 if not os.access(os.path.join(TESTDIR, test), os.X_OK):
244 print '\nSkipping %s: not executable' % test
245 return None
246 cmd = '"%s"' % (os.path.join(TESTDIR, test))
229 247
230 248 if options.timeout > 0:
231 249 signal.alarm(options.timeout)
232 250
233 251 vlog("# Running", cmd)
234 252 ret, out = run(cmd)
235 253 vlog("# Ret was:", ret)
236 254
237 255 if options.timeout > 0:
238 256 signal.alarm(0)
239 257
240 258 diffret = 0
241 259 # If reference output file exists, check test output against it
242 260 if os.path.exists(ref):
243 261 f = open(ref, "r")
244 262 ref_out = splitnewlines(f.read())
245 263 f.close()
246 264 else:
247 265 ref_out = []
248 266 if out != ref_out:
249 267 diffret = 1
250 268 print "\nERROR: %s output changed" % (test)
251 269 show_diff(ref_out, out)
252 270 if ret:
253 271 print "\nERROR: %s failed with error code %d" % (test, ret)
254 272 elif diffret:
255 273 ret = diffret
256 274
257 275 if ret != 0: # Save errors to a file for diagnosis
258 276 f = open(err, "wb")
259 277 for line in out:
260 278 f.write(line)
261 279 f.close()
262 280
263 281 # Kill off any leftover daemon processes
264 282 try:
265 283 fp = file(DAEMON_PIDS)
266 284 for line in fp:
267 285 try:
268 286 pid = int(line)
269 287 except ValueError:
270 288 continue
271 289 try:
272 290 os.kill(pid, 0)
273 291 vlog('# Killing daemon process %d' % pid)
274 292 os.kill(pid, signal.SIGTERM)
275 293 time.sleep(0.25)
276 294 os.kill(pid, 0)
277 295 vlog('# Daemon process %d is stuck - really killing it' % pid)
278 296 os.kill(pid, signal.SIGKILL)
279 297 except OSError, err:
280 298 if err.errno != errno.ESRCH:
281 299 raise
282 300 fp.close()
283 301 os.unlink(DAEMON_PIDS)
284 302 except IOError:
285 303 pass
286 304
287 305 os.chdir(TESTDIR)
288 306 shutil.rmtree(tmpd, True)
289 307 return ret == 0
290 308
291 309
292 310 os.umask(022)
293 311
294 312 check_required_tools()
295 313
296 314 # Reset some environment variables to well-known values so that
297 315 # the tests produce repeatable output.
298 316 os.environ['LANG'] = os.environ['LC_ALL'] = 'C'
299 317 os.environ['TZ'] = 'GMT'
300 318
301 319 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
302 320 os.environ["HGMERGE"] = sys.executable + ' -c "import sys; sys.exit(0)"'
303 321 os.environ["HGUSER"] = "test"
304 322 os.environ["HGRCPATH"] = ""
305 323
306 324 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
307 325 HGTMP = os.environ["HGTMP"] = tempfile.mkdtemp("", "hgtests.")
308 326 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
309 327
310 328 vlog("# Using TESTDIR", TESTDIR)
311 329 vlog("# Using HGTMP", HGTMP)
312 330
313 331 INST = os.path.join(HGTMP, "install")
314 332 BINDIR = os.path.join(INST, "bin")
315 333 PYTHONDIR = os.path.join(INST, "lib", "python")
316 334 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
317 335
318 336 try:
319 337 try:
320 338 install_hg()
321 339
322 340 if options.timeout > 0:
323 341 try:
324 342 signal.signal(signal.SIGALRM, alarmed)
325 343 vlog('# Running tests with %d-second timeout' %
326 344 options.timeout)
327 345 except AttributeError:
328 346 print 'WARNING: cannot run tests with timeouts'
329 347 options.timeout = 0
330 348
331 349 tests = 0
332 350 failed = 0
351 skipped = 0
333 352
334 353 if len(args) == 0:
335 354 args = os.listdir(".")
336 355 for test in args:
337 356 if (test.startswith("test-") and '~' not in test and
338 357 ('.' not in test or test.endswith('.py') or
339 358 test.endswith('.bat'))):
340 if not run_one(test):
359 ret = run_one(test)
360 if ret is None:
361 skipped += 1
362 elif not ret:
341 363 failed += 1
342 364 tests += 1
343 365
344 print "\n# Ran %d tests, %d failed." % (tests, failed)
366 print "\n# Ran %d tests, %d skipped, %d failed." % (tests, skipped,
367 failed)
345 368 if coverage:
346 369 output_coverage()
347 370 except KeyboardInterrupt:
348 371 failed = True
349 372 print "\ninterrupted!"
350 373 finally:
351 374 cleanup_exit()
352 375
353 376 if failed:
354 377 sys.exit(1)
General Comments 0
You need to be logged in to leave comments. Login now