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
9 changes: 8 additions & 1 deletion src/pytest_markdown_docs/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -357,7 +357,11 @@ def find_object_tests_recursive(
_visited_objects.add(id(object))
docstr = inspect.getdoc(object)

for member_name, member in inspect.getmembers(object):
# Walk __dict__ like doctest.DocTestFinder does, instead of
# inspect.getmembers(), so that descriptors are never evaluated.
for member in list(getattr(object, "__dict__", {}).values()):
if isinstance(member, (staticmethod, classmethod)):
member = member.__func__
if (
inspect.isclass(member)
or inspect.isfunction(member)
Expand Down Expand Up @@ -438,6 +442,9 @@ def pytest_collect_file(
only_docstrings = parent.config.option.markdowndocs_only_docstrings
only_text = parent.config.option.markdowndocs_only_text
if pathlib_path.suffix == ".py" and not only_text:
if pathlib_path.name == "__main__.py":
# importing it would clash with the running pytest's __main__
return None
return MarkdownDocstringCodeModule.from_parent(parent, path=pathlib_path)
elif pathlib_path.suffix in (".md", ".mdx", ".svx") and not only_docstrings:
return MarkdownTextFile.from_parent(parent, path=pathlib_path)
Expand Down
50 changes: 50 additions & 0 deletions tests/plugin_test.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import re

import pytest

from _pytest.pytester import LineMatcher

import pytest_markdown_docs # hack: used for storing a side effect in one of the tests
Expand Down Expand Up @@ -1165,3 +1167,51 @@ def test_top_level_await_requires_pytest_asyncio(testdir):
result = testdir.runpytest("--markdown-docs")
result.assert_outcomes(failed=1)
result.stdout.fnmatch_lines(["*pytest-asyncio>=1.1.0*"])


def test_docstring_collection_does_not_evaluate_descriptors(testdir):
testdir.makepyfile(
"""
class Raising:
def __get__(self, instance, owner=None):
raise RuntimeError("evaluated during collection")


class Hostile:
attribute = Raising()

@staticmethod
def static():
\"\"\"
```python
assert True
```
\"\"\"

@classmethod
def klass(cls):
\"\"\"
```python
assert True
```
\"\"\"
"""
)
result = testdir.runpytest("--markdown-docs")
result.assert_outcomes(passed=2)


def test_main_py_is_skipped(testdir):
testdir.makepyfile(
__main__="""
def main():
\"\"\"
```python
assert True
```
\"\"\"
"""
)
result = testdir.runpytest("--markdown-docs")
result.assert_outcomes()
assert result.ret == pytest.ExitCode.NO_TESTS_COLLECTED
Loading