From 79a4a7be271a3bf615e2fdb9ebc372f9e6851efd Mon Sep 17 00:00:00 2001 From: Pierre Sassoulas Date: Sun, 9 Jan 2022 10:51:58 +0100 Subject: [PATCH 1/8] Add a testutil class to be able to test formatters easily --- pydocstringformatter/formatting/__init__.py | 2 +- pydocstringformatter/formatting/base.py | 10 +++ pydocstringformatter/utils/testutils.py | 79 +++++++++++++++++++++ tests/test_run.py | 30 +++----- 4 files changed, 98 insertions(+), 23 deletions(-) create mode 100644 pydocstringformatter/utils/testutils.py diff --git a/pydocstringformatter/formatting/__init__.py b/pydocstringformatter/formatting/__init__.py index a95e4a03..ad72ed13 100644 --- a/pydocstringformatter/formatting/__init__.py +++ b/pydocstringformatter/formatting/__init__.py @@ -1,4 +1,4 @@ -__all__ = ["FORMATTERS"] +__all__ = ["FORMATTERS", "Formatter"] from typing import List diff --git a/pydocstringformatter/formatting/base.py b/pydocstringformatter/formatting/base.py index 9edcbe73..d1a6a246 100644 --- a/pydocstringformatter/formatting/base.py +++ b/pydocstringformatter/formatting/base.py @@ -17,6 +17,16 @@ def name(self) -> str: user-facing and should be chosen carefully. """ + @property + def activate_option(self) -> str: + """The argparse option to activate this formatter.""" + return f"--{self.name}" + + @property + def deactivate_option(self) -> str: + """The argparse option to deactivate this formatter.""" + return f"--no-{self.name}" + @abc.abstractmethod def treat_token(self, tokeninfo: tokenize.TokenInfo) -> tokenize.TokenInfo: """Return a modified token.""" diff --git a/pydocstringformatter/utils/testutils.py b/pydocstringformatter/utils/testutils.py new file mode 100644 index 00000000..34dfe38d --- /dev/null +++ b/pydocstringformatter/utils/testutils.py @@ -0,0 +1,79 @@ +import contextlib +import logging +from pathlib import Path +from types import TracebackType +from typing import List, Type, Union + +import pytest + +import pydocstringformatter +from pydocstringformatter.formatting import Formatter + +LOGGER = logging.getLogger(__name__) + + +class FormatterAssert(contextlib.AbstractContextManager): # type: ignore[type-arg] + + """Permit to assert that a Formatter does something on a docstring. + + Also permit to check that nothing happen if it's deactivated. + """ + + def __init__( + self, + docstring: str, + formatters: List[Formatter], + capsys: pytest.CaptureFixture[str], + tmp_path: Path, + ): + self.formatters = formatters + file_name = "_".join([f.name for f in self.formatters]) + self.file_to_format = tmp_path / f"test_{file_name}.py" + self.file_to_format.write_text(docstring) + self.capsys = capsys + names = [f"'{f.name}'" for f in formatters] + verb = "is" if len(names) == 1 else "are" + self.assert_msg = f""" +{{}} was modified but {', '.join(names)} {verb} {{}}. +Temp file is '{self.file_to_format}' +""" + + def __enter__(self) -> "FormatterAssert": + return self + + @staticmethod + def __launch(commands: List[str]) -> None: + """Launch pydocstringformatter, with a logging for easier debug.""" + pydocstringformatter.run_docstring_formatter(commands) + LOGGER.info("Launching 'pydocstringformatter' with: %s", commands) + + def assert_format_when_activated(self) -> None: + """The formatter does something when activated.""" + msg = self.assert_msg.format("Nothing", "activated") + self.__launch( + [str(self.file_to_format)] + [f.activate_option for f in self.formatters] + ) + out, err = self.capsys.readouterr() + assert not err + assert "Nothing to do!" not in out, msg + expected = ["---", "@@", "+++"] + assert all(e in out for e in expected), msg + + def assert_no_change_when_deactivated(self) -> None: + """The formatter does nothing when deactivated.""" + self.__launch( + [str(self.file_to_format)] + [f.deactivate_option for f in self.formatters] + ) + out, err = self.capsys.readouterr() + assert not err + assert "Nothing to do!" in out, self.assert_msg.format( + "Something", "deactivated" + ) + + def __exit__( + self, + exc_type: Union[Type[BaseException], None], + exc_val: Union[BaseException, None], + exc_tb: Union[TracebackType, None], + ) -> None: + return None diff --git a/tests/test_run.py b/tests/test_run.py index 02d76c2e..d1d98e16 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -2,12 +2,12 @@ import os import sys from pathlib import Path -from typing import List import pytest import pydocstringformatter from pydocstringformatter.formatting import FORMATTERS +from pydocstringformatter.utils.testutils import FormatterAssert def test_no_arguments(capsys: pytest.CaptureFixture[str]) -> None: @@ -117,29 +117,15 @@ def test_output_message_two_files( @pytest.mark.parametrize( - "args,should_format", + "bad_docstring", [ - [[f"--no-{f.name}" for f in FORMATTERS], False], - [[f"--{f.name}" for f in FORMATTERS], True], + f'"""{"a" * 120}\n{"b" * 120}"""', ], ) -def test_optional_formatters( - args: List[str], - should_format: bool, - capsys: pytest.CaptureFixture[str], - tmp_path: Path, +def test_begin_quote_formatters( + bad_docstring: str, capsys: pytest.CaptureFixture[str], tmp_path: Path ) -> None: """Test that (optional) formatters are activated or not depending on options.""" - bad_docstring = tmp_path / "bad_docstring.py" - bad_docstring.write_text(f'"""{"a" * 120}\n{"b" * 120}"""') - pydocstringformatter.run_docstring_formatter([str(bad_docstring)] + args) - out, err = capsys.readouterr() - assert not err - if should_format: - msg = "Nothing was modified, but all formatters are activated." - assert "Nothing to do!" not in out - expected = ["---", "@@", "+++"] - assert all(e in out for e in expected), msg - else: - msg = "Something was modified, but all formatter are deactivated." - assert "Nothing to do!" in out, msg + with FormatterAssert(bad_docstring, FORMATTERS, capsys, tmp_path) as asserter: + asserter.assert_format_when_activated() + asserter.assert_no_change_when_deactivated() From ce1524cfbb3028c2acf2c4b16006e93f9dd21f2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:09:50 +0100 Subject: [PATCH 2/8] Review --- pydocstringformatter/utils/testutils.py | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/pydocstringformatter/utils/testutils.py b/pydocstringformatter/utils/testutils.py index 34dfe38d..2a89ba86 100644 --- a/pydocstringformatter/utils/testutils.py +++ b/pydocstringformatter/utils/testutils.py @@ -2,7 +2,7 @@ import logging from pathlib import Path from types import TracebackType -from typing import List, Type, Union +from typing import List, Optional, Type import pytest @@ -12,11 +12,10 @@ LOGGER = logging.getLogger(__name__) -class FormatterAssert(contextlib.AbstractContextManager): # type: ignore[type-arg] +class FormatterAssert(contextlib.AbstractContextManager["FormatterAssert"]): + """ContextManager used to assert that a Formatter does something on a docstring. - """Permit to assert that a Formatter does something on a docstring. - - Also permit to check that nothing happen if it's deactivated. + Also permit to check that nothing happens if it's deactivated. """ def __init__( @@ -25,7 +24,7 @@ def __init__( formatters: List[Formatter], capsys: pytest.CaptureFixture[str], tmp_path: Path, - ): + ) -> None: self.formatters = formatters file_name = "_".join([f.name for f in self.formatters]) self.file_to_format = tmp_path / f"test_{file_name}.py" @@ -43,12 +42,12 @@ def __enter__(self) -> "FormatterAssert": @staticmethod def __launch(commands: List[str]) -> None: - """Launch pydocstringformatter, with a logging for easier debug.""" + """Launch pydocstringformatter while logging for easier debugging.""" pydocstringformatter.run_docstring_formatter(commands) LOGGER.info("Launching 'pydocstringformatter' with: %s", commands) def assert_format_when_activated(self) -> None: - """The formatter does something when activated.""" + """Assert that the formatter does something when activated.""" msg = self.assert_msg.format("Nothing", "activated") self.__launch( [str(self.file_to_format)] + [f.activate_option for f in self.formatters] @@ -60,7 +59,7 @@ def assert_format_when_activated(self) -> None: assert all(e in out for e in expected), msg def assert_no_change_when_deactivated(self) -> None: - """The formatter does nothing when deactivated.""" + """Assert that the formatter does nothing when deactivated.""" self.__launch( [str(self.file_to_format)] + [f.deactivate_option for f in self.formatters] ) @@ -72,8 +71,8 @@ def assert_no_change_when_deactivated(self) -> None: def __exit__( self, - exc_type: Union[Type[BaseException], None], - exc_val: Union[BaseException, None], - exc_tb: Union[TracebackType, None], + exc_type: Optional[Type[BaseException]], + exc_val: Optional[BaseException], + exc_tb: Optional[TracebackType], ) -> None: return None From da15d9ff157d0d5dbef48de8abb5b2a0c7c8ca1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:11:51 +0100 Subject: [PATCH 3/8] Review 2 --- tests/test_run.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/tests/test_run.py b/tests/test_run.py index d1d98e16..c1e685ee 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -116,16 +116,12 @@ def test_output_message_two_files( assert not output.err -@pytest.mark.parametrize( - "bad_docstring", - [ - f'"""{"a" * 120}\n{"b" * 120}"""', - ], -) def test_begin_quote_formatters( - bad_docstring: str, capsys: pytest.CaptureFixture[str], tmp_path: Path + capsys: pytest.CaptureFixture[str], tmp_path: Path ) -> None: """Test that (optional) formatters are activated or not depending on options.""" - with FormatterAssert(bad_docstring, FORMATTERS, capsys, tmp_path) as asserter: + with FormatterAssert( + f'"""{"a" * 120}\n{"b" * 120}"""', FORMATTERS, capsys, tmp_path + ) as asserter: asserter.assert_format_when_activated() asserter.assert_no_change_when_deactivated() From b2365a4955f346efa770500c44e5b85f228ce78a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:13:43 +0100 Subject: [PATCH 4/8] Add disable --- pydocstringformatter/utils/testutils.py | 1 + 1 file changed, 1 insertion(+) diff --git a/pydocstringformatter/utils/testutils.py b/pydocstringformatter/utils/testutils.py index 2a89ba86..c3352a57 100644 --- a/pydocstringformatter/utils/testutils.py +++ b/pydocstringformatter/utils/testutils.py @@ -12,6 +12,7 @@ LOGGER = logging.getLogger(__name__) +# pylint: disable-next=unsubscriptable-object class FormatterAssert(contextlib.AbstractContextManager["FormatterAssert"]): """ContextManager used to assert that a Formatter does something on a docstring. From 9d4ca66945e867d25ea4cf02959670014b8a09f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:15:09 +0100 Subject: [PATCH 5/8] Redo imports --- pydocstringformatter/utils/testutils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pydocstringformatter/utils/testutils.py b/pydocstringformatter/utils/testutils.py index c3352a57..aa547207 100644 --- a/pydocstringformatter/utils/testutils.py +++ b/pydocstringformatter/utils/testutils.py @@ -6,7 +6,7 @@ import pytest -import pydocstringformatter +from pydocstringformatter import run_docstring_formatter from pydocstringformatter.formatting import Formatter LOGGER = logging.getLogger(__name__) @@ -44,7 +44,7 @@ def __enter__(self) -> "FormatterAssert": @staticmethod def __launch(commands: List[str]) -> None: """Launch pydocstringformatter while logging for easier debugging.""" - pydocstringformatter.run_docstring_formatter(commands) + run_docstring_formatter(commands) LOGGER.info("Launching 'pydocstringformatter' with: %s", commands) def assert_format_when_activated(self) -> None: From e8fc6d630a215c8eaccf4e9a54c59d437cf6e256 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:16:47 +0100 Subject: [PATCH 6/8] Move around --- .../{utils/testutils.py => testutils/__init__.py} | 0 tests/test_run.py | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename pydocstringformatter/{utils/testutils.py => testutils/__init__.py} (100%) diff --git a/pydocstringformatter/utils/testutils.py b/pydocstringformatter/testutils/__init__.py similarity index 100% rename from pydocstringformatter/utils/testutils.py rename to pydocstringformatter/testutils/__init__.py diff --git a/tests/test_run.py b/tests/test_run.py index c1e685ee..6c9230d3 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -7,7 +7,7 @@ import pydocstringformatter from pydocstringformatter.formatting import FORMATTERS -from pydocstringformatter.utils.testutils import FormatterAssert +from pydocstringformatter.testutils import FormatterAssert def test_no_arguments(capsys: pytest.CaptureFixture[str]) -> None: From 6601a9c6113eb36fc6e2b20e4ec1b107f6c408df Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:17:44 +0100 Subject: [PATCH 7/8] Rename --- pydocstringformatter/testutils/__init__.py | 4 ++-- tests/test_run.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pydocstringformatter/testutils/__init__.py b/pydocstringformatter/testutils/__init__.py index aa547207..bf985a8c 100644 --- a/pydocstringformatter/testutils/__init__.py +++ b/pydocstringformatter/testutils/__init__.py @@ -13,7 +13,7 @@ # pylint: disable-next=unsubscriptable-object -class FormatterAssert(contextlib.AbstractContextManager["FormatterAssert"]): +class FormatterAsserter(contextlib.AbstractContextManager["FormatterAsserter"]): """ContextManager used to assert that a Formatter does something on a docstring. Also permit to check that nothing happens if it's deactivated. @@ -38,7 +38,7 @@ def __init__( Temp file is '{self.file_to_format}' """ - def __enter__(self) -> "FormatterAssert": + def __enter__(self) -> "FormatterAsserter": return self @staticmethod diff --git a/tests/test_run.py b/tests/test_run.py index 6c9230d3..5b4f9aad 100644 --- a/tests/test_run.py +++ b/tests/test_run.py @@ -7,7 +7,7 @@ import pydocstringformatter from pydocstringformatter.formatting import FORMATTERS -from pydocstringformatter.testutils import FormatterAssert +from pydocstringformatter.testutils import FormatterAsserter def test_no_arguments(capsys: pytest.CaptureFixture[str]) -> None: @@ -120,7 +120,7 @@ def test_begin_quote_formatters( capsys: pytest.CaptureFixture[str], tmp_path: Path ) -> None: """Test that (optional) formatters are activated or not depending on options.""" - with FormatterAssert( + with FormatterAsserter( f'"""{"a" * 120}\n{"b" * 120}"""', FORMATTERS, capsys, tmp_path ) as asserter: asserter.assert_format_when_activated() From 96d1b1f145fce2e447b72af22bfe317459f890a0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20van=20Noord?= <13665637+DanielNoord@users.noreply.github.com> Date: Tue, 1 Feb 2022 21:19:32 +0100 Subject: [PATCH 8/8] Re-add disable for mypy --- pydocstringformatter/testutils/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pydocstringformatter/testutils/__init__.py b/pydocstringformatter/testutils/__init__.py index bf985a8c..9a426ce4 100644 --- a/pydocstringformatter/testutils/__init__.py +++ b/pydocstringformatter/testutils/__init__.py @@ -12,8 +12,7 @@ LOGGER = logging.getLogger(__name__) -# pylint: disable-next=unsubscriptable-object -class FormatterAsserter(contextlib.AbstractContextManager["FormatterAsserter"]): +class FormatterAsserter(contextlib.AbstractContextManager): # type: ignore[type-arg] """ContextManager used to assert that a Formatter does something on a docstring. Also permit to check that nothing happens if it's deactivated.