tortoise / tortoise-orm

Familiar asyncio ORM for python, built with relations in mind
https://tortoise.github.io
Apache License 2.0
4.58k stars 378 forks source link

Parametrize test leads to the closing of the event loop #689

Closed dropkickdev closed 3 years ago

dropkickdev commented 3 years ago

Describe the bug In FastAPI, when running a test which uses @pytest.mark.parametrize to test multiple values, the first test goes through but the second and succeeding ones do not. Regardless of the test data being run they all have the same error.

RuntimeError: Event loop is closed

If @pytest.mark.parametrize has 3 types of data to test then the error above comes up 2x since only the first test would work. I'm guessing after the first test it thinks everything is all done and closes the event loop.

Changing the scope of the fixture also doesn't work and leads to

ScopeMismatch: You tried to access the 'function' scoped fixture 'event_loop' with a 'module' scoped request object, involved factories
../../venv/myvenv/lib/python3.8/site-packages/pytest_asyncio/plugin.py:136:  def wrapper(*args, **kwargs)

To Reproduce

from tortoise import Tortoise

DATABASE_URL = 'use your own'      # I'm using postgres
DATABASE_MODELS = ['app.auth.models.rbac',]

# Fixture
@pytest.fixture
async def db():
    await Tortoise.init(
        db_url=DATABASE_URL,
        modules={'models': DATABASE_MODELS}
    )
    await Tortoise.generate_schemas()

# Test
param = [
    ('user.create', ['AdminGroup', 'NoaddGroup']),
    ('page.create', ['DataGroup'])
]
@pytest.mark.parametrize('perm, out', param)
@pytest.mark.asyncio
async def test_permissions_get_groups(db, perm, out):
    groups = await Permission.get_groups(perm)
    assert Counter(groups) == Counter(out)

And the models (simplified for this)

class Group(models.Model):
    name = fields.CharField(max_length=191, index=True, unique=True)
    permissions: models.ManyToManyRelation['Permission'] = \
        fields.ManyToManyField('models.Permission', related_name='groups',
                               through='auth_group_permissions', backward_key='group_id')

    class Meta:
        table = 'auth_group'

class Permission(models.Model):
    code = fields.CharField(max_length=191, index=True, unique=True)

    class Meta:
        table = 'auth_permission'

    @classmethod
    async def get_groups(cls, code):
        groups = await Group.filter(permissions__code=code).values('name')
        return [i.get('name') for i in groups]

Expected behavior Event loop would stay open until the last @pytest.mark.parametrize test is done.

Additional context As a hack, I'm running param inside the test as a loop so there would technically only be one test but with multiple assertions. It works but I think using @pytest.mark.parametrize is much better.

# Fixture
@pytest.fixture
async def db():
    await Tortoise.init(
        db_url=DATABASE_URL,
        modules={'models': DATABASE_MODELS}
    )
    await Tortoise.generate_schemas()

# Test
@pytest.mark.asyncio
async def test_permissions_get_groups(db):
    param = [
        ('user.create', ['AdminGroup', 'NoaddGroup']),
        ('page.create', ['DataGroup'])
    ]
    for i in param:
        perm, out = i
        groups = await Permission.get_groups(perm)
        assert Counter(groups) == Counter(out)
dropkickdev commented 3 years ago

I thought it was a bug but it wasn't at all. My apologies. Here is the solution I found for anyone who's interested in finding out how to make @pytest.mark.parametrize work.

# Fixtures
@pytest.fixture
def client():
    with TestClient(get_app()) as tc:
        yield tc

@pytest.fixture
def loop(client):
    yield client.task.get_loop()

# Test
param = [
    ('user.create', ['AdminGroup', 'NoaddGroup']),
    ('page.create', ['DataGroup'])
]
@pytest.mark.parametrize('perm, out', param)
@pytest.mark.focus
def test_permissions_get_groups(loop, perm, out):
    async def ab():
        groups = await Permission.get_groups(perm)
        assert Counter(groups) == Counter(out)

    loop.run_until_complete(ab())
long2ice commented 3 years ago

Thanks for sharing!