pytorch/tools/test/test_actions_local_runner.py
driazati a679bb5ecf Refactor local lint (#58798)
Summary:
Pull Request resolved: https://github.com/pytorch/pytorch/pull/58798

In #58623 there was a bug in `make quicklint` where ShellCheck would run on the entire repo when there were no files. This PR fixes that by refactoring out common stuff (like skipping quicklint when there are no files, let checks do their own file filtering) and pushes the logic into a runner class.

Test Plan: Imported from OSS

Reviewed By: samestep

Differential Revision: D28649889

Pulled By: driazati

fbshipit-source-id: b19f32cdb63396c806cb689b2f6daf97e1724d44
2021-05-24 13:52:53 -07:00

191 lines
7.4 KiB
Python

# -*- coding: utf-8 -*-
import textwrap
import unittest
import sys
import contextlib
import io
import os
import subprocess
import multiprocessing
from typing import List, Dict, Any
from tools import actions_local_runner
if sys.version_info >= (3, 8):
# actions_local_runner uses asyncio features not available in 3.6, and
# IsolatedAsyncioTestCase was added in 3.8, so skip testing on
# unsupported systems
class TestRunner(unittest.IsolatedAsyncioTestCase):
def run(self, *args: List[Any], **kwargs: List[Dict[str, Any]]) -> Any:
return super().run(*args, **kwargs)
def test_step_extraction(self) -> None:
fake_job = {
"steps": [
{"name": "test1", "run": "echo hi"},
{"name": "test2", "run": "echo hi"},
{"name": "test3", "run": "echo hi"},
]
}
actual = actions_local_runner.grab_specific_steps(["test2"], fake_job)
expected = [
{"name": "test2", "run": "echo hi"},
]
self.assertEqual(actual, expected)
async def test_runner(self) -> None:
fake_step = {"name": "say hello", "run": "echo hi"}
f = io.StringIO()
with contextlib.redirect_stdout(f):
await actions_local_runner.YamlStep(fake_step, "test", True).run()
result = f.getvalue()
self.assertIn("say hello", result)
class TestEndToEnd(unittest.TestCase):
expected = [
"cmakelint: Run cmakelint",
"quick-checks: Ensure no direct cub include",
"quick-checks: Ensure no unqualified type ignore",
"quick-checks: Ensure no unqualified noqa",
"quick-checks: Ensure canonical include",
"quick-checks: Ensure no non-breaking spaces",
"quick-checks: Ensure no tabs",
"flake8",
"quick-checks: Ensure correct trailing newlines",
"quick-checks: Ensure no trailing spaces",
"shellcheck: Regenerate workflows",
"shellcheck: Assert that regenerating the workflows didn't change them",
"shellcheck: Extract scripts from GitHub Actions workflows",
"shellcheck: Run ShellCheck",
]
def test_lint(self):
cmd = ["make", "lint", "-j", str(multiprocessing.cpu_count())]
proc = subprocess.run(
cmd, cwd=actions_local_runner.REPO_ROOT, stdout=subprocess.PIPE
)
stdout = proc.stdout.decode()
for line in self.expected:
self.assertIn(line, stdout)
self.assertIn("mypy", stdout)
def test_quicklint(self):
cmd = ["make", "quicklint", "-j", str(multiprocessing.cpu_count())]
proc = subprocess.run(
cmd, cwd=actions_local_runner.REPO_ROOT, stdout=subprocess.PIPE
)
stdout = proc.stdout.decode()
for line in self.expected:
self.assertIn(line, stdout)
# TODO: See https://github.com/pytorch/pytorch/issues/57967
self.assertIn("mypy (skipped typestub generation)", stdout)
class TestQuicklint(unittest.IsolatedAsyncioTestCase):
test_files = [
os.path.join("caffe2", "some_cool_file.py"),
os.path.join("torch", "some_cool_file.py"),
os.path.join("aten", "some_cool_file.py"),
os.path.join("torch", "some_stubs.pyi"),
os.path.join("test.sh"),
]
test_py_files = [
f for f in test_files if f.endswith(".py") or f.endswith(".pyi")
]
test_sh_files = [f for f in test_files if f.endswith(".sh")]
maxDiff = None
def setUp(self, *args, **kwargs):
for name in self.test_files:
bad_code = textwrap.dedent(
"""
some_variable = '2'
some_variable = None
some_variable = 11.2
"""
).rstrip("\n")
with open(name, "w") as f:
f.write(bad_code)
def tearDown(self, *args, **kwargs):
for name in self.test_files:
os.remove(name)
def test_file_selection(self):
files = actions_local_runner.find_changed_files()
for name in self.test_files:
self.assertIn(name, files)
async def test_flake8(self):
f = io.StringIO()
with contextlib.redirect_stdout(f):
await actions_local_runner.Flake8(self.test_py_files, True).run()
# Should exclude the caffe2/ file
expected = textwrap.dedent(
"""
x flake8
torch/some_cool_file.py:4:21: W292 no newline at end of file
aten/some_cool_file.py:4:21: W292 no newline at end of file
"""
).lstrip("\n")
self.assertEqual(expected, f.getvalue())
async def test_shellcheck(self):
f = io.StringIO()
with contextlib.redirect_stdout(f):
await actions_local_runner.ShellCheck(self.test_sh_files, True).run()
self.assertIn("SC2148: Tips depend on target shell", f.getvalue())
self.assertIn("SC2283: Remove spaces around = to assign", f.getvalue())
async def test_mypy(self):
self.maxDiff = None
f = io.StringIO()
with contextlib.redirect_stdout(f):
# Quicklint assumes this has been run already and doesn't work
# without it
_, _, _ = await actions_local_runner.shell_cmd(
[
f"{sys.executable}",
"tools/actions_local_runner.py",
"--job",
"mypy",
"--file",
".github/workflows/lint.yml",
"--step",
"Run autogen",
],
redirect=True,
)
await actions_local_runner.Mypy(self.test_py_files, True).run()
# Should exclude the aten/ file; also, apparently mypy
# typechecks files in reverse order
expected = textwrap.dedent(
"""
x mypy (skipped typestub generation)
torch/some_stubs.pyi:3:17: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment]
torch/some_stubs.pyi:4:17: error: Incompatible types in assignment (expression has type "float", variable has type "str") [assignment]
torch/some_cool_file.py:3:17: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment]
torch/some_cool_file.py:4:17: error: Incompatible types in assignment (expression has type "float", variable has type "str") [assignment]
caffe2/some_cool_file.py:3:17: error: Incompatible types in assignment (expression has type "None", variable has type "str") [assignment]
caffe2/some_cool_file.py:4:17: error: Incompatible types in assignment (expression has type "float", variable has type "str") [assignment]
""" # noqa: B950
).lstrip(
"\n"
)
self.assertEqual(expected, f.getvalue())
if __name__ == "__main__":
unittest.main()