##// END OF EJS Templates
run-tests: only sort files when not given as argument...
Simon Heimberg -
r18788:05d544d1 default
parent child Browse files
Show More
@@ -1,1373 +1,1372 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 of the
8 8 # GNU General Public License version 2 or any later version.
9 9
10 10 # Modifying this script is tricky because it has many modes:
11 11 # - serial (default) vs parallel (-jN, N > 1)
12 12 # - no coverage (default) vs coverage (-c, -C, -s)
13 13 # - temp install (default) vs specific hg script (--with-hg, --local)
14 14 # - tests are a mix of shell scripts and Python scripts
15 15 #
16 16 # If you change this script, it is recommended that you ensure you
17 17 # haven't broken it by running it in various modes with a representative
18 18 # sample of test scripts. For example:
19 19 #
20 20 # 1) serial, no coverage, temp install:
21 21 # ./run-tests.py test-s*
22 22 # 2) serial, no coverage, local hg:
23 23 # ./run-tests.py --local test-s*
24 24 # 3) serial, coverage, temp install:
25 25 # ./run-tests.py -c test-s*
26 26 # 4) serial, coverage, local hg:
27 27 # ./run-tests.py -c --local test-s* # unsupported
28 28 # 5) parallel, no coverage, temp install:
29 29 # ./run-tests.py -j2 test-s*
30 30 # 6) parallel, no coverage, local hg:
31 31 # ./run-tests.py -j2 --local test-s*
32 32 # 7) parallel, coverage, temp install:
33 33 # ./run-tests.py -j2 -c test-s* # currently broken
34 34 # 8) parallel, coverage, local install:
35 35 # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken)
36 36 # 9) parallel, custom tmp dir:
37 37 # ./run-tests.py -j2 --tmpdir /tmp/myhgtests
38 38 #
39 39 # (You could use any subset of the tests: test-s* happens to match
40 40 # enough that it's worth doing parallel runs, few enough that it
41 41 # completes fairly quickly, includes both shell and Python scripts, and
42 42 # includes some scripts that run daemon processes.)
43 43
44 44 from distutils import version
45 45 import difflib
46 46 import errno
47 47 import optparse
48 48 import os
49 49 import shutil
50 50 import subprocess
51 51 import signal
52 52 import sys
53 53 import tempfile
54 54 import time
55 55 import random
56 56 import re
57 57 import threading
58 58 import killdaemons as killmod
59 59 import cPickle as pickle
60 60 import Queue as queue
61 61
62 62 processlock = threading.Lock()
63 63
64 64 closefds = os.name == 'posix'
65 65 def Popen4(cmd, wd, timeout):
66 66 processlock.acquire()
67 67 p = subprocess.Popen(cmd, shell=True, bufsize=-1, cwd=wd,
68 68 close_fds=closefds,
69 69 stdin=subprocess.PIPE, stdout=subprocess.PIPE,
70 70 stderr=subprocess.STDOUT)
71 71 processlock.release()
72 72
73 73 p.fromchild = p.stdout
74 74 p.tochild = p.stdin
75 75 p.childerr = p.stderr
76 76
77 77 p.timeout = False
78 78 if timeout:
79 79 def t():
80 80 start = time.time()
81 81 while time.time() - start < timeout and p.returncode is None:
82 82 time.sleep(.1)
83 83 p.timeout = True
84 84 if p.returncode is None:
85 85 terminate(p)
86 86 threading.Thread(target=t).start()
87 87
88 88 return p
89 89
90 90 # reserved exit code to skip test (used by hghave)
91 91 SKIPPED_STATUS = 80
92 92 SKIPPED_PREFIX = 'skipped: '
93 93 FAILED_PREFIX = 'hghave check failed: '
94 94 PYTHON = sys.executable.replace('\\', '/')
95 95 IMPL_PATH = 'PYTHONPATH'
96 96 if 'java' in sys.platform:
97 97 IMPL_PATH = 'JYTHONPATH'
98 98
99 99 requiredtools = [os.path.basename(sys.executable), "diff", "grep", "unzip",
100 100 "gunzip", "bunzip2", "sed"]
101 101
102 102 defaults = {
103 103 'jobs': ('HGTEST_JOBS', 1),
104 104 'timeout': ('HGTEST_TIMEOUT', 180),
105 105 'port': ('HGTEST_PORT', 20059),
106 106 'shell': ('HGTEST_SHELL', 'sh'),
107 107 }
108 108
109 109 def parselistfiles(files, listtype, warn=True):
110 110 entries = dict()
111 111 for filename in files:
112 112 try:
113 113 path = os.path.expanduser(os.path.expandvars(filename))
114 114 f = open(path, "r")
115 115 except IOError, err:
116 116 if err.errno != errno.ENOENT:
117 117 raise
118 118 if warn:
119 119 print "warning: no such %s file: %s" % (listtype, filename)
120 120 continue
121 121
122 122 for line in f.readlines():
123 123 line = line.split('#', 1)[0].strip()
124 124 if line:
125 125 entries[line] = filename
126 126
127 127 f.close()
128 128 return entries
129 129
130 130 def parseargs():
131 131 parser = optparse.OptionParser("%prog [options] [tests]")
132 132
133 133 # keep these sorted
134 134 parser.add_option("--blacklist", action="append",
135 135 help="skip tests listed in the specified blacklist file")
136 136 parser.add_option("--whitelist", action="append",
137 137 help="always run tests listed in the specified whitelist file")
138 138 parser.add_option("-C", "--annotate", action="store_true",
139 139 help="output files annotated with coverage")
140 140 parser.add_option("--child", type="int",
141 141 help="run as child process, summary to given fd")
142 142 parser.add_option("-c", "--cover", action="store_true",
143 143 help="print a test coverage report")
144 144 parser.add_option("-d", "--debug", action="store_true",
145 145 help="debug mode: write output of test scripts to console"
146 146 " rather than capturing and diff'ing it (disables timeout)")
147 147 parser.add_option("-f", "--first", action="store_true",
148 148 help="exit on the first test failure")
149 149 parser.add_option("-H", "--htmlcov", action="store_true",
150 150 help="create an HTML report of the coverage of the files")
151 151 parser.add_option("--inotify", action="store_true",
152 152 help="enable inotify extension when running tests")
153 153 parser.add_option("-i", "--interactive", action="store_true",
154 154 help="prompt to accept changed output")
155 155 parser.add_option("-j", "--jobs", type="int",
156 156 help="number of jobs to run in parallel"
157 157 " (default: $%s or %d)" % defaults['jobs'])
158 158 parser.add_option("--keep-tmpdir", action="store_true",
159 159 help="keep temporary directory after running tests")
160 160 parser.add_option("-k", "--keywords",
161 161 help="run tests matching keywords")
162 162 parser.add_option("-l", "--local", action="store_true",
163 163 help="shortcut for --with-hg=<testdir>/../hg")
164 164 parser.add_option("-n", "--nodiff", action="store_true",
165 165 help="skip showing test changes")
166 166 parser.add_option("-p", "--port", type="int",
167 167 help="port on which servers should listen"
168 168 " (default: $%s or %d)" % defaults['port'])
169 169 parser.add_option("--compiler", type="string",
170 170 help="compiler to build with")
171 171 parser.add_option("--pure", action="store_true",
172 172 help="use pure Python code instead of C extensions")
173 173 parser.add_option("-R", "--restart", action="store_true",
174 174 help="restart at last error")
175 175 parser.add_option("-r", "--retest", action="store_true",
176 176 help="retest failed tests")
177 177 parser.add_option("-S", "--noskips", action="store_true",
178 178 help="don't report skip tests verbosely")
179 179 parser.add_option("--shell", type="string",
180 180 help="shell to use (default: $%s or %s)" % defaults['shell'])
181 181 parser.add_option("-t", "--timeout", type="int",
182 182 help="kill errant tests after TIMEOUT seconds"
183 183 " (default: $%s or %d)" % defaults['timeout'])
184 184 parser.add_option("--time", action="store_true",
185 185 help="time how long each test takes")
186 186 parser.add_option("--tmpdir", type="string",
187 187 help="run tests in the given temporary directory"
188 188 " (implies --keep-tmpdir)")
189 189 parser.add_option("-v", "--verbose", action="store_true",
190 190 help="output verbose messages")
191 191 parser.add_option("--view", type="string",
192 192 help="external diff viewer")
193 193 parser.add_option("--with-hg", type="string",
194 194 metavar="HG",
195 195 help="test using specified hg script rather than a "
196 196 "temporary installation")
197 197 parser.add_option("-3", "--py3k-warnings", action="store_true",
198 198 help="enable Py3k warnings on Python 2.6+")
199 199 parser.add_option('--extra-config-opt', action="append",
200 200 help='set the given config opt in the test hgrc')
201 201
202 202 for option, (envvar, default) in defaults.items():
203 203 defaults[option] = type(default)(os.environ.get(envvar, default))
204 204 parser.set_defaults(**defaults)
205 205 (options, args) = parser.parse_args()
206 206
207 207 # jython is always pure
208 208 if 'java' in sys.platform or '__pypy__' in sys.modules:
209 209 options.pure = True
210 210
211 211 if options.with_hg:
212 212 options.with_hg = os.path.expanduser(options.with_hg)
213 213 if not (os.path.isfile(options.with_hg) and
214 214 os.access(options.with_hg, os.X_OK)):
215 215 parser.error('--with-hg must specify an executable hg script')
216 216 if not os.path.basename(options.with_hg) == 'hg':
217 217 sys.stderr.write('warning: --with-hg should specify an hg script\n')
218 218 if options.local:
219 219 testdir = os.path.dirname(os.path.realpath(sys.argv[0]))
220 220 hgbin = os.path.join(os.path.dirname(testdir), 'hg')
221 221 if os.name != 'nt' and not os.access(hgbin, os.X_OK):
222 222 parser.error('--local specified, but %r not found or not executable'
223 223 % hgbin)
224 224 options.with_hg = hgbin
225 225
226 226 options.anycoverage = options.cover or options.annotate or options.htmlcov
227 227 if options.anycoverage:
228 228 try:
229 229 import coverage
230 230 covver = version.StrictVersion(coverage.__version__).version
231 231 if covver < (3, 3):
232 232 parser.error('coverage options require coverage 3.3 or later')
233 233 except ImportError:
234 234 parser.error('coverage options now require the coverage package')
235 235
236 236 if options.anycoverage and options.local:
237 237 # this needs some path mangling somewhere, I guess
238 238 parser.error("sorry, coverage options do not work when --local "
239 239 "is specified")
240 240
241 241 global vlog
242 242 if options.verbose:
243 243 if options.jobs > 1 or options.child is not None:
244 244 pid = "[%d]" % os.getpid()
245 245 else:
246 246 pid = None
247 247 def vlog(*msg):
248 248 iolock.acquire()
249 249 if pid:
250 250 print pid,
251 251 for m in msg:
252 252 print m,
253 253 print
254 254 sys.stdout.flush()
255 255 iolock.release()
256 256 else:
257 257 vlog = lambda *msg: None
258 258
259 259 if options.tmpdir:
260 260 options.tmpdir = os.path.expanduser(options.tmpdir)
261 261
262 262 if options.jobs < 1:
263 263 parser.error('--jobs must be positive')
264 264 if options.interactive and options.jobs > 1:
265 265 print '(--interactive overrides --jobs)'
266 266 options.jobs = 1
267 267 if options.interactive and options.debug:
268 268 parser.error("-i/--interactive and -d/--debug are incompatible")
269 269 if options.debug:
270 270 if options.timeout != defaults['timeout']:
271 271 sys.stderr.write(
272 272 'warning: --timeout option ignored with --debug\n')
273 273 options.timeout = 0
274 274 if options.time:
275 275 sys.stderr.write(
276 276 'warning: --time option ignored with --debug\n')
277 277 options.time = False
278 278 if options.py3k_warnings:
279 279 if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0):
280 280 parser.error('--py3k-warnings can only be used on Python 2.6+')
281 281 if options.blacklist:
282 282 options.blacklist = parselistfiles(options.blacklist, 'blacklist')
283 283 if options.whitelist:
284 284 options.whitelisted = parselistfiles(options.whitelist, 'whitelist',
285 285 warn=options.child is None)
286 286 else:
287 287 options.whitelisted = {}
288 288
289 289 return (options, args)
290 290
291 291 def rename(src, dst):
292 292 """Like os.rename(), trade atomicity and opened files friendliness
293 293 for existing destination support.
294 294 """
295 295 shutil.copy(src, dst)
296 296 os.remove(src)
297 297
298 298 def parsehghaveoutput(lines):
299 299 '''Parse hghave log lines.
300 300 Return tuple of lists (missing, failed):
301 301 * the missing/unknown features
302 302 * the features for which existence check failed'''
303 303 missing = []
304 304 failed = []
305 305 for line in lines:
306 306 if line.startswith(SKIPPED_PREFIX):
307 307 line = line.splitlines()[0]
308 308 missing.append(line[len(SKIPPED_PREFIX):])
309 309 elif line.startswith(FAILED_PREFIX):
310 310 line = line.splitlines()[0]
311 311 failed.append(line[len(FAILED_PREFIX):])
312 312
313 313 return missing, failed
314 314
315 315 def showdiff(expected, output, ref, err):
316 316 print
317 317 for line in difflib.unified_diff(expected, output, ref, err):
318 318 sys.stdout.write(line)
319 319
320 320 def findprogram(program):
321 321 """Search PATH for a executable program"""
322 322 for p in os.environ.get('PATH', os.defpath).split(os.pathsep):
323 323 name = os.path.join(p, program)
324 324 if os.name == 'nt' or os.access(name, os.X_OK):
325 325 return name
326 326 return None
327 327
328 328 def checktools():
329 329 # Before we go any further, check for pre-requisite tools
330 330 # stuff from coreutils (cat, rm, etc) are not tested
331 331 for p in requiredtools:
332 332 if os.name == 'nt' and not p.endswith('.exe'):
333 333 p += '.exe'
334 334 found = findprogram(p)
335 335 if found:
336 336 vlog("# Found prerequisite", p, "at", found)
337 337 else:
338 338 print "WARNING: Did not find prerequisite tool: "+p
339 339
340 340 def terminate(proc):
341 341 """Terminate subprocess (with fallback for Python versions < 2.6)"""
342 342 vlog('# Terminating process %d' % proc.pid)
343 343 try:
344 344 getattr(proc, 'terminate', lambda : os.kill(proc.pid, signal.SIGTERM))()
345 345 except OSError:
346 346 pass
347 347
348 348 def killdaemons():
349 349 return killmod.killdaemons(DAEMON_PIDS, tryhard=False, remove=True,
350 350 logfn=vlog)
351 351
352 352 def cleanup(options):
353 353 if not options.keep_tmpdir:
354 354 vlog("# Cleaning up HGTMP", HGTMP)
355 355 shutil.rmtree(HGTMP, True)
356 356
357 357 def usecorrectpython():
358 358 # some tests run python interpreter. they must use same
359 359 # interpreter we use or bad things will happen.
360 360 pyexename = sys.platform == 'win32' and 'python.exe' or 'python'
361 361 if getattr(os, 'symlink', None):
362 362 vlog("# Making python executable in test path a symlink to '%s'" %
363 363 sys.executable)
364 364 mypython = os.path.join(BINDIR, pyexename)
365 365 try:
366 366 if os.readlink(mypython) == sys.executable:
367 367 return
368 368 os.unlink(mypython)
369 369 except OSError, err:
370 370 if err.errno != errno.ENOENT:
371 371 raise
372 372 if findprogram(pyexename) != sys.executable:
373 373 try:
374 374 os.symlink(sys.executable, mypython)
375 375 except OSError, err:
376 376 # child processes may race, which is harmless
377 377 if err.errno != errno.EEXIST:
378 378 raise
379 379 else:
380 380 exedir, exename = os.path.split(sys.executable)
381 381 vlog("# Modifying search path to find %s as %s in '%s'" %
382 382 (exename, pyexename, exedir))
383 383 path = os.environ['PATH'].split(os.pathsep)
384 384 while exedir in path:
385 385 path.remove(exedir)
386 386 os.environ['PATH'] = os.pathsep.join([exedir] + path)
387 387 if not findprogram(pyexename):
388 388 print "WARNING: Cannot find %s in search path" % pyexename
389 389
390 390 def installhg(options):
391 391 vlog("# Performing temporary installation of HG")
392 392 installerrs = os.path.join("tests", "install.err")
393 393 compiler = ''
394 394 if options.compiler:
395 395 compiler = '--compiler ' + options.compiler
396 396 pure = options.pure and "--pure" or ""
397 397
398 398 # Run installer in hg root
399 399 script = os.path.realpath(sys.argv[0])
400 400 hgroot = os.path.dirname(os.path.dirname(script))
401 401 os.chdir(hgroot)
402 402 nohome = '--home=""'
403 403 if os.name == 'nt':
404 404 # The --home="" trick works only on OS where os.sep == '/'
405 405 # because of a distutils convert_path() fast-path. Avoid it at
406 406 # least on Windows for now, deal with .pydistutils.cfg bugs
407 407 # when they happen.
408 408 nohome = ''
409 409 cmd = ('%(exe)s setup.py %(pure)s clean --all'
410 410 ' build %(compiler)s --build-base="%(base)s"'
411 411 ' install --force --prefix="%(prefix)s" --install-lib="%(libdir)s"'
412 412 ' --install-scripts="%(bindir)s" %(nohome)s >%(logfile)s 2>&1'
413 413 % dict(exe=sys.executable, pure=pure, compiler=compiler,
414 414 base=os.path.join(HGTMP, "build"),
415 415 prefix=INST, libdir=PYTHONDIR, bindir=BINDIR,
416 416 nohome=nohome, logfile=installerrs))
417 417 vlog("# Running", cmd)
418 418 if os.system(cmd) == 0:
419 419 if not options.verbose:
420 420 os.remove(installerrs)
421 421 else:
422 422 f = open(installerrs)
423 423 for line in f:
424 424 print line,
425 425 f.close()
426 426 sys.exit(1)
427 427 os.chdir(TESTDIR)
428 428
429 429 usecorrectpython()
430 430
431 431 vlog("# Installing dummy diffstat")
432 432 f = open(os.path.join(BINDIR, 'diffstat'), 'w')
433 433 f.write('#!' + sys.executable + '\n'
434 434 'import sys\n'
435 435 'files = 0\n'
436 436 'for line in sys.stdin:\n'
437 437 ' if line.startswith("diff "):\n'
438 438 ' files += 1\n'
439 439 'sys.stdout.write("files patched: %d\\n" % files)\n')
440 440 f.close()
441 441 os.chmod(os.path.join(BINDIR, 'diffstat'), 0700)
442 442
443 443 if options.py3k_warnings and not options.anycoverage:
444 444 vlog("# Updating hg command to enable Py3k Warnings switch")
445 445 f = open(os.path.join(BINDIR, 'hg'), 'r')
446 446 lines = [line.rstrip() for line in f]
447 447 lines[0] += ' -3'
448 448 f.close()
449 449 f = open(os.path.join(BINDIR, 'hg'), 'w')
450 450 for line in lines:
451 451 f.write(line + '\n')
452 452 f.close()
453 453
454 454 hgbat = os.path.join(BINDIR, 'hg.bat')
455 455 if os.path.isfile(hgbat):
456 456 # hg.bat expects to be put in bin/scripts while run-tests.py
457 457 # installation layout put it in bin/ directly. Fix it
458 458 f = open(hgbat, 'rb')
459 459 data = f.read()
460 460 f.close()
461 461 if '"%~dp0..\python" "%~dp0hg" %*' in data:
462 462 data = data.replace('"%~dp0..\python" "%~dp0hg" %*',
463 463 '"%~dp0python" "%~dp0hg" %*')
464 464 f = open(hgbat, 'wb')
465 465 f.write(data)
466 466 f.close()
467 467 else:
468 468 print 'WARNING: cannot fix hg.bat reference to python.exe'
469 469
470 470 if options.anycoverage:
471 471 custom = os.path.join(TESTDIR, 'sitecustomize.py')
472 472 target = os.path.join(PYTHONDIR, 'sitecustomize.py')
473 473 vlog('# Installing coverage trigger to %s' % target)
474 474 shutil.copyfile(custom, target)
475 475 rc = os.path.join(TESTDIR, '.coveragerc')
476 476 vlog('# Installing coverage rc to %s' % rc)
477 477 os.environ['COVERAGE_PROCESS_START'] = rc
478 478 fn = os.path.join(INST, '..', '.coverage')
479 479 os.environ['COVERAGE_FILE'] = fn
480 480
481 481 def outputtimes(options):
482 482 vlog('# Producing time report')
483 483 times.sort(key=lambda t: (t[1], t[0]), reverse=True)
484 484 cols = '%7.3f %s'
485 485 print '\n%-7s %s' % ('Time', 'Test')
486 486 for test, timetaken in times:
487 487 print cols % (timetaken, test)
488 488
489 489 def outputcoverage(options):
490 490
491 491 vlog('# Producing coverage report')
492 492 os.chdir(PYTHONDIR)
493 493
494 494 def covrun(*args):
495 495 cmd = 'coverage %s' % ' '.join(args)
496 496 vlog('# Running: %s' % cmd)
497 497 os.system(cmd)
498 498
499 499 if options.child:
500 500 return
501 501
502 502 covrun('-c')
503 503 omit = ','.join(os.path.join(x, '*') for x in [BINDIR, TESTDIR])
504 504 covrun('-i', '-r', '"--omit=%s"' % omit) # report
505 505 if options.htmlcov:
506 506 htmldir = os.path.join(TESTDIR, 'htmlcov')
507 507 covrun('-i', '-b', '"--directory=%s"' % htmldir, '"--omit=%s"' % omit)
508 508 if options.annotate:
509 509 adir = os.path.join(TESTDIR, 'annotated')
510 510 if not os.path.isdir(adir):
511 511 os.mkdir(adir)
512 512 covrun('-i', '-a', '"--directory=%s"' % adir, '"--omit=%s"' % omit)
513 513
514 514 def pytest(test, wd, options, replacements):
515 515 py3kswitch = options.py3k_warnings and ' -3' or ''
516 516 cmd = '%s%s "%s"' % (PYTHON, py3kswitch, test)
517 517 vlog("# Running", cmd)
518 518 if os.name == 'nt':
519 519 replacements.append((r'\r\n', '\n'))
520 520 return run(cmd, wd, options, replacements)
521 521
522 522 needescape = re.compile(r'[\x00-\x08\x0b-\x1f\x7f-\xff]').search
523 523 escapesub = re.compile(r'[\x00-\x08\x0b-\x1f\\\x7f-\xff]').sub
524 524 escapemap = dict((chr(i), r'\x%02x' % i) for i in range(256))
525 525 escapemap.update({'\\': '\\\\', '\r': r'\r'})
526 526 def escapef(m):
527 527 return escapemap[m.group(0)]
528 528 def stringescape(s):
529 529 return escapesub(escapef, s)
530 530
531 531 def rematch(el, l):
532 532 try:
533 533 # use \Z to ensure that the regex matches to the end of the string
534 534 if os.name == 'nt':
535 535 return re.match(el + r'\r?\n\Z', l)
536 536 return re.match(el + r'\n\Z', l)
537 537 except re.error:
538 538 # el is an invalid regex
539 539 return False
540 540
541 541 def globmatch(el, l):
542 542 # The only supported special characters are * and ? plus / which also
543 543 # matches \ on windows. Escaping of these caracters is supported.
544 544 if el + '\n' == l:
545 545 if os.name == 'nt':
546 546 # matching on "/" is not needed for this line
547 547 iolock.acquire()
548 548 print "\nInfo, unnecessary glob: %s (glob)" % el
549 549 iolock.release()
550 550 return True
551 551 i, n = 0, len(el)
552 552 res = ''
553 553 while i < n:
554 554 c = el[i]
555 555 i += 1
556 556 if c == '\\' and el[i] in '*?\\/':
557 557 res += el[i - 1:i + 1]
558 558 i += 1
559 559 elif c == '*':
560 560 res += '.*'
561 561 elif c == '?':
562 562 res += '.'
563 563 elif c == '/' and os.name == 'nt':
564 564 res += '[/\\\\]'
565 565 else:
566 566 res += re.escape(c)
567 567 return rematch(res, l)
568 568
569 569 def linematch(el, l):
570 570 if el == l: # perfect match (fast)
571 571 return True
572 572 if el:
573 573 if el.endswith(" (esc)\n"):
574 574 el = el[:-7].decode('string-escape') + '\n'
575 575 if el == l or os.name == 'nt' and el[:-1] + '\r\n' == l:
576 576 return True
577 577 if (el.endswith(" (re)\n") and rematch(el[:-6], l) or
578 578 el.endswith(" (glob)\n") and globmatch(el[:-8], l)):
579 579 return True
580 580 return False
581 581
582 582 def tsttest(test, wd, options, replacements):
583 583 # We generate a shell script which outputs unique markers to line
584 584 # up script results with our source. These markers include input
585 585 # line number and the last return code
586 586 salt = "SALT" + str(time.time())
587 587 def addsalt(line, inpython):
588 588 if inpython:
589 589 script.append('%s %d 0\n' % (salt, line))
590 590 else:
591 591 script.append('echo %s %s $?\n' % (salt, line))
592 592
593 593 # After we run the shell script, we re-unify the script output
594 594 # with non-active parts of the source, with synchronization by our
595 595 # SALT line number markers. The after table contains the
596 596 # non-active components, ordered by line number
597 597 after = {}
598 598 pos = prepos = -1
599 599
600 600 # Expected shellscript output
601 601 expected = {}
602 602
603 603 # We keep track of whether or not we're in a Python block so we
604 604 # can generate the surrounding doctest magic
605 605 inpython = False
606 606
607 607 # True or False when in a true or false conditional section
608 608 skipping = None
609 609
610 610 def hghave(reqs):
611 611 # TODO: do something smarter when all other uses of hghave is gone
612 612 tdir = TESTDIR.replace('\\', '/')
613 613 proc = Popen4('%s -c "%s/hghave %s"' %
614 614 (options.shell, tdir, ' '.join(reqs)), wd, 0)
615 615 stdout, stderr = proc.communicate()
616 616 ret = proc.wait()
617 617 if wifexited(ret):
618 618 ret = os.WEXITSTATUS(ret)
619 619 if ret == 2:
620 620 print stdout
621 621 sys.exit(1)
622 622 return ret == 0
623 623
624 624 f = open(test)
625 625 t = f.readlines()
626 626 f.close()
627 627
628 628 script = []
629 629 if options.debug:
630 630 script.append('set -x\n')
631 631 if os.getenv('MSYSTEM'):
632 632 script.append('alias pwd="pwd -W"\n')
633 633 n = 0
634 634 for n, l in enumerate(t):
635 635 if not l.endswith('\n'):
636 636 l += '\n'
637 637 if l.startswith('#if'):
638 638 if skipping is not None:
639 639 after.setdefault(pos, []).append(' !!! nested #if\n')
640 640 skipping = not hghave(l.split()[1:])
641 641 after.setdefault(pos, []).append(l)
642 642 elif l.startswith('#else'):
643 643 if skipping is None:
644 644 after.setdefault(pos, []).append(' !!! missing #if\n')
645 645 skipping = not skipping
646 646 after.setdefault(pos, []).append(l)
647 647 elif l.startswith('#endif'):
648 648 if skipping is None:
649 649 after.setdefault(pos, []).append(' !!! missing #if\n')
650 650 skipping = None
651 651 after.setdefault(pos, []).append(l)
652 652 elif skipping:
653 653 after.setdefault(pos, []).append(l)
654 654 elif l.startswith(' >>> '): # python inlines
655 655 after.setdefault(pos, []).append(l)
656 656 prepos = pos
657 657 pos = n
658 658 if not inpython:
659 659 # we've just entered a Python block, add the header
660 660 inpython = True
661 661 addsalt(prepos, False) # make sure we report the exit code
662 662 script.append('%s -m heredoctest <<EOF\n' % PYTHON)
663 663 addsalt(n, True)
664 664 script.append(l[2:])
665 665 elif l.startswith(' ... '): # python inlines
666 666 after.setdefault(prepos, []).append(l)
667 667 script.append(l[2:])
668 668 elif l.startswith(' $ '): # commands
669 669 if inpython:
670 670 script.append("EOF\n")
671 671 inpython = False
672 672 after.setdefault(pos, []).append(l)
673 673 prepos = pos
674 674 pos = n
675 675 addsalt(n, False)
676 676 cmd = l[4:].split()
677 677 if len(cmd) == 2 and cmd[0] == 'cd':
678 678 l = ' $ cd %s || exit 1\n' % cmd[1]
679 679 script.append(l[4:])
680 680 elif l.startswith(' > '): # continuations
681 681 after.setdefault(prepos, []).append(l)
682 682 script.append(l[4:])
683 683 elif l.startswith(' '): # results
684 684 # queue up a list of expected results
685 685 expected.setdefault(pos, []).append(l[2:])
686 686 else:
687 687 if inpython:
688 688 script.append("EOF\n")
689 689 inpython = False
690 690 # non-command/result - queue up for merged output
691 691 after.setdefault(pos, []).append(l)
692 692
693 693 if inpython:
694 694 script.append("EOF\n")
695 695 if skipping is not None:
696 696 after.setdefault(pos, []).append(' !!! missing #endif\n')
697 697 addsalt(n + 1, False)
698 698
699 699 # Write out the script and execute it
700 700 fd, name = tempfile.mkstemp(suffix='hg-tst')
701 701 try:
702 702 for l in script:
703 703 os.write(fd, l)
704 704 os.close(fd)
705 705
706 706 cmd = '%s "%s"' % (options.shell, name)
707 707 vlog("# Running", cmd)
708 708 exitcode, output = run(cmd, wd, options, replacements)
709 709 # do not merge output if skipped, return hghave message instead
710 710 # similarly, with --debug, output is None
711 711 if exitcode == SKIPPED_STATUS or output is None:
712 712 return exitcode, output
713 713 finally:
714 714 os.remove(name)
715 715
716 716 # Merge the script output back into a unified test
717 717
718 718 pos = -1
719 719 postout = []
720 720 ret = 0
721 721 for l in output:
722 722 lout, lcmd = l, None
723 723 if salt in l:
724 724 lout, lcmd = l.split(salt, 1)
725 725
726 726 if lout:
727 727 if not lout.endswith('\n'):
728 728 lout += ' (no-eol)\n'
729 729
730 730 # find the expected output at the current position
731 731 el = None
732 732 if pos in expected and expected[pos]:
733 733 el = expected[pos].pop(0)
734 734
735 735 if linematch(el, lout):
736 736 postout.append(" " + el)
737 737 else:
738 738 if needescape(lout):
739 739 lout = stringescape(lout.rstrip('\n')) + " (esc)\n"
740 740 postout.append(" " + lout) # let diff deal with it
741 741
742 742 if lcmd:
743 743 # add on last return code
744 744 ret = int(lcmd.split()[1])
745 745 if ret != 0:
746 746 postout.append(" [%s]\n" % ret)
747 747 if pos in after:
748 748 # merge in non-active test bits
749 749 postout += after.pop(pos)
750 750 pos = int(lcmd.split()[0])
751 751
752 752 if pos in after:
753 753 postout += after.pop(pos)
754 754
755 755 return exitcode, postout
756 756
757 757 wifexited = getattr(os, "WIFEXITED", lambda x: False)
758 758 def run(cmd, wd, options, replacements):
759 759 """Run command in a sub-process, capturing the output (stdout and stderr).
760 760 Return a tuple (exitcode, output). output is None in debug mode."""
761 761 # TODO: Use subprocess.Popen if we're running on Python 2.4
762 762 if options.debug:
763 763 proc = subprocess.Popen(cmd, shell=True, cwd=wd)
764 764 ret = proc.wait()
765 765 return (ret, None)
766 766
767 767 proc = Popen4(cmd, wd, options.timeout)
768 768 def cleanup():
769 769 terminate(proc)
770 770 ret = proc.wait()
771 771 if ret == 0:
772 772 ret = signal.SIGTERM << 8
773 773 killdaemons()
774 774 return ret
775 775
776 776 output = ''
777 777 proc.tochild.close()
778 778
779 779 try:
780 780 output = proc.fromchild.read()
781 781 except KeyboardInterrupt:
782 782 vlog('# Handling keyboard interrupt')
783 783 cleanup()
784 784 raise
785 785
786 786 ret = proc.wait()
787 787 if wifexited(ret):
788 788 ret = os.WEXITSTATUS(ret)
789 789
790 790 if proc.timeout:
791 791 ret = 'timeout'
792 792
793 793 if ret:
794 794 killdaemons()
795 795
796 796 for s, r in replacements:
797 797 output = re.sub(s, r, output)
798 798 return ret, output.splitlines(True)
799 799
800 800 def runone(options, test):
801 801 '''tristate output:
802 802 None -> skipped
803 803 True -> passed
804 804 False -> failed'''
805 805
806 806 global results, resultslock, iolock
807 807
808 808 testpath = os.path.join(TESTDIR, test)
809 809
810 810 def result(l, e):
811 811 resultslock.acquire()
812 812 results[l].append(e)
813 813 resultslock.release()
814 814
815 815 def skip(msg):
816 816 if not options.verbose:
817 817 result('s', (test, msg))
818 818 else:
819 819 iolock.acquire()
820 820 print "\nSkipping %s: %s" % (testpath, msg)
821 821 iolock.release()
822 822 return None
823 823
824 824 def fail(msg, ret):
825 825 if not options.nodiff:
826 826 iolock.acquire()
827 827 print "\nERROR: %s %s" % (testpath, msg)
828 828 iolock.release()
829 829 if (not ret and options.interactive
830 830 and os.path.exists(testpath + ".err")):
831 831 iolock.acquire()
832 832 print "Accept this change? [n] ",
833 833 answer = sys.stdin.readline().strip()
834 834 iolock.release()
835 835 if answer.lower() in "y yes".split():
836 836 if test.endswith(".t"):
837 837 rename(testpath + ".err", testpath)
838 838 else:
839 839 rename(testpath + ".err", testpath + ".out")
840 840 result('p', test)
841 841 return
842 842 result('f', (test, msg))
843 843
844 844 def success():
845 845 result('p', test)
846 846
847 847 def ignore(msg):
848 848 result('i', (test, msg))
849 849
850 850 if (os.path.basename(test).startswith("test-") and '~' not in test and
851 851 ('.' not in test or test.endswith('.py') or
852 852 test.endswith('.bat') or test.endswith('.t'))):
853 853 if not os.path.exists(test):
854 854 skip("doesn't exist")
855 855 return None
856 856 else:
857 857 vlog('# Test file', test, 'not supported, ignoring')
858 858 return None # not a supported test, don't record
859 859
860 860 if not (options.whitelisted and test in options.whitelisted):
861 861 if options.blacklist and test in options.blacklist:
862 862 skip("blacklisted")
863 863 return None
864 864
865 865 if options.retest and not os.path.exists(test + ".err"):
866 866 ignore("not retesting")
867 867 return None
868 868
869 869 if options.keywords:
870 870 fp = open(test)
871 871 t = fp.read().lower() + test.lower()
872 872 fp.close()
873 873 for k in options.keywords.lower().split():
874 874 if k in t:
875 875 break
876 876 else:
877 877 ignore("doesn't match keyword")
878 878 return None
879 879
880 880 vlog("# Test", test)
881 881
882 882 # create a fresh hgrc
883 883 hgrc = open(HGRCPATH, 'w+')
884 884 hgrc.write('[ui]\n')
885 885 hgrc.write('slash = True\n')
886 886 hgrc.write('interactive = False\n')
887 887 hgrc.write('[defaults]\n')
888 888 hgrc.write('backout = -d "0 0"\n')
889 889 hgrc.write('commit = -d "0 0"\n')
890 890 hgrc.write('tag = -d "0 0"\n')
891 891 if options.inotify:
892 892 hgrc.write('[extensions]\n')
893 893 hgrc.write('inotify=\n')
894 894 hgrc.write('[inotify]\n')
895 895 hgrc.write('pidfile=%s\n' % DAEMON_PIDS)
896 896 hgrc.write('appendpid=True\n')
897 897 if options.extra_config_opt:
898 898 for opt in options.extra_config_opt:
899 899 section, key = opt.split('.', 1)
900 900 assert '=' in key, ('extra config opt %s must '
901 901 'have an = for assignment' % opt)
902 902 hgrc.write('[%s]\n%s\n' % (section, key))
903 903 hgrc.close()
904 904
905 905 ref = os.path.join(TESTDIR, test+".out")
906 906 err = os.path.join(TESTDIR, test+".err")
907 907 if os.path.exists(err):
908 908 os.remove(err) # Remove any previous output files
909 909 try:
910 910 tf = open(testpath)
911 911 firstline = tf.readline().rstrip()
912 912 tf.close()
913 913 except IOError:
914 914 firstline = ''
915 915 lctest = test.lower()
916 916
917 917 if lctest.endswith('.py') or firstline == '#!/usr/bin/env python':
918 918 runner = pytest
919 919 elif lctest.endswith('.t'):
920 920 runner = tsttest
921 921 ref = testpath
922 922 else:
923 923 return skip("unknown test type")
924 924
925 925 # Make a tmp subdirectory to work in
926 926 testtmp = os.environ["TESTTMP"] = os.environ["HOME"] = \
927 927 os.path.join(HGTMP, os.path.basename(test))
928 928
929 929 replacements = [
930 930 (r':%s\b' % options.port, ':$HGPORT'),
931 931 (r':%s\b' % (options.port + 1), ':$HGPORT1'),
932 932 (r':%s\b' % (options.port + 2), ':$HGPORT2'),
933 933 ]
934 934 if os.name == 'nt':
935 935 replacements.append(
936 936 (''.join(c.isalpha() and '[%s%s]' % (c.lower(), c.upper()) or
937 937 c in '/\\' and r'[/\\]' or
938 938 c.isdigit() and c or
939 939 '\\' + c
940 940 for c in testtmp), '$TESTTMP'))
941 941 else:
942 942 replacements.append((re.escape(testtmp), '$TESTTMP'))
943 943
944 944 os.mkdir(testtmp)
945 945 if options.time:
946 946 starttime = time.time()
947 947 ret, out = runner(testpath, testtmp, options, replacements)
948 948 if options.time:
949 949 endtime = time.time()
950 950 times.append((test, endtime - starttime))
951 951 vlog("# Ret was:", ret)
952 952
953 953 killdaemons()
954 954
955 955 mark = '.'
956 956
957 957 skipped = (ret == SKIPPED_STATUS)
958 958
959 959 # If we're not in --debug mode and reference output file exists,
960 960 # check test output against it.
961 961 if options.debug:
962 962 refout = None # to match "out is None"
963 963 elif os.path.exists(ref):
964 964 f = open(ref, "r")
965 965 refout = f.read().splitlines(True)
966 966 f.close()
967 967 else:
968 968 refout = []
969 969
970 970 if (ret != 0 or out != refout) and not skipped and not options.debug:
971 971 # Save errors to a file for diagnosis
972 972 f = open(err, "wb")
973 973 for line in out:
974 974 f.write(line)
975 975 f.close()
976 976
977 977 def describe(ret):
978 978 if ret < 0:
979 979 return 'killed by signal %d' % -ret
980 980 return 'returned error code %d' % ret
981 981
982 982 if skipped:
983 983 mark = 's'
984 984 if out is None: # debug mode: nothing to parse
985 985 missing = ['unknown']
986 986 failed = None
987 987 else:
988 988 missing, failed = parsehghaveoutput(out)
989 989 if not missing:
990 990 missing = ['irrelevant']
991 991 if failed:
992 992 fail("hghave failed checking for %s" % failed[-1], ret)
993 993 skipped = False
994 994 else:
995 995 skip(missing[-1])
996 996 elif ret == 'timeout':
997 997 mark = 't'
998 998 fail("timed out", ret)
999 999 elif out != refout:
1000 1000 mark = '!'
1001 1001 if not options.nodiff:
1002 1002 iolock.acquire()
1003 1003 if options.view:
1004 1004 os.system("%s %s %s" % (options.view, ref, err))
1005 1005 else:
1006 1006 showdiff(refout, out, ref, err)
1007 1007 iolock.release()
1008 1008 if ret:
1009 1009 fail("output changed and " + describe(ret), ret)
1010 1010 else:
1011 1011 fail("output changed", ret)
1012 1012 ret = 1
1013 1013 elif ret:
1014 1014 mark = '!'
1015 1015 fail(describe(ret), ret)
1016 1016 else:
1017 1017 success()
1018 1018
1019 1019 if not options.verbose:
1020 1020 iolock.acquire()
1021 1021 sys.stdout.write(mark)
1022 1022 sys.stdout.flush()
1023 1023 iolock.release()
1024 1024
1025 1025 if not options.keep_tmpdir:
1026 1026 shutil.rmtree(testtmp, True)
1027 1027 if skipped:
1028 1028 return None
1029 1029 return ret == 0
1030 1030
1031 1031 _hgpath = None
1032 1032
1033 1033 def _gethgpath():
1034 1034 """Return the path to the mercurial package that is actually found by
1035 1035 the current Python interpreter."""
1036 1036 global _hgpath
1037 1037 if _hgpath is not None:
1038 1038 return _hgpath
1039 1039
1040 1040 cmd = '%s -c "import mercurial; print mercurial.__path__[0]"'
1041 1041 pipe = os.popen(cmd % PYTHON)
1042 1042 try:
1043 1043 _hgpath = pipe.read().strip()
1044 1044 finally:
1045 1045 pipe.close()
1046 1046 return _hgpath
1047 1047
1048 1048 def _checkhglib(verb):
1049 1049 """Ensure that the 'mercurial' package imported by python is
1050 1050 the one we expect it to be. If not, print a warning to stderr."""
1051 1051 expecthg = os.path.join(PYTHONDIR, 'mercurial')
1052 1052 actualhg = _gethgpath()
1053 1053 if os.path.abspath(actualhg) != os.path.abspath(expecthg):
1054 1054 sys.stderr.write('warning: %s with unexpected mercurial lib: %s\n'
1055 1055 ' (expected %s)\n'
1056 1056 % (verb, actualhg, expecthg))
1057 1057
1058 1058 def runchildren(options, tests):
1059 1059 if INST:
1060 1060 installhg(options)
1061 1061 _checkhglib("Testing")
1062 1062 else:
1063 1063 usecorrectpython()
1064 1064
1065 1065 optcopy = dict(options.__dict__)
1066 1066 optcopy['jobs'] = 1
1067 1067
1068 1068 # Because whitelist has to override keyword matches, we have to
1069 1069 # actually load the whitelist in the children as well, so we allow
1070 1070 # the list of whitelist files to pass through and be parsed in the
1071 1071 # children, but not the dict of whitelisted tests resulting from
1072 1072 # the parse, used here to override blacklisted tests.
1073 1073 whitelist = optcopy['whitelisted'] or []
1074 1074 del optcopy['whitelisted']
1075 1075
1076 1076 blacklist = optcopy['blacklist'] or []
1077 1077 del optcopy['blacklist']
1078 1078 blacklisted = []
1079 1079
1080 1080 if optcopy['with_hg'] is None:
1081 1081 optcopy['with_hg'] = os.path.join(BINDIR, "hg")
1082 1082 optcopy.pop('anycoverage', None)
1083 1083
1084 1084 opts = []
1085 1085 for opt, value in optcopy.iteritems():
1086 1086 name = '--' + opt.replace('_', '-')
1087 1087 if value is True:
1088 1088 opts.append(name)
1089 1089 elif isinstance(value, list):
1090 1090 for v in value:
1091 1091 opts.append(name + '=' + str(v))
1092 1092 elif value is not None:
1093 1093 opts.append(name + '=' + str(value))
1094 1094
1095 1095 tests.reverse()
1096 1096 jobs = [[] for j in xrange(options.jobs)]
1097 1097 while tests:
1098 1098 for job in jobs:
1099 1099 if not tests:
1100 1100 break
1101 1101 test = tests.pop()
1102 1102 if test not in whitelist and test in blacklist:
1103 1103 blacklisted.append(test)
1104 1104 else:
1105 1105 job.append(test)
1106 1106
1107 1107 waitq = queue.Queue()
1108 1108
1109 1109 # windows lacks os.wait, so we must emulate it
1110 1110 def waitfor(proc, rfd):
1111 1111 fp = os.fdopen(rfd, 'rb')
1112 1112 return lambda: waitq.put((proc.pid, proc.wait(), fp))
1113 1113
1114 1114 for j, job in enumerate(jobs):
1115 1115 if not job:
1116 1116 continue
1117 1117 rfd, wfd = os.pipe()
1118 1118 childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)]
1119 1119 childtmp = os.path.join(HGTMP, 'child%d' % j)
1120 1120 childopts += ['--tmpdir', childtmp]
1121 1121 cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job
1122 1122 vlog(' '.join(cmdline))
1123 1123 proc = subprocess.Popen(cmdline, executable=cmdline[0])
1124 1124 threading.Thread(target=waitfor(proc, rfd)).start()
1125 1125 os.close(wfd)
1126 1126 signal.signal(signal.SIGINT, signal.SIG_IGN)
1127 1127 failures = 0
1128 1128 passed, skipped, failed = 0, 0, 0
1129 1129 skips = []
1130 1130 fails = []
1131 1131 for job in jobs:
1132 1132 if not job:
1133 1133 continue
1134 1134 pid, status, fp = waitq.get()
1135 1135 try:
1136 1136 childresults = pickle.load(fp)
1137 1137 except (pickle.UnpicklingError, EOFError):
1138 1138 sys.exit(255)
1139 1139 else:
1140 1140 passed += len(childresults['p'])
1141 1141 skipped += len(childresults['s'])
1142 1142 failed += len(childresults['f'])
1143 1143 skips.extend(childresults['s'])
1144 1144 fails.extend(childresults['f'])
1145 1145 if options.time:
1146 1146 childtimes = pickle.load(fp)
1147 1147 times.extend(childtimes)
1148 1148
1149 1149 vlog('pid %d exited, status %d' % (pid, status))
1150 1150 failures |= status
1151 1151 print
1152 1152 skipped += len(blacklisted)
1153 1153 if not options.noskips:
1154 1154 for s in skips:
1155 1155 print "Skipped %s: %s" % (s[0], s[1])
1156 1156 for s in blacklisted:
1157 1157 print "Skipped %s: blacklisted" % s
1158 1158 for s in fails:
1159 1159 print "Failed %s: %s" % (s[0], s[1])
1160 1160
1161 1161 _checkhglib("Tested")
1162 1162 print "# Ran %d tests, %d skipped, %d failed." % (
1163 1163 passed + failed, skipped, failed)
1164 1164
1165 1165 if options.time:
1166 1166 outputtimes(options)
1167 1167 if options.anycoverage:
1168 1168 outputcoverage(options)
1169 1169 sys.exit(failures != 0)
1170 1170
1171 1171 results = dict(p=[], f=[], s=[], i=[])
1172 1172 resultslock = threading.Lock()
1173 1173 times = []
1174 1174 iolock = threading.Lock()
1175 1175
1176 1176 def runqueue(options, tests):
1177 1177 for test in tests:
1178 1178 ret = runone(options, test)
1179 1179 if options.first and ret is not None and not ret:
1180 1180 break
1181 1181
1182 1182 def runtests(options, tests):
1183 1183 global DAEMON_PIDS, HGRCPATH
1184 1184 DAEMON_PIDS = os.environ["DAEMON_PIDS"] = os.path.join(HGTMP, 'daemon.pids')
1185 1185 HGRCPATH = os.environ["HGRCPATH"] = os.path.join(HGTMP, '.hgrc')
1186 1186
1187 1187 try:
1188 1188 if INST:
1189 1189 installhg(options)
1190 1190 _checkhglib("Testing")
1191 1191 else:
1192 1192 usecorrectpython()
1193 1193
1194 1194 if options.restart:
1195 1195 orig = list(tests)
1196 1196 while tests:
1197 1197 if os.path.exists(tests[0] + ".err"):
1198 1198 break
1199 1199 tests.pop(0)
1200 1200 if not tests:
1201 1201 print "running all tests"
1202 1202 tests = orig
1203 1203
1204 1204 runqueue(options, tests)
1205 1205
1206 1206 failed = len(results['f'])
1207 1207 tested = len(results['p']) + failed
1208 1208 skipped = len(results['s'])
1209 1209 ignored = len(results['i'])
1210 1210
1211 1211 if options.child:
1212 1212 fp = os.fdopen(options.child, 'wb')
1213 1213 pickle.dump(results, fp, pickle.HIGHEST_PROTOCOL)
1214 1214 if options.time:
1215 1215 pickle.dump(times, fp, pickle.HIGHEST_PROTOCOL)
1216 1216 fp.close()
1217 1217 else:
1218 1218 print
1219 1219 for s in results['s']:
1220 1220 print "Skipped %s: %s" % s
1221 1221 for s in results['f']:
1222 1222 print "Failed %s: %s" % s
1223 1223 _checkhglib("Tested")
1224 1224 print "# Ran %d tests, %d skipped, %d failed." % (
1225 1225 tested, skipped + ignored, failed)
1226 1226 if options.time:
1227 1227 outputtimes(options)
1228 1228
1229 1229 if options.anycoverage:
1230 1230 outputcoverage(options)
1231 1231 except KeyboardInterrupt:
1232 1232 failed = True
1233 1233 if not options.child:
1234 1234 print "\ninterrupted!"
1235 1235
1236 1236 if failed:
1237 1237 sys.exit(1)
1238 1238
1239 1239 def main():
1240 1240 (options, args) = parseargs()
1241 1241 if not options.child:
1242 1242 os.umask(022)
1243 1243
1244 1244 checktools()
1245 1245
1246 1246 if len(args) == 0:
1247 args = os.listdir(".")
1248 args.sort()
1247 args = sorted(os.listdir("."))
1249 1248
1250 1249 tests = args
1251 1250
1252 1251 # Reset some environment variables to well-known values so that
1253 1252 # the tests produce repeatable output.
1254 1253 os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C'
1255 1254 os.environ['TZ'] = 'GMT'
1256 1255 os.environ["EMAIL"] = "Foo Bar <foo.bar@example.com>"
1257 1256 os.environ['CDPATH'] = ''
1258 1257 os.environ['COLUMNS'] = '80'
1259 1258 os.environ['GREP_OPTIONS'] = ''
1260 1259 os.environ['http_proxy'] = ''
1261 1260 os.environ['no_proxy'] = ''
1262 1261 os.environ['NO_PROXY'] = ''
1263 1262 os.environ['TERM'] = 'xterm'
1264 1263 if 'PYTHONHASHSEED' not in os.environ:
1265 1264 # use a random python hash seed all the time
1266 1265 # we do the randomness ourself to know what seed is used
1267 1266 os.environ['PYTHONHASHSEED'] = str(random.getrandbits(32))
1268 1267 print 'python hash seed:', os.environ['PYTHONHASHSEED']
1269 1268
1270 1269 # unset env related to hooks
1271 1270 for k in os.environ.keys():
1272 1271 if k.startswith('HG_'):
1273 1272 # can't remove on solaris
1274 1273 os.environ[k] = ''
1275 1274 del os.environ[k]
1276 1275 if 'HG' in os.environ:
1277 1276 # can't remove on solaris
1278 1277 os.environ['HG'] = ''
1279 1278 del os.environ['HG']
1280 1279 if 'HGPROF' in os.environ:
1281 1280 os.environ['HGPROF'] = ''
1282 1281 del os.environ['HGPROF']
1283 1282
1284 1283 global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE
1285 1284 TESTDIR = os.environ["TESTDIR"] = os.getcwd()
1286 1285 if options.tmpdir:
1287 1286 options.keep_tmpdir = True
1288 1287 tmpdir = options.tmpdir
1289 1288 if os.path.exists(tmpdir):
1290 1289 # Meaning of tmpdir has changed since 1.3: we used to create
1291 1290 # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if
1292 1291 # tmpdir already exists.
1293 1292 sys.exit("error: temp dir %r already exists" % tmpdir)
1294 1293
1295 1294 # Automatically removing tmpdir sounds convenient, but could
1296 1295 # really annoy anyone in the habit of using "--tmpdir=/tmp"
1297 1296 # or "--tmpdir=$HOME".
1298 1297 #vlog("# Removing temp dir", tmpdir)
1299 1298 #shutil.rmtree(tmpdir)
1300 1299 os.makedirs(tmpdir)
1301 1300 else:
1302 1301 d = None
1303 1302 if os.name == 'nt':
1304 1303 # without this, we get the default temp dir location, but
1305 1304 # in all lowercase, which causes troubles with paths (issue3490)
1306 1305 d = os.getenv('TMP')
1307 1306 tmpdir = tempfile.mkdtemp('', 'hgtests.', d)
1308 1307 HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir)
1309 1308 DAEMON_PIDS = None
1310 1309 HGRCPATH = None
1311 1310
1312 1311 os.environ["HGEDITOR"] = sys.executable + ' -c "import sys; sys.exit(0)"'
1313 1312 os.environ["HGMERGE"] = "internal:merge"
1314 1313 os.environ["HGUSER"] = "test"
1315 1314 os.environ["HGENCODING"] = "ascii"
1316 1315 os.environ["HGENCODINGMODE"] = "strict"
1317 1316 os.environ["HGPORT"] = str(options.port)
1318 1317 os.environ["HGPORT1"] = str(options.port + 1)
1319 1318 os.environ["HGPORT2"] = str(options.port + 2)
1320 1319
1321 1320 if options.with_hg:
1322 1321 INST = None
1323 1322 BINDIR = os.path.dirname(os.path.realpath(options.with_hg))
1324 1323
1325 1324 # This looks redundant with how Python initializes sys.path from
1326 1325 # the location of the script being executed. Needed because the
1327 1326 # "hg" specified by --with-hg is not the only Python script
1328 1327 # executed in the test suite that needs to import 'mercurial'
1329 1328 # ... which means it's not really redundant at all.
1330 1329 PYTHONDIR = BINDIR
1331 1330 else:
1332 1331 INST = os.path.join(HGTMP, "install")
1333 1332 BINDIR = os.environ["BINDIR"] = os.path.join(INST, "bin")
1334 1333 PYTHONDIR = os.path.join(INST, "lib", "python")
1335 1334
1336 1335 os.environ["BINDIR"] = BINDIR
1337 1336 os.environ["PYTHON"] = PYTHON
1338 1337
1339 1338 if not options.child:
1340 1339 path = [BINDIR] + os.environ["PATH"].split(os.pathsep)
1341 1340 os.environ["PATH"] = os.pathsep.join(path)
1342 1341
1343 1342 # Include TESTDIR in PYTHONPATH so that out-of-tree extensions
1344 1343 # can run .../tests/run-tests.py test-foo where test-foo
1345 1344 # adds an extension to HGRC
1346 1345 pypath = [PYTHONDIR, TESTDIR]
1347 1346 # We have to augment PYTHONPATH, rather than simply replacing
1348 1347 # it, in case external libraries are only available via current
1349 1348 # PYTHONPATH. (In particular, the Subversion bindings on OS X
1350 1349 # are in /opt/subversion.)
1351 1350 oldpypath = os.environ.get(IMPL_PATH)
1352 1351 if oldpypath:
1353 1352 pypath.append(oldpypath)
1354 1353 os.environ[IMPL_PATH] = os.pathsep.join(pypath)
1355 1354
1356 1355 COVERAGE_FILE = os.path.join(TESTDIR, ".coverage")
1357 1356
1358 1357 vlog("# Using TESTDIR", TESTDIR)
1359 1358 vlog("# Using HGTMP", HGTMP)
1360 1359 vlog("# Using PATH", os.environ["PATH"])
1361 1360 vlog("# Using", IMPL_PATH, os.environ[IMPL_PATH])
1362 1361
1363 1362 try:
1364 1363 if len(tests) > 1 and options.jobs > 1:
1365 1364 runchildren(options, tests)
1366 1365 else:
1367 1366 runtests(options, tests)
1368 1367 finally:
1369 1368 time.sleep(.1)
1370 1369 cleanup(options)
1371 1370
1372 1371 if __name__ == '__main__':
1373 1372 main()
General Comments 0
You need to be logged in to leave comments. Login now