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