Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

2.1.0: pytest fails in tests/test_sphinx_autodoc_typehints.py::test_always_document_param_types[doc_param_type] unit and pytest warnings #450

Open
kloczek opened this issue Apr 28, 2024 · 1 comment

Comments

@kloczek
Copy link

kloczek commented Apr 28, 2024

I'm packaging your module as an rpm package so I'm using the typical PEP517 based build, install and test cycle used on building packages from non-root account.

  • python3 -sBm build -w --no-isolation
  • because I'm calling build with --no-isolation I'm using during all processes only locally installed modules
  • install .whl file in </install/prefix> using installer module
  • run pytest with $PYTHONPATH pointing to sitearch and sitelib inside </install/prefix>
  • build is performed in env which is cut off from access to the public network (pytest is executed with -m "not network")
Here is pytest output:
+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-sphinx-autodoc-typehints-2.1.0-2.fc37.x86_64/usr/lib64/python3.10/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-sphinx-autodoc-typehints-2.1.0-2.fc37.x86_64/usr/lib/python3.10/site-packages
+ /usr/bin/pytest -ra -m 'not network'
==================================================================================== test session starts ====================================================================================
platform linux -- Python 3.10.14, pytest-8.1.1, pluggy-1.4.0
rootdir: /home/tkloczko/rpmbuild/BUILD/sphinx-autodoc-typehints-2.1.0
configfile: pyproject.toml
testpaths: tests
collected 306 items

tests/test_integration.py ........................................................................................................................................................    [ 49%]
tests/test_sphinx_autodoc_typehints.py ..............................................................................................................................F............... [ 96%]
...........                                                                                                                                                                           [ 99%]
tests/test_version.py .                                                                                                                                                               [100%]

========================================================================================= FAILURES ==========================================================================================
_____________________________________________________________________ test_always_document_param_types[doc_param_type] ______________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f8ca2204670>, warning = <_io.StringIO object at 0x7f8ca22056c0>, always_document_param_types = True

    @pytest.mark.parametrize("always_document_param_types", [True, False], ids=["doc_param_type", "no_doc_param_type"])
    @pytest.mark.sphinx("text", testroot="dummy")
    @patch("sphinx.writers.text.MAXWIDTH", 2000)
    def test_always_document_param_types(
        app: SphinxTestApp,
        status: StringIO,
        warning: StringIO,
        always_document_param_types: bool,
    ) -> None:
        set_python_path()

        app.config.always_document_param_types = always_document_param_types  # type: ignore[attr-defined] # create flag
        app.config.autodoc_mock_imports = ["mailbox"]  # type: ignore[attr-defined] # create flag

        # Prevent "document isn't included in any toctree" warnings
        for f in Path(app.srcdir).glob("*.rst"):
            f.unlink()
        (Path(app.srcdir) / "index.rst").write_text(
            dedent(
                """
                .. autofunction:: dummy_module.undocumented_function

                .. autoclass:: dummy_module.DataClass
                    :undoc-members:
                    :special-members: __init__
                """,
            ),
        )

        app.build()

        assert "build succeeded" in status.getvalue()  # Build succeeded
        assert not warning.getvalue().strip()

        format_args = {}
        for indentation_level in range(2):
            key = f"undoc_params_{indentation_level}"
            if always_document_param_types:
                format_args[key] = indent('\n\n   Parameters:\n      **x** ("int")', "   " * indentation_level)
            else:
                format_args[key] = ""

        contents = (Path(app.srcdir) / "_build/text/index.txt").read_text()
        expected_contents = """\
        dummy_module.undocumented_function(x)

           Hi{undoc_params_0}

           Return type:
              "str"

        class dummy_module.DataClass(x)

           Class docstring.{undoc_params_0}

           __init__(x){undoc_params_1}
        """
        expected_contents = dedent(expected_contents).format(**format_args)
>       assert contents == expected_contents
E       assert 'dummy_module... ("int") --\n' == 'dummy_module...x** ("int")\n'
E
E         Skipping 70 identical leading characters in diff, use -v to show
E         - ** ("int")
E         + ** ("int") --
E         ?           +++
E
E              Return type:...
E
E         ...Full output truncated (17 lines hidden), use '-vv' to show

tests/test_sphinx_autodoc_typehints.py:615: AssertionError
--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-85/dummy
# outdir: /tmp/pytest-of-tkloczko/pytest-85/dummy/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 5 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-85/dummy/_build/text.

# warning:

===================================================================================== warnings summary ======================================================================================
tests/conftest.py:10
  /home/tkloczko/rpmbuild/BUILD/sphinx-autodoc-typehints-2.1.0/tests/conftest.py:10: RemovedInSphinx90Warning: 'sphinx.testing.path' is deprecated. Use 'os.path' or 'pathlib' instead.
    from sphinx.testing.path import path

tests/test_integration.py: 112 warnings
tests/test_sphinx_autodoc_typehints.py: 82 warnings
  /home/tkloczko/rpmbuild/BUILDROOT/python-sphinx-autodoc-typehints-2.1.0-2.fc37.x86_64/usr/lib/python3.10/site-packages/sphinx_autodoc_typehints/__init__.py:806: DeprecationWarning: The frontend.OptionParser class will be replaced by a subclass of argparse.ArgumentParser in Docutils 0.21 or later.
    settings = OptionParser(components=(RSTParser,)).get_default_values()

tests/test_integration.py: 7728 warnings
tests/test_sphinx_autodoc_typehints.py: 5658 warnings
  /usr/lib64/python3.10/optparse.py:1000: DeprecationWarning: The frontend.Option class will be removed in Docutils 0.21 or later.
    option = self.option_class(*args, **kwargs)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================================================================== short test summary info ==================================================================================
FAILED tests/test_sphinx_autodoc_typehints.py::test_always_document_param_types[doc_param_type] - assert 'dummy_module... ("int") --\n' == 'dummy_module...x** ("int")\n'
====================================================================== 1 failed, 305 passed, 13581 warnings in 18.10s =======================================================================
List of installed modules in build env:
Package                       Version
----------------------------- -----------
alabaster                     0.7.16
attrs                         23.2.0
Babel                         2.14.0
build                         1.2.1
certifi                       2023.7.22
charset-normalizer            3.3.2
docutils                      0.20.1
exceptiongroup                1.1.3
hatch-vcs                     0.4.0
hatchling                     1.24.0
idna                          3.7
imagesize                     1.4.1
importlib_metadata            7.1.0
iniconfig                     2.0.0
installer                     0.7.0
Jinja2                        3.1.3
jsonschema                    4.20.0
jsonschema-specifications     2023.12.1
MarkupSafe                    2.1.5
packaging                     24.0
pathspec                      0.12.1
pluggy                        1.4.0
Pygments                      2.17.2
pyproject_hooks               1.0.0
pytest                        8.1.1
python-dateutil               2.9.0.post0
referencing                   0.32.0
requests                      2.31.0
rpds-py                       0.18.0
setuptools                    69.4.0
setuptools-scm                8.0.4
snowballstemmer               2.2.0
Sphinx                        7.2.6
sphinxcontrib-applehelp       1.0.8
sphinxcontrib-devhelp         1.0.6
sphinxcontrib-htmlhelp        2.0.5
sphinxcontrib-jsmath          1.0.1
sphinxcontrib-qthelp          1.0.7
sphinxcontrib-serializinghtml 1.1.10
sphobjinv                     2.3.1
tokenize_rt                   5.2.0
tomli                         2.0.1
trove-classifiers             2024.4.16
typing_extensions             4.11.0
urllib3                       2.2.1
wheel                         0.43.0
zipp                          3.18.1

Please let me know if you need more details or want me to perform some diagnostics.

@kloczek
Copy link
Author

kloczek commented May 14, 2024

Gentle ping .. any update? 🤔

BTW looks like filtering sphinx-autodoc-typehints code over pyupgrade --py38-plus causes that test suite starts failing in new units.

Here is pytest output
+ PYTHONPATH=/home/tkloczko/rpmbuild/BUILDROOT/python-sphinx-autodoc-typehints-2.1.0-2.fc37.x86_64/usr/lib64/python3.10/site-packages:/home/tkloczko/rpmbuild/BUILDROOT/python-sphinx-autodoc-typehints-2.1.0-2.fc37.x86_64/usr/lib/python3.10/site-packages
+ /usr/bin/pytest -ra -m 'not network'
==================================================================================== test session starts ====================================================================================
platform linux -- Python 3.10.14, pytest-8.1.1, pluggy-1.4.0
benchmark: 4.0.0 (defaults: timer=time.perf_counter disable_gc=False min_rounds=5 min_time=0.000005 max_time=1.0 calibration_precision=10 warmup=False warmup_iterations=100000)
rootdir: /home/tkloczko/rpmbuild/BUILD/sphinx-autodoc-typehints-2.1.0
configfile: pyproject.toml
testpaths: tests
plugins: timeout-2.3.1, xdist-3.6.1, benchmark-4.0.0, hypothesis-6.100.0
collected 306 items

tests/test_integration.py ................FF....................................FF....................................FF....................................FF....................    [ 49%]
tests/test_sphinx_autodoc_typehints.py ..............................................................................................................................F............... [ 96%]
...........                                                                                                                                                                           [ 99%]
tests/test_version.py .                                                                                                                                                               [100%]

========================================================================================= FAILURES ==========================================================================================
_____________________________________________________________________ test_integration[default_conf-func_with_overload] _____________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e6959090>, warning = <_io.StringIO object at 0x7f57e6959240>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f57e69c4760>, val = <function func_with_overload at 0x7f57e7a71360>, conf_run = 'default_conf'

    @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
    @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
    @pytest.mark.sphinx("text", testroot="integration")
    def test_integration(
        app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
    ) -> None:
        if isclass(val) and issubclass(val, BaseException):
            template = AUTO_EXCEPTION
        elif isclass(val):
            template = AUTO_CLASS
        else:
            template = AUTO_FUNCTION

        (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
        app.config.__dict__.update(configs[conf_run])
        app.config.__dict__.update(val.OPTIONS)
        monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
        app.build()
        assert "build succeeded" in status.getvalue()  # Build succeeded

        regexp = getattr(val, "WARNING", None)
        value = warning.getvalue().strip()
        if regexp:
            msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
            assert re.search(regexp, value), msg
        else:
            assert not value

        result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

        expected = val.EXPECTED
        if LT_PY310:
            expected = expected.replace("NewType", "NewType()")
        try:
>           assert result.strip() == dedent(expected).strip()
E           assert 'mod.func_wit...n      "None"' == 'mod.func_wit...n      "None"'
E
E             Skipping 159 identical leading characters in diff, use -v to show
E             - * **a** ("Union"["int", "str"]) -- The first thing
E             ?          --------     ^      -
E             + * **a** ("int" | "str") -- The first thing
E             ?               ^^
E               ...
E
E             ...Full output truncated (7 lines hidden), use '-vv' to show

tests/test_integration.py:1394: AssertionError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
@expected(
    """
    mod.func_with_overload(a, b)

       f does the thing. The arguments can either be ints or strings but
       they must both have the same type.

       Parameters:
          * **a** ("int" | "str") -- The first thing

          * **b** ("int" | "str") -- The second thing

       Return type:
          "None"

    """
)

--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/integration
# outdir: /tmp/pytest-of-tkloczko/pytest-140/integration/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 0 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/integration/_build/text.

# warning:

___________________________________________________________________ test_integration[default_conf-TestClassAttributeDocs] ___________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e6a06830>, warning = <_io.StringIO object at 0x7f57e6a075b0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f57e6796bf0>, val = <class 'test_integration.TestClassAttributeDocs'>, conf_run = 'default_conf'

    @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
    @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
    @pytest.mark.sphinx("text", testroot="integration")
    def test_integration(
        app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
    ) -> None:
        if isclass(val) and issubclass(val, BaseException):
            template = AUTO_EXCEPTION
        elif isclass(val):
            template = AUTO_CLASS
        else:
            template = AUTO_FUNCTION

        (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
        app.config.__dict__.update(configs[conf_run])
        app.config.__dict__.update(val.OPTIONS)
        monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
        app.build()
        assert "build succeeded" in status.getvalue()  # Build succeeded

        regexp = getattr(val, "WARNING", None)
        value = warning.getvalue().strip()
        if regexp:
            msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
            assert re.search(regexp, value), msg
        else:
            assert not value

        result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

        expected = val.EXPECTED
        if LT_PY310:
            expected = expected.replace("NewType", "NewType()")
        try:
>           assert result.strip() == dedent(expected).strip()
E           assert 'class mod.Te... An attribute' == 'class mod.Te... An attribute'
E
E             Skipping 46 identical leading characters in diff, use -v to show
E             -    code: "Optional"["CodeType"]
E             +    code: "CodeType" | "None"
E
E                     An attribute

tests/test_integration.py:1394: AssertionError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
@expected(
    """
    class mod.TestClassAttributeDocs

       A class

       code: "CodeType" | "None"

          An attribute

    """
)

--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/integration
# outdir: /tmp/pytest-of-tkloczko/pytest-140/integration/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 0 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/integration/_build/text.

# warning:

_____________________________________________________________________ test_integration[prolog_conf-func_with_overload] ______________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e6c9d5a0>, warning = <_io.StringIO object at 0x7f57e6959ea0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f57e696b1f0>, val = <function func_with_overload at 0x7f57e7a71360>, conf_run = 'prolog_conf'

    @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
    @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
    @pytest.mark.sphinx("text", testroot="integration")
    def test_integration(
        app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
    ) -> None:
        if isclass(val) and issubclass(val, BaseException):
            template = AUTO_EXCEPTION
        elif isclass(val):
            template = AUTO_CLASS
        else:
            template = AUTO_FUNCTION

        (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
        app.config.__dict__.update(configs[conf_run])
        app.config.__dict__.update(val.OPTIONS)
        monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
        app.build()
        assert "build succeeded" in status.getvalue()  # Build succeeded

        regexp = getattr(val, "WARNING", None)
        value = warning.getvalue().strip()
        if regexp:
            msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
            assert re.search(regexp, value), msg
        else:
            assert not value

        result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

        expected = val.EXPECTED
        if LT_PY310:
            expected = expected.replace("NewType", "NewType()")
        try:
>           assert result.strip() == dedent(expected).strip()
E           assert 'mod.func_wit...n      "None"' == 'mod.func_wit...n      "None"'
E
E             Skipping 159 identical leading characters in diff, use -v to show
E             - * **a** ("Union"["int", "str"]) -- The first thing
E             ?          --------     ^      -
E             + * **a** ("int" | "str") -- The first thing
E             ?               ^^
E               ...
E
E             ...Full output truncated (7 lines hidden), use '-vv' to show

tests/test_integration.py:1394: AssertionError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
@expected(
    """
    mod.func_with_overload(a, b)

       f does the thing. The arguments can either be ints or strings but
       they must both have the same type.

       Parameters:
          * **a** ("int" | "str") -- The first thing

          * **b** ("int" | "str") -- The second thing

       Return type:
          "None"

    """
)

--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/integration
# outdir: /tmp/pytest-of-tkloczko/pytest-140/integration/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 0 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/integration/_build/text.

# warning:

___________________________________________________________________ test_integration[prolog_conf-TestClassAttributeDocs] ____________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e695aa70>, warning = <_io.StringIO object at 0x7f57e695b7f0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f57e633d6c0>, val = <class 'test_integration.TestClassAttributeDocs'>, conf_run = 'prolog_conf'

    @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
    @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
    @pytest.mark.sphinx("text", testroot="integration")
    def test_integration(
        app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
    ) -> None:
        if isclass(val) and issubclass(val, BaseException):
            template = AUTO_EXCEPTION
        elif isclass(val):
            template = AUTO_CLASS
        else:
            template = AUTO_FUNCTION

        (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
        app.config.__dict__.update(configs[conf_run])
        app.config.__dict__.update(val.OPTIONS)
        monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
        app.build()
        assert "build succeeded" in status.getvalue()  # Build succeeded

        regexp = getattr(val, "WARNING", None)
        value = warning.getvalue().strip()
        if regexp:
            msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
            assert re.search(regexp, value), msg
        else:
            assert not value

        result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

        expected = val.EXPECTED
        if LT_PY310:
            expected = expected.replace("NewType", "NewType()")
        try:
>           assert result.strip() == dedent(expected).strip()
E           assert 'class mod.Te... An attribute' == 'class mod.Te... An attribute'
E
E             Skipping 46 identical leading characters in diff, use -v to show
E             -    code: "Optional"["CodeType"]
E             +    code: "CodeType" | "None"
E
E                     An attribute

tests/test_integration.py:1394: AssertionError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
@expected(
    """
    class mod.TestClassAttributeDocs

       A class

       code: "CodeType" | "None"

          An attribute

    """
)

--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/integration
# outdir: /tmp/pytest-of-tkloczko/pytest-140/integration/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 0 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/integration/_build/text.

# warning:

_____________________________________________________________________ test_integration[epilog_conf-func_with_overload] ______________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e6de7910>, warning = <_io.StringIO object at 0x7f57e66f3400>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f57e6511e70>, val = <function func_with_overload at 0x7f57e7a71360>, conf_run = 'epilog_conf'

    @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
    @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
    @pytest.mark.sphinx("text", testroot="integration")
    def test_integration(
        app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
    ) -> None:
        if isclass(val) and issubclass(val, BaseException):
            template = AUTO_EXCEPTION
        elif isclass(val):
            template = AUTO_CLASS
        else:
            template = AUTO_FUNCTION

        (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
        app.config.__dict__.update(configs[conf_run])
        app.config.__dict__.update(val.OPTIONS)
        monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
        app.build()
        assert "build succeeded" in status.getvalue()  # Build succeeded

        regexp = getattr(val, "WARNING", None)
        value = warning.getvalue().strip()
        if regexp:
            msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
            assert re.search(regexp, value), msg
        else:
            assert not value

        result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

        expected = val.EXPECTED
        if LT_PY310:
            expected = expected.replace("NewType", "NewType()")
        try:
>           assert result.strip() == dedent(expected).strip()
E           assert 'mod.func_wit...n      "None"' == 'mod.func_wit...n      "None"'
E
E             Skipping 159 identical leading characters in diff, use -v to show
E             - * **a** ("Union"["int", "str"]) -- The first thing
E             ?          --------     ^      -
E             + * **a** ("int" | "str") -- The first thing
E             ?               ^^
E               ...
E
E             ...Full output truncated (7 lines hidden), use '-vv' to show

tests/test_integration.py:1394: AssertionError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
@expected(
    """
    mod.func_with_overload(a, b)

       f does the thing. The arguments can either be ints or strings but
       they must both have the same type.

       Parameters:
          * **a** ("int" | "str") -- The first thing

          * **b** ("int" | "str") -- The second thing

       Return type:
          "None"

    """
)

--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/integration
# outdir: /tmp/pytest-of-tkloczko/pytest-140/integration/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 0 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/integration/_build/text.

# warning:

___________________________________________________________________ test_integration[epilog_conf-TestClassAttributeDocs] ____________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e6a06c20>, warning = <_io.StringIO object at 0x7f57e6c9e5f0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f57e61ba2f0>, val = <class 'test_integration.TestClassAttributeDocs'>, conf_run = 'epilog_conf'

    @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
    @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
    @pytest.mark.sphinx("text", testroot="integration")
    def test_integration(
        app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
    ) -> None:
        if isclass(val) and issubclass(val, BaseException):
            template = AUTO_EXCEPTION
        elif isclass(val):
            template = AUTO_CLASS
        else:
            template = AUTO_FUNCTION

        (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
        app.config.__dict__.update(configs[conf_run])
        app.config.__dict__.update(val.OPTIONS)
        monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
        app.build()
        assert "build succeeded" in status.getvalue()  # Build succeeded

        regexp = getattr(val, "WARNING", None)
        value = warning.getvalue().strip()
        if regexp:
            msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
            assert re.search(regexp, value), msg
        else:
            assert not value

        result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

        expected = val.EXPECTED
        if LT_PY310:
            expected = expected.replace("NewType", "NewType()")
        try:
>           assert result.strip() == dedent(expected).strip()
E           assert 'class mod.Te... An attribute' == 'class mod.Te... An attribute'
E
E             Skipping 46 identical leading characters in diff, use -v to show
E             -    code: "Optional"["CodeType"]
E             +    code: "CodeType" | "None"
E
E                     An attribute

tests/test_integration.py:1394: AssertionError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
@expected(
    """
    class mod.TestClassAttributeDocs

       A class

       code: "CodeType" | "None"

          An attribute

    """
)

--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/integration
# outdir: /tmp/pytest-of-tkloczko/pytest-140/integration/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 0 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/integration/_build/text.

# warning:

_____________________________________________________________________ test_integration[bothlog_conf-func_with_overload] _____________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e695bd00>, warning = <_io.StringIO object at 0x7f57e695ab00>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f57e5f47e80>, val = <function func_with_overload at 0x7f57e7a71360>, conf_run = 'bothlog_conf'

    @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
    @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
    @pytest.mark.sphinx("text", testroot="integration")
    def test_integration(
        app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
    ) -> None:
        if isclass(val) and issubclass(val, BaseException):
            template = AUTO_EXCEPTION
        elif isclass(val):
            template = AUTO_CLASS
        else:
            template = AUTO_FUNCTION

        (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
        app.config.__dict__.update(configs[conf_run])
        app.config.__dict__.update(val.OPTIONS)
        monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
        app.build()
        assert "build succeeded" in status.getvalue()  # Build succeeded

        regexp = getattr(val, "WARNING", None)
        value = warning.getvalue().strip()
        if regexp:
            msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
            assert re.search(regexp, value), msg
        else:
            assert not value

        result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

        expected = val.EXPECTED
        if LT_PY310:
            expected = expected.replace("NewType", "NewType()")
        try:
>           assert result.strip() == dedent(expected).strip()
E           assert 'mod.func_wit...n      "None"' == 'mod.func_wit...n      "None"'
E
E             Skipping 159 identical leading characters in diff, use -v to show
E             - * **a** ("Union"["int", "str"]) -- The first thing
E             ?          --------     ^      -
E             + * **a** ("int" | "str") -- The first thing
E             ?               ^^
E               ...
E
E             ...Full output truncated (7 lines hidden), use '-vv' to show

tests/test_integration.py:1394: AssertionError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
@expected(
    """
    mod.func_with_overload(a, b)

       f does the thing. The arguments can either be ints or strings but
       they must both have the same type.

       Parameters:
          * **a** ("int" | "str") -- The first thing

          * **b** ("int" | "str") -- The second thing

       Return type:
          "None"

    """
)

--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/integration
# outdir: /tmp/pytest-of-tkloczko/pytest-140/integration/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 0 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/integration/_build/text.

# warning:

___________________________________________________________________ test_integration[bothlog_conf-TestClassAttributeDocs] ___________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e6958e50>, warning = <_io.StringIO object at 0x7f57e695b0a0>
monkeypatch = <_pytest.monkeypatch.MonkeyPatch object at 0x7f57e609c850>, val = <class 'test_integration.TestClassAttributeDocs'>, conf_run = 'bothlog_conf'

    @pytest.mark.parametrize("val", [x for x in globals().values() if hasattr(x, "EXPECTED")])
    @pytest.mark.parametrize("conf_run", ["default_conf", "prolog_conf", "epilog_conf", "bothlog_conf"])
    @pytest.mark.sphinx("text", testroot="integration")
    def test_integration(
        app: SphinxTestApp, status: StringIO, warning: StringIO, monkeypatch: pytest.MonkeyPatch, val: Any, conf_run: str
    ) -> None:
        if isclass(val) and issubclass(val, BaseException):
            template = AUTO_EXCEPTION
        elif isclass(val):
            template = AUTO_CLASS
        else:
            template = AUTO_FUNCTION

        (Path(app.srcdir) / "index.rst").write_text(template.format(val.__name__))
        app.config.__dict__.update(configs[conf_run])
        app.config.__dict__.update(val.OPTIONS)
        monkeypatch.setitem(sys.modules, "mod", sys.modules[__name__])
        app.build()
        assert "build succeeded" in status.getvalue()  # Build succeeded

        regexp = getattr(val, "WARNING", None)
        value = warning.getvalue().strip()
        if regexp:
            msg = f"Regex pattern did not match.\n Regex: {regexp!r}\n Input: {value!r}"
            assert re.search(regexp, value), msg
        else:
            assert not value

        result = (Path(app.srcdir) / "_build/text/index.txt").read_text()

        expected = val.EXPECTED
        if LT_PY310:
            expected = expected.replace("NewType", "NewType()")
        try:
>           assert result.strip() == dedent(expected).strip()
E           assert 'class mod.Te... An attribute' == 'class mod.Te... An attribute'
E
E             Skipping 46 identical leading characters in diff, use -v to show
E             -    code: "Optional"["CodeType"]
E             +    code: "CodeType" | "None"
E
E                     An attribute

tests/test_integration.py:1394: AssertionError
----------------------------------------------------------------------------------- Captured stdout call ------------------------------------------------------------------------------------
@expected(
    """
    class mod.TestClassAttributeDocs

       A class

       code: "CodeType" | "None"

          An attribute

    """
)

--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/integration
# outdir: /tmp/pytest-of-tkloczko/pytest-140/integration/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 0 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/integration/_build/text.

# warning:

_____________________________________________________________________ test_always_document_param_types[doc_param_type] ______________________________________________________________________

app = <SphinxTestApp buildername='text'>, status = <_io.StringIO object at 0x7f57e6a069e0>, warning = <_io.StringIO object at 0x7f57e6a06950>, always_document_param_types = True

    @pytest.mark.parametrize("always_document_param_types", [True, False], ids=["doc_param_type", "no_doc_param_type"])
    @pytest.mark.sphinx("text", testroot="dummy")
    @patch("sphinx.writers.text.MAXWIDTH", 2000)
    def test_always_document_param_types(
        app: SphinxTestApp,
        status: StringIO,
        warning: StringIO,
        always_document_param_types: bool,
    ) -> None:
        set_python_path()

        app.config.always_document_param_types = always_document_param_types  # type: ignore[attr-defined] # create flag
        app.config.autodoc_mock_imports = ["mailbox"]  # type: ignore[attr-defined] # create flag

        # Prevent "document isn't included in any toctree" warnings
        for f in Path(app.srcdir).glob("*.rst"):
            f.unlink()
        (Path(app.srcdir) / "index.rst").write_text(
            dedent(
                """
                .. autofunction:: dummy_module.undocumented_function

                .. autoclass:: dummy_module.DataClass
                    :undoc-members:
                    :special-members: __init__
                """,
            ),
        )

        app.build()

        assert "build succeeded" in status.getvalue()  # Build succeeded
        assert not warning.getvalue().strip()

        format_args = {}
        for indentation_level in range(2):
            key = f"undoc_params_{indentation_level}"
            if always_document_param_types:
                format_args[key] = indent('\n\n   Parameters:\n      **x** ("int")', "   " * indentation_level)
            else:
                format_args[key] = ""

        contents = (Path(app.srcdir) / "_build/text/index.txt").read_text()
        expected_contents = """\
        dummy_module.undocumented_function(x)

           Hi{undoc_params_0}

           Return type:
              "str"

        class dummy_module.DataClass(x)

           Class docstring.{undoc_params_0}

           __init__(x){undoc_params_1}
        """
        expected_contents = dedent(expected_contents).format(**format_args)
>       assert contents == expected_contents
E       assert 'dummy_module... ("int") --\n' == 'dummy_module...x** ("int")\n'
E
E         Skipping 70 identical leading characters in diff, use -v to show
E         - ** ("int")
E         + ** ("int") --
E         ?           +++
E
E              Return type:...
E
E         ...Full output truncated (17 lines hidden), use '-vv' to show

tests/test_sphinx_autodoc_typehints.py:615: AssertionError
--------------------------------------------------------------------------------- Captured stdout teardown ----------------------------------------------------------------------------------
# testroot: root
# builder: text
# srcdir: /tmp/pytest-of-tkloczko/pytest-140/dummy
# outdir: /tmp/pytest-of-tkloczko/pytest-140/dummy/_build/text
# status:
Running Sphinx v7.2.6
building [mo]: targets for 0 po files that are out of date
writing output...
building [text]: targets for 5 source files that are out of date
updating environment: [new config] 1 added, 0 changed, 0 removed
reading sources... [100%] index
looking for now-outdated files... none found
pickling environment... done
checking consistency... done
preparing documents... done
copying assets... done
writing output... [100%] index
build succeeded.

The text files are in ../../../../../tmp/pytest-of-tkloczko/pytest-140/dummy/_build/text.

# warning:

===================================================================================== warnings summary ======================================================================================
tests/conftest.py:10
  /home/tkloczko/rpmbuild/BUILD/sphinx-autodoc-typehints-2.1.0/tests/conftest.py:10: RemovedInSphinx90Warning: 'sphinx.testing.path' is deprecated. Use 'os.path' or 'pathlib' instead.
    from sphinx.testing.path import path

tests/test_integration.py: 112 warnings
tests/test_sphinx_autodoc_typehints.py: 82 warnings
  /home/tkloczko/rpmbuild/BUILDROOT/python-sphinx-autodoc-typehints-2.1.0-2.fc37.x86_64/usr/lib/python3.10/site-packages/sphinx_autodoc_typehints/__init__.py:806: DeprecationWarning: The frontend.OptionParser class will be replaced by a subclass of argparse.ArgumentParser in Docutils 0.21 or later.
    settings = OptionParser(components=(RSTParser,)).get_default_values()

tests/test_integration.py: 7728 warnings
tests/test_sphinx_autodoc_typehints.py: 5658 warnings
  /usr/lib64/python3.10/optparse.py:1000: DeprecationWarning: The frontend.Option class will be removed in Docutils 0.21 or later.
    option = self.option_class(*args, **kwargs)

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
================================================================================== short test summary info ==================================================================================
FAILED tests/test_integration.py::test_integration[default_conf-func_with_overload] - assert 'mod.func_wit...n      "None"' == 'mod.func_wit...n      "None"'
FAILED tests/test_integration.py::test_integration[default_conf-TestClassAttributeDocs] - assert 'class mod.Te... An attribute' == 'class mod.Te... An attribute'
FAILED tests/test_integration.py::test_integration[prolog_conf-func_with_overload] - assert 'mod.func_wit...n      "None"' == 'mod.func_wit...n      "None"'
FAILED tests/test_integration.py::test_integration[prolog_conf-TestClassAttributeDocs] - assert 'class mod.Te... An attribute' == 'class mod.Te... An attribute'
FAILED tests/test_integration.py::test_integration[epilog_conf-func_with_overload] - assert 'mod.func_wit...n      "None"' == 'mod.func_wit...n      "None"'
FAILED tests/test_integration.py::test_integration[epilog_conf-TestClassAttributeDocs] - assert 'class mod.Te... An attribute' == 'class mod.Te... An attribute'
FAILED tests/test_integration.py::test_integration[bothlog_conf-func_with_overload] - assert 'mod.func_wit...n      "None"' == 'mod.func_wit...n      "None"'
FAILED tests/test_integration.py::test_integration[bothlog_conf-TestClassAttributeDocs] - assert 'class mod.Te... An attribute' == 'class mod.Te... An attribute'
FAILED tests/test_sphinx_autodoc_typehints.py::test_always_document_param_types[doc_param_type] - assert 'dummy_module... ("int") --\n' == 'dummy_module...x** ("int")\n'
====================================================================== 9 failed, 297 passed, 13581 warnings in 19.02s =======================================================================
And here is patch generated by pyupgrade:
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -149,10 +149,10 @@
     :param z: baz
     """

-    def __init__(self, x: bool, y: int, z: Optional[str] = None) -> None:  # noqa: UP007
+    def __init__(self, x: bool, y: int, z: str | None = None) -> None:  # noqa: UP007
         pass

-    def a_method(self, x: bool, y: int, z: Optional[str] = None) -> str:  # noqa: UP007
+    def a_method(self, x: bool, y: int, z: str | None = None) -> str:  # noqa: UP007
         """
         Method docstring.

@@ -183,7 +183,7 @@
         """

     @classmethod
-    def a_classmethod(cls, x: bool, y: int, z: Optional[str] = None) -> str:  # noqa: UP007
+    def a_classmethod(cls, x: bool, y: int, z: str | None = None) -> str:  # noqa: UP007
         """
         Classmethod docstring.

@@ -193,7 +193,7 @@
         """

     @staticmethod
-    def a_staticmethod(x: bool, y: int, z: Optional[str] = None) -> str:  # noqa: UP007
+    def a_staticmethod(x: bool, y: int, z: str | None = None) -> str:  # noqa: UP007
         """
         Staticmethod docstring.

@@ -271,7 +271,7 @@
       bytes
 """,
 )
-def function(x: bool, y: int, z_: Optional[str] = None) -> str:  # noqa: ARG001, UP007
+def function(x: bool, y: int, z_: str | None = None) -> str:  # noqa: ARG001, UP007
     """
     Function docstring.

@@ -648,7 +648,7 @@
       "None"
 """,
 )
-def func_with_overload(a: Union[int, str], b: Union[int, str]) -> None:  # noqa: ARG001, UP007
+def func_with_overload(a: int | str, b: int | str) -> None:  # noqa: ARG001, UP007
     """
     f does the thing. The arguments can either be ints or strings but they must
     both have the same type.
@@ -676,7 +676,7 @@
 class TestClassAttributeDocs:
     """A class"""

-    code: Optional[CodeType]  # noqa: UP007
+    code: CodeType | None  # noqa: UP007
     """An attribute"""

It would be good to adapt code to be able filter it over pyupgrade or open the the ticket that this tools messes with the module code.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant