blob: ba4e6fd2cdb923c024cd679db29b4c76ddddd8d0 [file] [log] [blame]
# -*- 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()