Skip to content

Commit

Permalink
feat: support snapshots in doc tests (#525)
Browse files Browse the repository at this point in the history
  • Loading branch information
iamogbz authored May 12, 2022
1 parent 09ade1b commit 97256e3
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 7 deletions.
5 changes: 4 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ dist,
'''

[tool.pytest.ini_options]
addopts = '-p syrupy'
addopts = '-p syrupy --doctest-modules'
testpaths = [
"tests",
]

[tool.coverage.run]
source = ['./src']
Expand Down
2 changes: 1 addition & 1 deletion script/bootstrap
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ fi
if [[ -z $CI ]]; then
# FIXME: There must be a better way to install this per project rather than globally?
curl -sSL https://install.python-poetry.org | python3 - --version 1.2.0a2
poetry shell
. ./.venv/bin/activate
fi

if [[ -z $SKIP_DEPS ]]; then
Expand Down
41 changes: 36 additions & 5 deletions src/syrupy/location.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,23 +23,47 @@ class PyTestLocation:
filepath: str = field(init=False)

def __post_init__(self) -> None:
if self.is_doctest:
return self.__attrs_post_init_doc__()
self.__attrs_post_init_def__()

def __attrs_post_init_def__(self) -> None:
self.filepath = getattr(self._node, "fspath") # noqa: B009
obj = getattr(self._node, "obj") # noqa: B009
self.modulename = obj.__module__
self.methodname = obj.__name__
self.nodename = getattr(self._node, "name", None)
self.testname = self.nodename or self.methodname

def __attrs_post_init_doc__(self) -> None:
doctest = getattr(self._node, "dtest") # noqa: B009
self.filepath = doctest.filename
test_relfile, test_node = self.nodeid.split(PYTEST_NODE_SEP)
test_relpath = Path(test_relfile)
self.modulename = ".".join([*test_relpath.parent.parts, test_relpath.stem])
self.nodename = test_node.replace(f"{self.modulename}.", "")
self.testname = self.nodename or self.methodname

@property
def classname(self) -> Optional[str]:
if self.is_doctest:
return None
return ".".join(self.nodeid.split(PYTEST_NODE_SEP)[1:-1]) or None

@property
def nodeid(self) -> str:
"""
Pytest node names contain file path and module members delimited by `::`
Example tests/grouping/test_file.py::TestClass::TestSubClass::test_method
Examples:
- tests/grouping/test_file.py::TestClass::TestSubClass::test_method
- tests/grouping/test_file.py::DocTestClass.doc_test_method
- tests/grouping/test_file.py::doctestfile.txt
:raises: `AttributeError` if node has no node id
:return: test node id
"""
nodeid: Optional[str] = getattr(self._node, "nodeid", None)
if nodeid is None:
return None
return ".".join(nodeid.split(PYTEST_NODE_SEP)[1:-1]) or None
return str(getattr(self._node, "nodeid")) # noqa: B009

@property
def filename(self) -> str:
Expand All @@ -51,6 +75,13 @@ def snapshot_name(self) -> str:
return f"{self.classname}.{self.testname}"
return str(self.testname)

@property
def is_doctest(self) -> bool:
return self.__is_doctest(self._node)

def __is_doctest(self, node: "pytest.Item") -> bool:
return hasattr(node, "dtest")

def __valid_id(self, name: str) -> str:
"""
Take characters from the name while the result would be a valid python
Expand Down
37 changes: 37 additions & 0 deletions tests/syrupy/__snapshots__/test_doctest.ambr
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# name: DocTestClass
DocTestClass(
obj_attr='test class attr',
)
# ---
# name: DocTestClass.1
DocTestClass(
obj_attr='test class attr',
)
# ---
# name: DocTestClass.NestedDocTestClass
NestedDocTestClass(
nested_obj_attr='nested doc test class attr',
)
# ---
# name: DocTestClass.NestedDocTestClass.doctest_method
'nested doc test method return value'
# ---
# name: DocTestClass.doctest_method
'doc test method return value'
# ---
# name: doctest_fn
'doc test fn return value'
# ---
# name: test_doctest.txt
'There must be a break after every snapshot assertion'
# ---
# name: test_doctest.txt.1
'constant value'
# ---
# name: test_doctest.txt.2
set({
1,
2,
3,
})
# ---
42 changes: 42 additions & 0 deletions tests/syrupy/test_doctest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
def doctest_fn():
"""a doctest in a function docstring
>>> doctest_fn() == getfixture('snapshot')
True
"""
return "doc test fn return value"


class DocTestClass:
"""
>>> DocTestClass() == getfixture('snapshot')
True
a doctest in a class docstring
>>> DocTestClass() == getfixture('snapshot')
True
"""

obj_attr = "test class attr"

def doctest_method(self):
"""a doctest in a method docstring
>>> DocTestClass().doctest_method() == getfixture('snapshot')
True
"""
return "doc test method return value"

class NestedDocTestClass:
"""a doctest in a nested class docstring
>>> DocTestClass.NestedDocTestClass() == getfixture('snapshot')
True
"""

nested_obj_attr = "nested doc test class attr"

def doctest_method(self):
"""a doctest in a nested method docstring
>>> nested_obj = DocTestClass.NestedDocTestClass()
>>> nested_obj.doctest_method() == getfixture('snapshot')
True
"""
return "nested doc test method return value"
14 changes: 14 additions & 0 deletions tests/syrupy/test_doctest.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
>>> "There must be a break after every snapshot assertion" == getfixture('snapshot')
True

doctest x

doctest y
>>> y = "constant value"
>>> y == getfixture('snapshot')
True

doctest z
>>> z = {1, 2, 3}
>>> z == getfixture('snapshot')
True

0 comments on commit 97256e3

Please sign in to comment.