diff -r 06ccaeb6cfc1 -r 629956d13cbc tests/run-tests.py --- a/tests/run-tests.py Fri Nov 27 18:26:25 2009 +0000 +++ b/tests/run-tests.py Thu Dec 24 10:15:49 2009 +0000 @@ -16,7 +16,7 @@ # If you change this script, it is recommended that you ensure you # haven't broken it by running it in various modes with a representative # sample of test scripts. For example: -# +# # 1) serial, no coverage, temp install: # ./run-tests.py test-s* # 2) serial, no coverage, local hg: @@ -31,14 +31,17 @@ # ./run-tests.py -j2 --local test-s* # 7) parallel, coverage, temp install: # ./run-tests.py -j2 -c test-s* # currently broken -# 8) parallel, coverage, local install +# 8) parallel, coverage, local install: # ./run-tests.py -j2 -c --local test-s* # unsupported (and broken) +# 9) parallel, custom tmp dir: +# ./run-tests.py -j2 --tmpdir /tmp/myhgtests # # (You could use any subset of the tests: test-s* happens to match # enough that it's worth doing parallel runs, few enough that it # completes fairly quickly, includes both shell and Python scripts, and # includes some scripts that run daemon processes.) +from ConfigParser import ConfigParser import difflib import errno import optparse @@ -90,9 +93,16 @@ parser.add_option("-j", "--jobs", type="int", help="number of jobs to run in parallel" " (default: $%s or %d)" % defaults['jobs']) + parser.add_option("-k", "--keywords", + help="run tests matching keywords") parser.add_option("--keep-tmpdir", action="store_true", - help="keep temporary directory after running tests" - " (best used with --tmpdir)") + help="keep temporary directory after running tests") + parser.add_option("--tmpdir", type="string", + help="run tests in the given temporary directory" + " (implies --keep-tmpdir)") + parser.add_option("-d", "--debug", action="store_true", + help="debug mode: write output of test scripts to console" + " rather than capturing and diff'ing it (disables timeout)") parser.add_option("-R", "--restart", action="store_true", help="restart at last error") parser.add_option("-p", "--port", type="int", @@ -102,11 +112,11 @@ help="retest failed tests") parser.add_option("-s", "--cover_stdlib", action="store_true", help="print a test coverage report inc. standard libraries") + parser.add_option("-S", "--noskips", action="store_true", + help="don't report skip tests verbosely") parser.add_option("-t", "--timeout", type="int", help="kill errant tests after TIMEOUT seconds" " (default: $%s or %d)" % defaults['timeout']) - parser.add_option("--tmpdir", type="string", - help="run tests in the given temporary directory") parser.add_option("-v", "--verbose", action="store_true", help="output verbose messages") parser.add_option("-n", "--nodiff", action="store_true", @@ -119,6 +129,13 @@ help="shortcut for --with-hg=/../hg") parser.add_option("--pure", action="store_true", help="use pure Python code instead of C extensions") + parser.add_option("-3", "--py3k-warnings", action="store_true", + help="enable Py3k warnings on Python 2.6+") + parser.add_option("--inotify", action="store_true", + help="enable inotify extension when running tests") + parser.add_option("--blacklist", action="append", + help="skip tests listed in the specified section of " + "the blacklist file") for option, default in defaults.items(): defaults[option] = int(os.environ.get(*default)) @@ -162,15 +179,36 @@ for m in msg: print m, print + sys.stdout.flush() else: vlog = lambda *msg: None + if options.tmpdir: + options.tmpdir = os.path.expanduser(options.tmpdir) + if options.jobs < 1: - print >> sys.stderr, 'ERROR: -j/--jobs must be positive' - sys.exit(1) + parser.error('--jobs must be positive') if options.interactive and options.jobs > 1: print '(--interactive overrides --jobs)' options.jobs = 1 + if options.interactive and options.debug: + parser.error("-i/--interactive and -d/--debug are incompatible") + if options.debug: + if options.timeout != defaults['timeout']: + sys.stderr.write( + 'warning: --timeout option ignored with --debug\n') + options.timeout = 0 + if options.py3k_warnings: + if sys.version_info[:2] < (2, 6) or sys.version_info[:2] >= (3, 0): + parser.error('--py3k-warnings can only be used on Python 2.6+') + if options.blacklist: + configparser = ConfigParser() + configparser.read("blacklist") + blacklist = dict() + for section in options.blacklist: + for (item, value) in configparser.items(section): + blacklist["test-" + item] = section + options.blacklist = blacklist return (options, args) @@ -213,9 +251,8 @@ return missing, failed -def showdiff(expected, output): - for line in difflib.unified_diff(expected, output, - "Expected output", "Test output"): +def showdiff(expected, output, ref, err): + for line in difflib.unified_diff(expected, output, ref, err): sys.stdout.write(line) def findprogram(program): @@ -266,11 +303,21 @@ pure = options.pure and "--pure" or "" # Run installer in hg root - os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '..')) + script = os.path.realpath(sys.argv[0]) + hgroot = os.path.dirname(os.path.dirname(script)) + os.chdir(hgroot) + nohome = '--home=""' + if os.name == 'nt': + # The --home="" trick works only on OS where os.sep == '/' + # because of a distutils convert_path() fast-path. Avoid it at + # least on Windows for now, deal with .pydistutils.cfg bugs + # when they happen. + nohome = '' cmd = ('%s setup.py %s clean --all' ' install --force --prefix="%s" --install-lib="%s"' - ' --install-scripts="%s" >%s 2>&1' - % (sys.executable, pure, INST, PYTHONDIR, BINDIR, installerrs)) + ' --install-scripts="%s" %s >%s 2>&1' + % (sys.executable, pure, INST, PYTHONDIR, BINDIR, nohome, + installerrs)) vlog("# Running", cmd) if os.system(cmd) == 0: if not options.verbose: @@ -297,6 +344,17 @@ f.close() os.chmod(os.path.join(BINDIR, 'diffstat'), 0700) + if options.py3k_warnings and not options.anycoverage: + vlog("# Updating hg command to enable Py3k Warnings switch") + f = open(os.path.join(BINDIR, 'hg'), 'r') + lines = [line.rstrip() for line in f] + lines[0] += ' -3' + f.close() + f = open(os.path.join(BINDIR, 'hg'), 'w') + for line in lines: + f.write(line + '\n') + f.close() + if options.anycoverage: vlog("# Installing coverage wrapper") os.environ['COVERAGE_FILE'] = COVERAGE_FILE @@ -350,8 +408,13 @@ def run(cmd, options): """Run command in a sub-process, capturing the output (stdout and stderr). - Return the exist code, and output.""" + Return a tuple (exitcode, output). output is None in debug mode.""" # TODO: Use subprocess.Popen if we're running on Python 2.4 + if options.debug: + proc = subprocess.Popen(cmd, shell=True) + ret = proc.wait() + return (ret, None) + if os.name == 'nt' or sys.platform.startswith('java'): tochild, fromchild = os.popen4(cmd) tochild.close() @@ -388,25 +451,31 @@ if not options.verbose: skips.append((test, msg)) else: - print "\nSkipping %s: %s" % (test, msg) + print "\nSkipping %s: %s" % (testpath, msg) return None def fail(msg): fails.append((test, msg)) if not options.nodiff: - print "\nERROR: %s %s" % (test, msg) + print "\nERROR: %s %s" % (testpath, msg) return None vlog("# Test", test) # create a fresh hgrc - hgrc = file(HGRCPATH, 'w+') + hgrc = open(HGRCPATH, 'w+') hgrc.write('[ui]\n') hgrc.write('slash = True\n') hgrc.write('[defaults]\n') hgrc.write('backout = -d "0 0"\n') hgrc.write('commit = -d "0 0"\n') hgrc.write('tag = -d "0 0"\n') + if options.inotify: + hgrc.write('[extensions]\n') + hgrc.write('inotify=\n') + hgrc.write('[inotify]\n') + hgrc.write('pidfile=%s\n' % DAEMON_PIDS) + hgrc.write('appendpid=True\n') hgrc.close() err = os.path.join(TESTDIR, test+".err") @@ -430,7 +499,8 @@ lctest = test.lower() if lctest.endswith('.py') or firstline == '#!/usr/bin/env python': - cmd = '%s "%s"' % (PYTHON, testpath) + py3kswitch = options.py3k_warnings and ' -3' or '' + cmd = '%s%s "%s"' % (PYTHON, py3kswitch, testpath) elif lctest.endswith('.bat'): # do not run batch scripts on non-windows if os.name != 'nt': @@ -462,16 +532,24 @@ mark = '.' skipped = (ret == SKIPPED_STATUS) - # If reference output file exists, check test output against it - if os.path.exists(ref): + # If we're not in --debug mode and reference output file exists, + # check test output against it. + if options.debug: + refout = None # to match out == None + elif os.path.exists(ref): f = open(ref, "r") refout = splitnewlines(f.read()) f.close() else: refout = [] + if skipped: mark = 's' - missing, failed = parsehghaveoutput(out) + if out is None: # debug mode: nothing to parse + missing = ['unknown'] + failed = None + else: + missing, failed = parsehghaveoutput(out) if not missing: missing = ['irrelevant'] if failed: @@ -486,7 +564,7 @@ else: fail("output changed") if not options.nodiff: - showdiff(refout, out) + showdiff(refout, out, ref, err) ret = 1 elif ret: mark = '!' @@ -496,7 +574,7 @@ sys.stdout.write(mark) sys.stdout.flush() - if ret != 0 and not skipped: + if ret != 0 and not skipped and not options.debug: # Save errors to a file for diagnosis f = open(err, "wb") for line in out: @@ -505,7 +583,7 @@ # Kill off any leftover daemon processes try: - fp = file(DAEMON_PIDS) + fp = open(DAEMON_PIDS) for line in fp: try: pid = int(line) @@ -590,6 +668,8 @@ continue rfd, wfd = os.pipe() childopts = ['--child=%d' % wfd, '--port=%d' % (options.port + j * 3)] + childtmp = os.path.join(HGTMP, 'child%d' % j) + childopts += ['--tmpdir', childtmp] cmdline = [PYTHON, sys.argv[0]] + opts + childopts + job vlog(' '.join(cmdline)) fps[os.spawnvp(os.P_NOWAIT, cmdline[0], cmdline)] = os.fdopen(rfd, 'r') @@ -614,8 +694,9 @@ vlog('pid %d exited, status %d' % (pid, status)) failures |= status print - for s in skips: - print "Skipped %s: %s" % (s[0], s[1]) + if not options.noskips: + for s in skips: + print "Skipped %s: %s" % (s[0], s[1]) for s in fails: print "Failed %s: %s" % (s[0], s[1]) @@ -659,10 +740,28 @@ skips = [] fails = [] + for test in tests: + if options.blacklist: + section = options.blacklist.get(test) + if section is not None: + skips.append((test, "blacklisted (%s section)" % section)) + skipped += 1 + continue + if options.retest and not os.path.exists(test + ".err"): skipped += 1 continue + + if options.keywords: + t = open(test).read().lower() + test.lower() + for k in options.keywords.lower().split(): + if k in t: + break + else: + skipped +=1 + continue + ret = runone(options, test, skips, fails) if ret is None: skipped += 1 @@ -716,15 +815,32 @@ # Reset some environment variables to well-known values so that # the tests produce repeatable output. - os.environ['LANG'] = os.environ['LC_ALL'] = 'C' + os.environ['LANG'] = os.environ['LC_ALL'] = os.environ['LANGUAGE'] = 'C' os.environ['TZ'] = 'GMT' os.environ["EMAIL"] = "Foo Bar " os.environ['CDPATH'] = '' + os.environ['COLUMNS'] = '80' global TESTDIR, HGTMP, INST, BINDIR, PYTHONDIR, COVERAGE_FILE TESTDIR = os.environ["TESTDIR"] = os.getcwd() - HGTMP = os.environ['HGTMP'] = os.path.realpath(tempfile.mkdtemp('', 'hgtests.', - options.tmpdir)) + if options.tmpdir: + options.keep_tmpdir = True + tmpdir = options.tmpdir + if os.path.exists(tmpdir): + # Meaning of tmpdir has changed since 1.3: we used to create + # HGTMP inside tmpdir; now HGTMP is tmpdir. So fail if + # tmpdir already exists. + sys.exit("error: temp dir %r already exists" % tmpdir) + + # Automatically removing tmpdir sounds convenient, but could + # really annoy anyone in the habit of using "--tmpdir=/tmp" + # or "--tmpdir=$HOME". + #vlog("# Removing temp dir", tmpdir) + #shutil.rmtree(tmpdir) + os.makedirs(tmpdir) + else: + tmpdir = tempfile.mkdtemp('', 'hgtests.') + HGTMP = os.environ['HGTMP'] = os.path.realpath(tmpdir) DAEMON_PIDS = None HGRCPATH = None