Skip to content

Commit

Permalink
Exception handling tests
Browse files Browse the repository at this point in the history
  • Loading branch information
adriangb committed Sep 30, 2021
1 parent 4e12a4e commit 52bd5b5
Show file tree
Hide file tree
Showing 2 changed files with 256 additions and 1 deletion.
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "di"
version = "0.4.4"
version = "0.4.5"
description = "Autowiring dependency injection"
authors = ["Adrian Garcia Badaracco <[email protected]>"]
readme = "README.md"
Expand Down
255 changes: 255 additions & 0 deletions tests/test_exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
from dataclasses import dataclass, field
from typing import AsyncGenerator, Dict, Generator

import pytest

from di import Container, Dependant, Depends


@dataclass
class Recorder:
caught: Dict[str, bool] = field(default_factory=dict)


class MyException(Exception):
...


def dep1(rec: Recorder) -> Generator[None, None, None]:
try:
yield
except MyException:
rec.caught["dep1"] = True


def dep2(rec: Recorder) -> Generator[None, None, None]:
try:
yield
except MyException:
rec.caught["dep2"] = True


async def async_dep1(rec: Recorder) -> AsyncGenerator[None, None]:
try:
yield
except MyException:
rec.caught["async_dep1"] = True


async def async_dep2(rec: Recorder) -> AsyncGenerator[None, None]:
try:
yield
except MyException:
rec.caught["async_dep1"] = True


def test_dependency_can_catch_exception_single_sync() -> None:
def collector(one: None = Depends(dep1)) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
container.execute_sync(container.solve(Dependant(collector)))
assert rec.caught == {"dep1": True}


@pytest.mark.anyio
async def test_dependency_can_catch_exception_single_async() -> None:
def collector(one: None = Depends(async_dep1)) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
await container.execute_async(container.solve(Dependant(collector)))
assert rec.caught == {"async_dep1": True}


def test_dependency_can_catch_exception_concurrent_sync() -> None:
def collector(one: None = Depends(dep1), two: None = Depends(dep2)) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
container.execute_sync(container.solve(Dependant(collector)))
# one of the dependencies catches and swallows the exception
# so the other one nevers sees it
# there is no promises as to the order, both cases are valid
assert rec.caught == {"dep1": True} or rec.caught == {"dep2": True}


@pytest.mark.anyio
async def test_dependency_can_catch_exception_concurrent_async() -> None:
def collector(
one: None = Depends(async_dep1), two: None = Depends(async_dep2)
) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
await container.execute_async(container.solve(Dependant(collector)))
# one of the dependencies catches and swallows the exception
# so the other one nevers sees it
# there is no promises as to the order, both cases are valid
assert rec.caught == {"async_dep1": True} or rec.caught == {"async_dep2": True}


@pytest.mark.anyio
async def test_dependency_can_catch_exception_concurrent_mixed() -> None:
def collector(one: None = Depends(async_dep1), two: None = Depends(dep2)) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
await container.execute_async(container.solve(Dependant(collector)))
# one of the dependencies catches and swallows the exception
# so the other one nevers sees it
# there is no promises as to the order, both cases are valid
assert rec.caught == {"async_dep1": True} or rec.caught == {"dep2": True}


def dep1_reraise(rec: Recorder) -> Generator[None, None, None]:
try:
yield
except MyException:
rec.caught["dep1_reraise"] = True
raise


def dep2_reraise(rec: Recorder) -> Generator[None, None, None]:
try:
yield
except MyException:
rec.caught["dep2_reraise"] = True
raise


async def async_dep1_reraise(rec: Recorder) -> AsyncGenerator[None, None]:
try:
yield
except MyException:
rec.caught["async_dep1_reraise"] = True
raise


async def async_dep2_reraise(rec: Recorder) -> AsyncGenerator[None, None]:
try:
yield
except MyException:
rec.caught["async_dep2_reraise"] = True
raise


def test_dependency_can_catch_exception_single_sync_reraise() -> None:
def collector(one: None = Depends(dep1_reraise)) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
try:
container.execute_sync(container.solve(Dependant(collector)))
except MyException:
pass
else:
raise AssertionError("MyException should have been re-raised")
assert rec.caught == {"dep1_reraise": True}


@pytest.mark.anyio
async def test_dependency_can_catch_exception_single_async_reraise() -> None:
def collector(one: None = Depends(async_dep1_reraise)) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
try:
await container.execute_async(container.solve(Dependant(collector)))
except MyException:
pass
else:
raise AssertionError("MyException should have been re-raised")
assert rec.caught == {"async_dep1_reraise": True}


def test_dependency_can_catch_exception_concurrent_sync_reraise() -> None:
def collector(
one: None = Depends(dep1_reraise), two: None = Depends(dep2_reraise)
) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
try:
container.execute_sync(container.solve(Dependant(collector)))
except MyException:
pass
else:
raise AssertionError("MyException should have been re-raised")
assert rec.caught == {"dep1_reraise": True, "dep2_reraise": True}


@pytest.mark.anyio
async def test_dependency_can_catch_exception_concurrent_async_reraise() -> None:
def collector(
one: None = Depends(async_dep1_reraise), two: None = Depends(async_dep2_reraise)
) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
try:
await container.execute_async(container.solve(Dependant(collector)))
except MyException:
pass
else:
raise AssertionError("MyException should have been re-raised")
assert rec.caught == {"async_dep1_reraise": True, "async_dep2_reraise": True}


@pytest.mark.anyio
async def test_dependency_can_catch_exception_concurrent_mixed_reraise() -> None:
def collector(
one: None = Depends(async_dep1_reraise), two: None = Depends(dep2_reraise)
) -> None:
raise MyException

container = Container()
rec = Recorder()
container.bind(Dependant(lambda: rec), Recorder)
try:
await container.execute_async(container.solve(Dependant(collector)))
except MyException:
pass
else:
raise AssertionError("MyException should have been re-raised")
assert rec.caught == {"async_dep1_reraise": True, "dep2_reraise": True}


def test_deep_reraise() -> None:
def leaf() -> Generator[None, None, None]:
try:
yield
except MyException:
pass
else:
raise AssertionError("Exception did not propagate")

def parent(child: None = Depends(leaf)) -> Generator[None, None, None]:
try:
yield
except MyException:
raise

def root(child: None = Depends(parent)) -> None:
raise MyException

container = Container()
container.execute_sync(container.solve(Dependant(root)))

0 comments on commit 52bd5b5

Please sign in to comment.