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