sapling/contrib/unit.py
Jun Wu 584656dff3 codemod: join the auto-formatter party
Summary:
Turned on the auto formatter. Ran `arc lint --apply-patches --take BLACK **/*.py`.
Then run `arc lint` again so some other autofixers like spellchecker etc. looked
at the code base. Manually accept the changes whenever they make sense, or use
a workaround (ex. changing "dict()" to "dict constructor") where autofix is false
positive. Disabled linters on files that are hard (i18n/polib.py) to fix, or less
interesting to fix (hgsubversion tests), or cannot be fixed without breaking
OSS build (FBPYTHON4).

Conflicted linters (test-check-module-imports.t, part of test-check-code.t,
test-check-pyflakes.t) are removed or disabled.

Duplicated linters (test-check-pyflakes.t, test-check-pylint.t) are removed.

An issue of the auto-formatter is lines are no longer guarnateed to be <= 80
chars. But that seems less important comparing with the benefit auto-formatter
provides.

As we're here, also remove test-check-py3-compat.t, as it is currently broken
if `PYTHON3=/bin/python3` is set.

Reviewed By: wez, phillco, simpkins, pkaush, singhsrb

Differential Revision: D8173629

fbshipit-source-id: 90e248ae0c5e6eaadbe25520a6ee42d32005621b
2018-05-25 22:17:29 -07:00

225 lines
6.7 KiB
Python
Executable File

#!/usr/bin/env python
"""run a subset of tests that related to the current change
Optionally write result using JSON format. The JSON format can be parsed
by MercurialTestEngine.php
"""
from __future__ import absolute_import
import json
import optparse
import os
import re
import subprocess
import sys
import utils
sys.path.insert(0, os.path.dirname(__file__))
reporoot = utils.reporoot
def info(message):
"""print message to stderr"""
sys.stderr.write(message)
def checkoutput(*args, **kwds):
"""like subprocess.checked_output, but raise RuntimeError and return
stderr as a second value.
"""
proc = subprocess.Popen(
*args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, **kwds
)
out, err = proc.communicate()
retcode = proc.poll()
if retcode:
raise RuntimeError("%r exits with %d" % (args, retcode))
return out, err
def changedfiles(rev="wdir() + ."):
"""return a list of paths (relative to repo root) that rev touches.
by default, check the working directory and its parent.
"""
cmd = ["hg", "log", "-T", '{join(files,"\\0")}\\0', "-r", rev]
out, err = checkoutput(cmd, cwd=reporoot)
return set(out.rstrip("\0").split("\0"))
def words(path):
"""strip extension and split it to words.
for example, 'a/b-c.txt' -> ['a', 'b', 'c']
"""
return re.split("[^\w]+", os.path.splitext(path)[0])
def alltests(include_checks=True):
tests = [
p
for p in os.listdir(os.path.join(reporoot, "tests"))
if p.startswith("test-") and p[-2:] in ["py", ".t"]
]
if not include_checks:
tests = [t for t in tests if not t.startswith("test-check")]
return tests
def interestingtests(changed_files, include_checks=True):
"""return a list of interesting test filenames"""
tests = [
p
for p in os.listdir(os.path.join(reporoot, "tests"))
if p.startswith("test-") and p[-2:] in ["py", ".t"]
]
result = set()
# Build a dictionary mapping words to the relevant tests
# (tests whose name contains that word)
testwords = {}
for t in tests:
# Include all tests starting with test-check*,
if include_checks and t.startswith("test-check"):
result.add(t)
continue
for word in words(t)[1:]:
test_set = testwords.setdefault(word, set())
test_set.add(t)
# Also scan test files to check if they use extensions. For example,
# pushrebase.py change should trigger test-pull-createmarkers.t since the
# latter enables pushrebase extension.
extre = re.compile("> ([^ ]+)\s*=\s*\$TESTDIR")
for t in tests:
with open(os.path.join(reporoot, "tests", t)) as f:
content = f.read()
for word in extre.findall(content):
test_set = testwords.setdefault(word, set())
test_set.add(t)
# A test is interesting if there is a common word in both the path of the
# changed source file and the name of the test file. For example:
# - test-githelp.t is interesting if githelp.py is changed
# - test-remotefilelog-sparse.t is interesting if sparse.py is changed
# - test-remotefilelog-foo.t is interesting if remotefilelog/* is changed
for path in changed_files:
if path.startswith("tests/test-"):
# for a test file, do not enable other tests but only itself
result.add(os.path.basename(path))
continue
for w in words(path):
result.update(testwords.get(w, []))
return result
def runtests(tests=None):
"""run given tests
Returns a tuple of (exitcode, report)
exitcode will be 0 on success, and non-zero on failure
report is a dictionary of test results.
"""
modcheckpath = os.path.join(reporoot, "tests", "modcheck.py")
args = ["-l", "--json", "--extra-config-opt=extensions.modcheck=%s" % modcheckpath]
if tests:
args += tests
# Run the tests.
#
# We ignore KeyboardInterrupt while running the tests: when the user hits
# Ctrl-C the interrupt will also be delivered to the test runner, which
# should cause it to exit soon. We want to wait for the test runner to
# exit before we quit. Otherwise may keep printing data even after we have
# exited and returned control of the terminal to the user's shell.
proc = utils.spawnruntests(args)
interruptcount = 0
maxinterrupts = 3
while True:
try:
exitcode = proc.wait()
break
except KeyboardInterrupt:
interruptcount += 1
if interruptcount >= maxinterrupts:
sys.stderr.write(
"Warning: test runner has not exited after "
"multiple interrupts. Giving up on it and "
"quiting anyway.\n"
)
raise
try:
reportpath = os.path.join(reporoot, "tests", "report.json")
with open(reportpath) as rf:
report_contents = rf.read()
# strip the "testreport =" header which makes the JSON illegal
report = json.loads(re.sub("^testreport =", "", report_contents))
os.unlink(reportpath)
except (EnvironmentError, ValueError) as ex:
# If anything goes wrong parsing the report.json file, build our own
# fake failure report, and make sure we have non-zero exit code.
sys.stderr.write("warning: error reading results: %s\n" % (ex,))
report = {"run-tests": {"result": "failure"}}
if exitcode == 0:
exitcode = 1
return exitcode, report
def main():
op = optparse.OptionParser()
op.add_option(
"-j",
"--json",
metavar="FILE",
help="Write a JSON result file at the specified location",
)
op.add_option("--all", action="store_true", default=False, help="Run all tests.")
op.add_option(
"--skip-checks",
action="store_true",
default=False,
help='Do not automatically include all "check" tests.',
)
opts, args = op.parse_args()
if opts.all:
tests = alltests(include_checks=not opts.skip_checks)
else:
if args:
changed_files = args
else:
changed_files = changedfiles()
tests = interestingtests(changed_files, include_checks=not opts.skip_checks)
if tests:
info(
"%d test%s to run: %s\n"
% (len(tests), ("" if len(tests) == 1 else "s"), " ".join(tests))
)
exitcode, report = runtests(tests)
else:
info("no tests to run\n")
exitcode = 0
report = {}
if opts.json:
with open(opts.json, "w") as fp:
json.dump(report, fp)
return exitcode
if __name__ == "__main__":
exitcode = main()
sys.exit(exitcode)