diff --git a/src/pytest_mypy/__init__.py b/src/pytest_mypy/__init__.py index 4163b71..dd80bf3 100644 --- a/src/pytest_mypy/__init__.py +++ b/src/pytest_mypy/__init__.py @@ -68,10 +68,12 @@ def default_test_name_formatter(*, item: MypyFileItem) -> str: def default_file_error_formatter( item: MypyItem, results: MypyResults, - errors: List[str], + lines: List[str], ) -> str: """Create a string to be displayed when mypy finds errors in a file.""" - return "\n".join(errors) + if item.config.option.mypy_report_style == "mypy": + return "\n".join(lines) + return "\n".join(line.partition(":")[2].strip() for line in lines) file_error_formatter = default_file_error_formatter @@ -92,6 +94,16 @@ def pytest_addoption(parser: pytest.Parser) -> None: type=str, help="adds custom mypy config file", ) + styles = { + "mypy": "modify the original mypy output as little as possible", + "no-path": "(default) strip the path prefix from mypy errors", + } + group.addoption( + "--mypy-report-style", + choices=list(styles), + help="change the way mypy output is reported:\n" + + "\n".join(f"- {name}: {desc}" for name, desc in styles.items()), + ) group.addoption( "--mypy-no-status-check", action="store_true", @@ -175,6 +187,7 @@ def pytest_configure(config: pytest.Config) -> None: [ config.option.mypy, config.option.mypy_config_file, + config.option.mypy_report_style, config.option.mypy_ignore_missing_imports, config.option.mypy_no_status_check, config.option.mypy_xfail, @@ -268,13 +281,7 @@ def runtest(self) -> None: reason="mypy errors are expected by --mypy-xfail.", ) ) - raise MypyError( - file_error_formatter( - self, - results, - errors=[line.partition(":")[2].strip() for line in lines], - ) - ) + raise MypyError(file_error_formatter(self, results, lines)) def reportinfo(self) -> Tuple[Path, None, str]: """Produce a heading for the test report.""" diff --git a/tests/test_pytest_mypy.py b/tests/test_pytest_mypy.py index e217f2d..498b5f4 100644 --- a/tests/test_pytest_mypy.py +++ b/tests/test_pytest_mypy.py @@ -399,24 +399,19 @@ def pyfunc(x: int) -> str: assert result.ret == pytest.ExitCode.TESTS_FAILED -def test_api_error_formatter(testdir, xdist_args): - """Ensure that the plugin can be configured in a conftest.py.""" +def test_api_file_error_formatter(testdir, xdist_args): + """Ensure that the file_error_formatter can be replaced in a conftest.py.""" testdir.makepyfile( bad=""" def pyfunc(x: int) -> str: return x * 2 """, ) + file_error = "UnmistakableFileError" testdir.makepyfile( - conftest=""" - def custom_file_error_formatter(item, results, errors): - return '\\n'.join( - '{path}:{error}'.format( - path=item.fspath, - error=error, - ) - for error in errors - ) + conftest=f""" + def custom_file_error_formatter(item, results, lines): + return '{file_error}' def pytest_configure(config): plugin = config.pluginmanager.getplugin('mypy') @@ -424,7 +419,7 @@ def pytest_configure(config): """, ) result = testdir.runpytest_subprocess("--mypy", *xdist_args) - result.stdout.fnmatch_lines(["*/bad.py:2: error: Incompatible return value*"]) + result.stdout.fnmatch_lines([f"*{file_error}*"]) assert result.ret == pytest.ExitCode.TESTS_FAILED @@ -671,3 +666,29 @@ def pytest_configure(config): def test_error_severity(): """Verify that non-error lines produce no severity.""" assert pytest_mypy._error_severity("arbitrary line with no error") is None + + +def test_mypy_report_style(testdir, xdist_args): + """Verify that --mypy-report-style functions correctly.""" + module_name = "unmistakable_module_name" + testdir.makepyfile( + **{ + module_name: """ + def pyfunc(x: int) -> str: + return x * 2 + """ + }, + ) + result = testdir.runpytest_subprocess("--mypy-report-style", "no-path", *xdist_args) + mypy_file_checks = 1 + mypy_status_check = 1 + mypy_checks = mypy_file_checks + mypy_status_check + result.assert_outcomes(failed=mypy_checks) + result.stdout.fnmatch_lines(["2: error: Incompatible return value*"]) + assert result.ret == pytest.ExitCode.TESTS_FAILED + result = testdir.runpytest_subprocess("--mypy-report-style", "mypy", *xdist_args) + result.assert_outcomes(failed=mypy_checks) + result.stdout.fnmatch_lines( + [f"{module_name}.py:2: error: Incompatible return value*"] + ) + assert result.ret == pytest.ExitCode.TESTS_FAILED