Updated script that can be controled by Nodejs web app
This commit is contained in:
@ -0,0 +1,176 @@
|
||||
import ast
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from trio._tests.pytest_plugin import skip_if_optional_else_raise
|
||||
|
||||
# imports in gen_exports that are not in `install_requires` in setup.py
|
||||
try:
|
||||
import astor # noqa: F401
|
||||
import isort # noqa: F401
|
||||
except ImportError as error:
|
||||
skip_if_optional_else_raise(error)
|
||||
|
||||
|
||||
from trio._tools.gen_exports import (
|
||||
File,
|
||||
create_passthrough_args,
|
||||
get_public_methods,
|
||||
process,
|
||||
run_linters,
|
||||
run_ruff,
|
||||
)
|
||||
|
||||
SOURCE = '''from _run import _public
|
||||
from collections import Counter
|
||||
|
||||
class Test:
|
||||
@_public
|
||||
def public_func(self):
|
||||
"""With doc string"""
|
||||
|
||||
@ignore_this
|
||||
@_public
|
||||
@another_decorator
|
||||
async def public_async_func(self) -> Counter:
|
||||
pass # no doc string
|
||||
|
||||
def not_public(self):
|
||||
pass
|
||||
|
||||
async def not_public_async(self):
|
||||
pass
|
||||
'''
|
||||
|
||||
IMPORT_1 = """\
|
||||
from collections import Counter
|
||||
"""
|
||||
|
||||
IMPORT_2 = """\
|
||||
from collections import Counter
|
||||
import os
|
||||
"""
|
||||
|
||||
IMPORT_3 = """\
|
||||
from typing import TYPE_CHECKING
|
||||
if TYPE_CHECKING:
|
||||
from collections import Counter
|
||||
"""
|
||||
|
||||
|
||||
def test_get_public_methods() -> None:
|
||||
methods = list(get_public_methods(ast.parse(SOURCE)))
|
||||
assert {m.name for m in methods} == {"public_func", "public_async_func"}
|
||||
|
||||
|
||||
def test_create_pass_through_args() -> None:
|
||||
testcases = [
|
||||
("def f()", "()"),
|
||||
("def f(one)", "(one)"),
|
||||
("def f(one, two)", "(one, two)"),
|
||||
("def f(one, *args)", "(one, *args)"),
|
||||
(
|
||||
"def f(one, *args, kw1, kw2=None, **kwargs)",
|
||||
"(one, *args, kw1=kw1, kw2=kw2, **kwargs)",
|
||||
),
|
||||
]
|
||||
|
||||
for funcdef, expected in testcases:
|
||||
func_node = ast.parse(funcdef + ":\n pass").body[0]
|
||||
assert isinstance(func_node, ast.FunctionDef)
|
||||
assert create_passthrough_args(func_node) == expected
|
||||
|
||||
|
||||
skip_lints = pytest.mark.skipif(
|
||||
sys.implementation.name != "cpython",
|
||||
reason="gen_exports is internal, black/isort only runs on CPython",
|
||||
)
|
||||
|
||||
|
||||
@skip_lints
|
||||
@pytest.mark.parametrize("imports", [IMPORT_1, IMPORT_2, IMPORT_3])
|
||||
def test_process(
|
||||
tmp_path: Path,
|
||||
imports: str,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
try:
|
||||
import black # noqa: F401
|
||||
# there's no dedicated CI run that has astor+isort, but lacks black.
|
||||
except ImportError as error: # pragma: no cover
|
||||
skip_if_optional_else_raise(error)
|
||||
|
||||
modpath = tmp_path / "_module.py"
|
||||
genpath = tmp_path / "_generated_module.py"
|
||||
modpath.write_text(SOURCE, encoding="utf-8")
|
||||
file = File(modpath, "runner", platform="linux", imports=imports)
|
||||
assert not genpath.exists()
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
process([file], do_test=True)
|
||||
assert excinfo.value.code == 1
|
||||
captured = capsys.readouterr()
|
||||
assert "Generated sources are outdated. Please regenerate." in captured.out
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
process([file], do_test=False)
|
||||
assert excinfo.value.code == 1
|
||||
captured = capsys.readouterr()
|
||||
assert "Regenerated sources successfully." in captured.out
|
||||
assert genpath.exists()
|
||||
process([file], do_test=True)
|
||||
# But if we change the lookup path it notices
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
process(
|
||||
[File(modpath, "runner.io_manager", platform="linux", imports=imports)],
|
||||
do_test=True,
|
||||
)
|
||||
assert excinfo.value.code == 1
|
||||
# Also if the platform is changed.
|
||||
with pytest.raises(SystemExit) as excinfo:
|
||||
process([File(modpath, "runner", imports=imports)], do_test=True)
|
||||
assert excinfo.value.code == 1
|
||||
|
||||
|
||||
@skip_lints
|
||||
def test_run_ruff(tmp_path: Path) -> None:
|
||||
"""Test that processing properly fails if ruff does."""
|
||||
try:
|
||||
import ruff # noqa: F401
|
||||
except ImportError as error: # pragma: no cover
|
||||
skip_if_optional_else_raise(error)
|
||||
|
||||
file = File(tmp_path / "module.py", "module")
|
||||
|
||||
success, _ = run_ruff(file, "class not valid code ><")
|
||||
assert not success
|
||||
|
||||
test_function = '''def combine_and(data: list[str]) -> str:
|
||||
"""Join values of text, and have 'and' with the last one properly."""
|
||||
if len(data) >= 2:
|
||||
data[-1] = 'and ' + data[-1]
|
||||
if len(data) > 2:
|
||||
return ', '.join(data)
|
||||
return ' '.join(data)'''
|
||||
|
||||
success, response = run_ruff(file, test_function)
|
||||
assert success
|
||||
assert response == test_function
|
||||
|
||||
|
||||
@skip_lints
|
||||
def test_lint_failure(tmp_path: Path) -> None:
|
||||
"""Test that processing properly fails if black or ruff does."""
|
||||
try:
|
||||
import black # noqa: F401
|
||||
import ruff # noqa: F401
|
||||
except ImportError as error: # pragma: no cover
|
||||
skip_if_optional_else_raise(error)
|
||||
|
||||
file = File(tmp_path / "module.py", "module")
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
run_linters(file, "class not valid code ><")
|
||||
|
||||
with pytest.raises(SystemExit):
|
||||
run_linters(file, "import waffle\n;import trio")
|
@ -0,0 +1,140 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import sys
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import pytest
|
||||
|
||||
from trio._tools.mypy_annotate import Result, export, main, process_line
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("src", "expected"),
|
||||
[
|
||||
("", None),
|
||||
("a regular line\n", None),
|
||||
(
|
||||
"package\\filename.py:42:8: note: Some info\n",
|
||||
Result(
|
||||
kind="notice",
|
||||
filename="package\\filename.py",
|
||||
start_line=42,
|
||||
start_col=8,
|
||||
end_line=None,
|
||||
end_col=None,
|
||||
message=" Some info",
|
||||
),
|
||||
),
|
||||
(
|
||||
"package/filename.py:42:1:46:3: error: Type error here [code]\n",
|
||||
Result(
|
||||
kind="error",
|
||||
filename="package/filename.py",
|
||||
start_line=42,
|
||||
start_col=1,
|
||||
end_line=46,
|
||||
end_col=3,
|
||||
message=" Type error here [code]",
|
||||
),
|
||||
),
|
||||
(
|
||||
"package/module.py:87: warn: Bad code\n",
|
||||
Result(
|
||||
kind="warning",
|
||||
filename="package/module.py",
|
||||
start_line=87,
|
||||
message=" Bad code",
|
||||
),
|
||||
),
|
||||
],
|
||||
ids=["blank", "normal", "note-wcol", "error-wend", "warn-lineonly"],
|
||||
)
|
||||
def test_processing(src: str, expected: Result | None) -> None:
|
||||
result = process_line(src)
|
||||
assert result == expected
|
||||
|
||||
|
||||
def test_export(capsys: pytest.CaptureFixture[str]) -> None:
|
||||
results = {
|
||||
Result(
|
||||
kind="notice",
|
||||
filename="package\\filename.py",
|
||||
start_line=42,
|
||||
start_col=8,
|
||||
end_line=None,
|
||||
end_col=None,
|
||||
message=" Some info",
|
||||
): ["Windows", "Mac"],
|
||||
Result(
|
||||
kind="error",
|
||||
filename="package/filename.py",
|
||||
start_line=42,
|
||||
start_col=1,
|
||||
end_line=46,
|
||||
end_col=3,
|
||||
message=" Type error here [code]",
|
||||
): ["Linux", "Mac"],
|
||||
Result(
|
||||
kind="warning",
|
||||
filename="package/module.py",
|
||||
start_line=87,
|
||||
message=" Bad code",
|
||||
): ["Linux"],
|
||||
}
|
||||
export(results)
|
||||
std = capsys.readouterr()
|
||||
assert std.err == ""
|
||||
assert std.out == (
|
||||
"::notice file=package\\filename.py,line=42,col=8,"
|
||||
"title=Mypy-Windows+Mac::package\\filename.py:(42:8): Some info"
|
||||
"\n"
|
||||
"::error file=package/filename.py,line=42,col=1,endLine=46,endColumn=3,"
|
||||
"title=Mypy-Linux+Mac::package/filename.py:(42:1 - 46:3): Type error here [code]"
|
||||
"\n"
|
||||
"::warning file=package/module.py,line=87,"
|
||||
"title=Mypy-Linux::package/module.py:87: Bad code\n"
|
||||
)
|
||||
|
||||
|
||||
def test_endtoend(
|
||||
tmp_path: Path,
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
capsys: pytest.CaptureFixture[str],
|
||||
) -> None:
|
||||
import trio._tools.mypy_annotate as mypy_annotate
|
||||
|
||||
inp_text = """\
|
||||
Mypy begun
|
||||
trio/core.py:15: error: Bad types here [misc]
|
||||
trio/package/module.py:48:4:56:18: warn: Missing annotations [no-untyped-def]
|
||||
Found 3 errors in 29 files
|
||||
"""
|
||||
result_file = tmp_path / "dump.dat"
|
||||
assert not result_file.exists()
|
||||
with monkeypatch.context():
|
||||
monkeypatch.setattr(sys, "stdin", io.StringIO(inp_text))
|
||||
|
||||
mypy_annotate.main(
|
||||
["--dumpfile", str(result_file), "--platform", "SomePlatform"],
|
||||
)
|
||||
|
||||
std = capsys.readouterr()
|
||||
assert std.err == ""
|
||||
assert std.out == inp_text # Echos the original.
|
||||
|
||||
assert result_file.exists()
|
||||
|
||||
main(["--dumpfile", str(result_file)])
|
||||
|
||||
std = capsys.readouterr()
|
||||
assert std.err == ""
|
||||
assert std.out == (
|
||||
"::error file=trio/core.py,line=15,title=Mypy-SomePlatform::trio/core.py:15: Bad types here [misc]\n"
|
||||
"::warning file=trio/package/module.py,line=48,col=4,endLine=56,endColumn=18,"
|
||||
"title=Mypy-SomePlatform::trio/package/module.py:(48:4 - 56:18): Missing "
|
||||
"annotations [no-untyped-def]\n"
|
||||
)
|
Reference in New Issue
Block a user