Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions src/pytest_ty/plugin.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import functools
import itertools
import json
import subprocess
import typing

Expand Down Expand Up @@ -46,13 +48,13 @@ def _run_ty_once(config: pytest.Config) -> dict[str, list[str]]:
if (results := config.stash.get(_TY_RESULTS_STASH_KEY, None)) is not None:
return results

command = [_ty_bin(), "check", "--output-format=concise"]
command = [_ty_bin(), "check", "--output-format=gitlab"]
results = {}
Comment thread
boidolr marked this conversation as resolved.

try:
subprocess.run(command, check=True, timeout=60, capture_output=True, cwd=config.rootpath) # noqa: S603
except subprocess.CalledProcessError as e:
stdout = e.stdout.decode(errors="replace") if e.stdout else "<empty>"
stdout = e.stdout.decode(errors="replace") if e.stdout else "[]"
stderr = e.stderr.decode(errors="replace") if e.stderr else "<empty>"
results = _parse_ty_output(stdout)
if not results:
Expand All @@ -74,15 +76,21 @@ def _run_ty_once(config: pytest.Config) -> dict[str, list[str]]:


def _parse_ty_output(output: str) -> dict[str, list[str]]:
results: dict[str, list[str]] = {}
try:
diagnostics = json.loads(output)
except json.JSONDecodeError:
return {}

for line in output.split("\n"):
line = line.strip() # noqa: PLW2901 # loop variable cleanup
parts = line.rsplit(":", 3)
if len(parts) < 4: # noqa: PLR2004 # format is `file_name.py:line:pos:error_message
results: dict[str, list[str]] = {}
for diag in diagnostics:
path = diag.get("location", {}).get("path", "")
if not path:
continue
file_path = parts[0]
results.setdefault(file_path, []).append(line)
line = diag["location"]["positions"]["begin"]["line"]
column = diag["location"]["positions"]["begin"]["column"]
description = diag.get("description", "<ty failure>")
message = f"{path}:{line}:{column}: {description}"
results.setdefault(path, []).append(message)

return results

Expand Down Expand Up @@ -130,4 +138,4 @@ def runtest(self) -> None:
if _TY_FAILURE_MARKER in results:
raise TyError("\n".join(results[_TY_FAILURE_MARKER]))

raise TyError("\n".join(results.keys()))
raise TyError("\n".join(itertools.chain.from_iterable(results.values())))
42 changes: 42 additions & 0 deletions tests/test_pytest_ty.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,16 @@ def test_failure() -> None:
)


@pytest.fixture
def another_failing_test(pytester: pytest.Pytester) -> None:
pytester.makepyfile(
test_another_failing_file="""
def test_another_failure() -> None:
another_value: int = "2"
"""
)


@pytest.fixture
def passing_test(pytester: pytest.Pytester) -> None:
pytester.makepyfile(
Expand Down Expand Up @@ -188,3 +198,35 @@ def test_timeout_handling_failing_check(pytester: pytest.Pytester) -> None:
result.stdout.fnmatch_lines(["*::ty PASSED*"])
result.stdout.fnmatch_lines(["*::ty::status FAILED*"])
assert result.ret == 1


@pytest.mark.usefixtures("failing_test", "another_failing_test", "passing_test")
def test_status_item_shows_all_failures_with_verbose(pytester: pytest.Pytester) -> None:
result = pytester.runpytest("--ty", "-v")

result.stdout.fnmatch_lines("*test_passing_file.py::ty PASSED*")
Comment thread
boidolr marked this conversation as resolved.
result.stdout.fnmatch_lines(["*test_failing_file.py::ty FAILED*"])
result.stdout.fnmatch_lines(["*test_another_failing_file.py::ty FAILED*"])
result.stdout.fnmatch_lines(["*::ty::status FAILED*"])
result.stdout.fnmatch_lines(["*:2:18:*invalid-assignment*"])
assert result.ret == 1


def test_type_ignore_comment_parsing(pytester: pytest.Pytester) -> None:
Comment thread
boidolr marked this conversation as resolved.
pytester.makepyfile(
test_colon_error="""
from typing import TypedDict

class MyDict(TypedDict):
name: str

def test_case() -> None:
d: MyDict = {"name": "test", "extra": 1}
"""
)

result = pytester.runpytest("--ty", "-v")

result.stdout.fnmatch_lines(["*test_colon_error.py::ty FAILED*"])
result.stdout.fnmatch_lines(["*::ty::status FAILED*"])
assert result.ret == 1