Skip to content

Commit

Permalink
Merge pull request #50 from njsmith/fixture-rework
Browse files Browse the repository at this point in the history
Rework fixtures
  • Loading branch information
njsmith authored Aug 24, 2018
2 parents c905f15 + 98ba48b commit dd27d70
Show file tree
Hide file tree
Showing 17 changed files with 892 additions and 214 deletions.
3 changes: 3 additions & 0 deletions ci/rtd-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@
sphinx >= 1.6.1
sphinx_rtd_theme
sphinxcontrib-trio
# Workaround for this weird issue:
# https://travis-ci.org/python-trio/pytest-trio/jobs/407495415
attrs >= 17.4.0
3 changes: 3 additions & 0 deletions pytest_trio/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
"""Top-level package for pytest-trio."""

from ._version import __version__
from .plugin import trio_fixture

__all__ = ["trio_fixture"]
15 changes: 15 additions & 0 deletions pytest_trio/_tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import pytest


def enable_trio_mode_via_pytest_ini(testdir):
testdir.makefile(".ini", pytest="[pytest]\ntrio_mode = true\n")


def enable_trio_mode_via_conftest_py(testdir):
testdir.makeconftest("from pytest_trio.enable_trio_mode import *")


enable_trio_mode = pytest.mark.parametrize(
"enable_trio_mode",
[enable_trio_mode_via_pytest_ini, enable_trio_mode_via_conftest_py]
)
20 changes: 12 additions & 8 deletions pytest_trio/_tests/test_async_yield_fixture.py
Original file line number Diff line number Diff line change
Expand Up @@ -317,39 +317,43 @@ def test_async_yield_fixture_crashed_teardown_allow_other_teardowns(
import trio
from async_generator import async_generator, yield_
events = []
setup_events = set()
teardown_events = set()
@pytest.fixture
@async_generator
async def good_fixture():
async with trio.open_nursery() as nursery:
events.append('good_fixture setup')
setup_events.add('good_fixture setup')
await yield_(None)
events.append('good_fixture teardown')
teardown_events.add('good_fixture teardown')
@pytest.fixture
@async_generator
async def bad_fixture():
async with trio.open_nursery() as nursery:
events.append('bad_fixture setup')
setup_events.add('bad_fixture setup')
await yield_(None)
events.append('bad_fixture teardown')
teardown_events.add('bad_fixture teardown')
raise RuntimeError('Crash during fixture teardown')
def test_before():
assert not events
assert not setup_events
assert not teardown_events
@pytest.mark.trio
async def test_actual_test(bad_fixture, good_fixture):
pass
def test_after():
assert events == [
assert setup_events == {
'good_fixture setup',
'bad_fixture setup',
}
assert teardown_events == {
'bad_fixture teardown',
'good_fixture teardown',
]
}
"""
)
)
Expand Down
2 changes: 1 addition & 1 deletion pytest_trio/_tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_check_async_test_called():
"""
)

result = testdir.runpytest()
result = testdir.runpytest("-s")

result.assert_outcomes(passed=2)

Expand Down
37 changes: 37 additions & 0 deletions pytest_trio/_tests/test_contextvars.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pytest
from pytest_trio import trio_fixture

import contextvars

cv = contextvars.ContextVar("cv", default=None)


@trio_fixture
def cv_checker():
assert cv.get() is None
yield
assert cv.get() is None


@trio_fixture
def cv_setter(cv_checker):
assert cv.get() is None
token = cv.set("cv_setter")
yield
assert cv.get() == "cv_setter2"
cv.reset(token)
assert cv.get() is None


@trio_fixture
def cv_setter2(cv_setter):
assert cv.get() == "cv_setter"
# Intentionally leak, so can check that this is visible back in cv_setter
cv.set("cv_setter2")
yield
assert cv.get() == "cv_setter2"


@pytest.mark.trio
async def test_contextvars(cv_setter2):
assert cv.get() == "cv_setter2"
119 changes: 119 additions & 0 deletions pytest_trio/_tests/test_fixture_mistakes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
import pytest
from pytest_trio import trio_fixture

from .helpers import enable_trio_mode


def test_trio_fixture_with_non_trio_test(testdir):
testdir.makepyfile(
"""
import trio
from pytest_trio import trio_fixture
import pytest
@trio_fixture
def trio_time():
return trio.current_time()
@pytest.fixture
def indirect_trio_time(trio_time):
return trio_time + 1
@pytest.mark.trio
async def test_async(mock_clock, trio_time, indirect_trio_time):
assert trio_time == 0
assert indirect_trio_time == 1
def test_sync(trio_time):
pass
def test_sync_indirect(indirect_trio_time):
pass
"""
)

result = testdir.runpytest()

result.assert_outcomes(passed=1, error=2)
result.stdout.fnmatch_lines(
["*: Trio fixtures can only be used by Trio tests*"]
)


def test_trio_fixture_with_wrong_scope_without_trio_mode(testdir):
# There's a trick here: when you have a non-function-scope fixture, it's
# not instantiated for any particular function (obviously). So... when our
# pytest_fixture_setup hook tries to check for marks, it can't normally
# see @pytest.mark.trio. So... it's actually almost impossible to have an
# async fixture get treated as a Trio fixture *and* have it be
# non-function-scope. But, class-scoped fixtures can see marks on the
# class, so this is one way (the only way?) it can happen:
testdir.makepyfile(
"""
import pytest
@pytest.fixture(scope="class")
async def async_class_fixture():
pass
@pytest.mark.trio
class TestFoo:
async def test_foo(self, async_class_fixture):
pass
"""
)

result = testdir.runpytest()

result.assert_outcomes(error=1)
result.stdout.fnmatch_lines(["*: Trio fixtures must be function-scope*"])


@enable_trio_mode
def test_trio_fixture_with_wrong_scope_in_trio_mode(testdir, enable_trio_mode):
enable_trio_mode(testdir)

testdir.makepyfile(
"""
import pytest
@pytest.fixture(scope="session")
async def async_session_fixture():
pass
async def test_whatever(async_session_fixture):
pass
"""
)

result = testdir.runpytest()

result.assert_outcomes(error=1)
result.stdout.fnmatch_lines(["*: Trio fixtures must be function-scope*"])


@enable_trio_mode
def test_async_fixture_with_sync_test_in_trio_mode(testdir, enable_trio_mode):
enable_trio_mode(testdir)

testdir.makepyfile(
"""
import pytest
@pytest.fixture
async def async_fixture():
pass
def test_whatever(async_fixture):
pass
"""
)

result = testdir.runpytest()

result.assert_outcomes(error=1)
result.stdout.fnmatch_lines(
["*: Trio fixtures can only be used by Trio tests*"]
)
18 changes: 18 additions & 0 deletions pytest_trio/_tests/test_fixture_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import pytest
from pytest_trio import trio_fixture
import trio


@trio_fixture
def fixture_with_unique_name(nursery):
nursery.start_soon(trio.sleep_forever)


@pytest.mark.trio
async def test_fixture_names(fixture_with_unique_name):
# This might be a bit fragile ... if we rearrange the nursery hierarchy
# somehow so it breaks, then we can make it more robust.
task = trio.hazmat.current_task()
assert task.name == "<test 'test_fixture_names'>"
sibling_names = {task.name for task in task.parent_nursery.child_tasks}
assert "<fixture 'fixture_with_unique_name'>" in sibling_names
File renamed without changes.
Loading

0 comments on commit dd27d70

Please sign in to comment.