Skip to content

Commit

Permalink
[feature] exception handling in code sections
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanhoelzl committed Dec 22, 2021
1 parent 75ee4a1 commit 87e0424
Show file tree
Hide file tree
Showing 3 changed files with 45 additions and 10 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,11 @@ assert value is True
```python {"skip": true}
raise Exception("this section should not run")
```
````

### Exception Handling
````md
```python {"raises": "RuntimeError"}
raise RuntimeError("this section should pass")
```
````
35 changes: 25 additions & 10 deletions pytest_docfiles/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import ast
import itertools
import json
import types
from pathlib import Path
from typing import Any, Dict, Generator, List, Optional, Union

Expand Down Expand Up @@ -51,18 +52,31 @@ def __init__( # pylint: disable=too-many-arguments
fixtures: List[str],
scope: Dict[str, Any],
skip: bool,
raises: Optional[str],
):
super().__init__(name, parent)
self.lineno = lineno
self.source = source
self.scope = scope
self.skip = skip
self.fixtures = fixtures
self.raises = raises
self.own_markers = [Mark("usefixtures", args=tuple(fixtures), kwargs={})]

self.funcargs = {} # type: ignore
self._fixtureinfo = None

def runtest(self) -> None:
if self.skip:
pytest.skip()
self._setup_fixtures()
self._execute()

def _setup_fixtures(self) -> None:
fixture_request = self._setup_fixture_request()
for fixture in self.fixtures:
self.scope[fixture] = fixture_request.getfixturevalue(fixture)

def _setup_fixture_request(self) -> FixtureRequest:
fixturemanager = (
self.session._fixturemanager # pylint: disable=protected-access
Expand All @@ -74,22 +88,22 @@ def _setup_fixture_request(self) -> FixtureRequest:
fixture_request._fillfixtures() # pylint: disable=protected-access
return fixture_request

def runtest(self) -> None:
if self.skip:
pytest.skip()

def _compile(self) -> types.CodeType:
tree = ast.parse(self.source)

rewrite_asserts(
tree, self.source.encode("utf-8"), str(self.fspath), self.config
)
return compile(tree, str(self.fspath), "exec", dont_inherit=True) # type: ignore

compiled = compile(tree, str(self.fspath), "exec", dont_inherit=True)
fixture_request = self._setup_fixture_request()
for fixture in self.fixtures:
self.scope[fixture] = fixture_request.getfixturevalue(fixture)
def _execute(self) -> None:
try:
exec(self._compile(), self.scope) # pylint: disable=exec-used
except Exception as exception: # pylint: disable=broad-except
self._raise(exception)

exec(compiled, self.scope) # pylint: disable=exec-used
def _raise(self, exception: Exception) -> None:
if type(exception).__name__ != self.raises:
raise exception

def repr_failure(
self,
Expand Down Expand Up @@ -139,6 +153,7 @@ def collect(self) -> Generator[pytest.Item, None, None]:
fixtures=parsed_args.get("fixtures", []),
scope=scopes.setdefault(parsed_args.get("scope"), {}),
skip=parsed_args.get("skip", False),
raises=parsed_args.get("raises"),
)


Expand Down
13 changes: 13 additions & 0 deletions tests/test_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,16 @@ def test_skip(pytester: pytest.Pytester) -> None:
)
result = pytester.runpytest("--docfiles", "-k", "doc.md")
assert result.ret == 0


def test_exception_handling(pytester: pytest.Pytester) -> None:
pytester.makefile(
".md",
doc=joined(
"""```python {"raises": "RuntimeError"}""",
"raise RuntimeError('should pass')",
"```",
),
)
result = pytester.runpytest("--docfiles", "-k", "doc.md")
assert result.ret == 0

0 comments on commit 87e0424

Please sign in to comment.