From c7225b51af98b7d8a9e35e3a5ef283e2fed8460a Mon Sep 17 00:00:00 2001 From: David Tucker Date: Thu, 29 Aug 2024 08:53:42 -0700 Subject: [PATCH] Add --mypy-xfail --- src/pytest_mypy/__init__.py | 28 +++++++++++++++++-- tests/test_pytest_mypy.py | 56 +++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+), 2 deletions(-) diff --git a/src/pytest_mypy/__init__.py b/src/pytest_mypy/__init__.py index 40d8c3d..73bd12f 100644 --- a/src/pytest_mypy/__init__.py +++ b/src/pytest_mypy/__init__.py @@ -89,6 +89,11 @@ def pytest_addoption(parser: pytest.Parser) -> None: action="store_true", help="ignore mypy's exit status", ) + group.addoption( + "--mypy-xfail", + action="store_true", + help="xfail mypy errors", + ) def _xdist_worker(config: pytest.Config) -> Dict[str, Any]: @@ -170,6 +175,7 @@ def pytest_collect_file( parent.config.option.mypy_config_file, parent.config.option.mypy_ignore_missing_imports, parent.config.option.mypy_no_status_check, + parent.config.option.mypy_xfail, ], ): # Do not create MypyFile instance for a .py file if a @@ -234,6 +240,13 @@ def runtest(self) -> None: error.partition(":")[2].partition(":")[0].strip() == "note" for error in errors ): + if self.session.config.option.mypy_xfail: + self.add_marker( + pytest.mark.xfail( + raises=MypyError, + reason="mypy errors are expected by --mypy-xfail.", + ) + ) raise MypyError(file_error_formatter(self, results, errors)) warnings.warn("\n" + "\n".join(errors), MypyWarning) @@ -253,6 +266,15 @@ def runtest(self) -> None: """Raise a MypyError if mypy exited with a non-zero status.""" results = MypyResults.from_session(self.session) if results.status: + if self.session.config.option.mypy_xfail: + self.add_marker( + pytest.mark.xfail( + raises=MypyError, + reason=( + "A non-zero mypy exit status is expected by --mypy-xfail." + ), + ) + ) raise MypyError(f"mypy exited with status {results.status}.") @@ -366,9 +388,11 @@ def pytest_terminal_summary( except FileNotFoundError: # No MypyItems executed. return - if results.unmatched_stdout or results.stderr: + if config.option.mypy_xfail or results.unmatched_stdout or results.stderr: terminalreporter.section(terminal_summary_title) - if results.unmatched_stdout: + if config.option.mypy_xfail: + terminalreporter.write(results.stdout) + elif results.unmatched_stdout: color = {"red": True} if results.status else {"green": True} terminalreporter.write_line(results.unmatched_stdout, **color) if results.stderr: diff --git a/tests/test_pytest_mypy.py b/tests/test_pytest_mypy.py index 3143521..6ee40a0 100644 --- a/tests/test_pytest_mypy.py +++ b/tests/test_pytest_mypy.py @@ -561,3 +561,59 @@ def test_mypy_no_status_check(testdir, xdist_args): result = testdir.runpytest_subprocess("--mypy-no-status-check", *xdist_args) result.assert_outcomes(passed=mypy_file_checks) assert result.ret == pytest.ExitCode.OK + + +def test_mypy_xfail_passes(testdir, xdist_args): + """Verify that --mypy-xfail passes passes.""" + testdir.makepyfile(thon="one: int = 1") + result = testdir.runpytest_subprocess("--mypy", *xdist_args) + mypy_file_checks = 1 + mypy_status_check = 1 + result.assert_outcomes(passed=mypy_file_checks + mypy_status_check) + assert result.ret == pytest.ExitCode.OK + result = testdir.runpytest_subprocess("--mypy-xfail", *xdist_args) + result.assert_outcomes(passed=mypy_file_checks + mypy_status_check) + assert result.ret == pytest.ExitCode.OK + + +def test_mypy_xfail_xfails(testdir, xdist_args): + """Verify that --mypy-xfail xfails failures.""" + testdir.makepyfile(thon="one: str = 1") + result = testdir.runpytest_subprocess("--mypy", *xdist_args) + mypy_file_checks = 1 + mypy_status_check = 1 + result.assert_outcomes(failed=mypy_file_checks + mypy_status_check) + assert result.ret == pytest.ExitCode.TESTS_FAILED + result = testdir.runpytest_subprocess("--mypy-xfail", *xdist_args) + result.assert_outcomes(xfailed=mypy_file_checks + mypy_status_check) + assert result.ret == pytest.ExitCode.OK + + +def test_mypy_xfail_reports_stdout(testdir, xdist_args): + """Verify that --mypy-xfail reports stdout from mypy.""" + stdout = "a distinct string on stdout" + testdir.makepyfile( + conftest=f""" + import pytest + + @pytest.hookimpl(trylast=True) + def pytest_configure(config): + pytest_mypy = config.pluginmanager.getplugin("mypy") + mypy_config_stash = config.stash[pytest_mypy.stash_key["config"]] + with open(mypy_config_stash.mypy_results_path, mode="w") as results_f: + pytest_mypy.MypyResults( + opts=[], + stdout="{stdout}", + stderr="", + status=0, + abspath_errors={{}}, + unmatched_stdout="", + ).dump(results_f) + """, + ) + result = testdir.runpytest_subprocess("--mypy", *xdist_args) + assert result.ret == pytest.ExitCode.OK + assert stdout not in result.stdout.str() + result = testdir.runpytest_subprocess("--mypy-xfail", *xdist_args) + assert result.ret == pytest.ExitCode.OK + assert stdout in result.stdout.str()