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