Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replace pytest-asyncio marker by anyio #1155

Merged
merged 15 commits into from Jun 17, 2022

Conversation

graingert
Copy link
Member

@graingert graingert commented Aug 15, 2021

uvicorn currently depends on both pytest-asyncio and anyio in tests, so we can save a dep by swapping pytest-asyncio for anyio.

scripts/install Outdated Show resolved Hide resolved
@florimondmanca
Copy link
Member

It doesn't seem like we "already" depend on anyio, do we? We're adding it as a new dependency here.

If so, would the rationale better be to prepare the ground for future trio support?

@graingert
Copy link
Member Author

It doesn't seem like we "already" depend on anyio, do we? We're adding it as a new dependency here.

no the anyio dep is already present

@graingert graingert marked this pull request as draft August 16, 2021 08:12
@Kludex
Copy link
Sponsor Member

Kludex commented Sep 30, 2021

@graingert where is it present? 🤔

@graingert
Copy link
Member Author

@graingert where is it present? 🤔

The test suite depends on httpx depends on anyio

requirements.txt Outdated Show resolved Hide resolved
@Kludex Kludex added this to the Version 0.18.0 milestone Apr 23, 2022
@Kludex Kludex changed the title drop pytest-asyncio Replace pytest-asyncio marker by anyio Apr 23, 2022
@Kludex Kludex marked this pull request as ready for review April 23, 2022 06:41
Kludex
Kludex previously approved these changes Apr 23, 2022
Copy link
Sponsor Member

@Kludex Kludex left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@euri10 Please check if you agree with this PR as well.

I've removed all the unnecessary changes from this PR, and focused on what it proposes, jfyk @graingert

Comment on lines 192 to 195
@pytest.fixture
def event_loop():
with contextlib.closing(asyncio.new_event_loop()) as loop:
yield loop
Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@euri10 this is introduced on this PR, jfyk

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the corresponding pytest-asyncio fixture event_loop is here

https://github.com/pytest-dev/pytest-asyncio/blob/4c7da65d6fcf9d725eccba28ad1ed2083524ee16/tests/async_fixtures/test_async_fixtures_with_finalizer.py#L19-L25

With this change, we don't close the loop after the yield, I'm surprised then pytest does not raise Resources not closed warnings, would be probably better to do it anyways so we dont get caught further down the road with such warning that are insanely hard to debug

and the scope is different, I'm not sure we need a loop for each test in that module, do we ?

Copy link
Sponsor Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With this change, we don't close the loop after the yield ...

We do, the contextlib.closing calls loop.close(). Is that what you mean? 🤔

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is this fixture even used for here? It feels like an anti-pattern.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do, the contextlib.closing calls loop.close(). Is that what you mean? thinking

yes, well if it passes tests then the ressources might be closed properly !

What is this fixture even used for here? It feels like an anti-pattern.

iirc we use this pytest_asyncio fixture only in those "protocol" tests to create a bunch of mocks, mocked loop, mocked transport and then we create a protocol we interact with and look at its buffer,
is it an anti-pattern, idk :)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be misunderstanding something, we use pytest-asyncio event_loop fixture, which is to me a real event loop

@pytest.fixture
def event_loop(request):
    """Create an instance of the default event loop for each test case."""
    loop = asyncio.get_event_loop_policy().new_event_loop()
    yield loop
    loop.close()

we could avoid using the MockLoop doing something like this, is that what you mean ?

@@ -175,18 +175,15 @@ class MockTask:
 
 @contextlib.contextmanager
 def get_connected_protocol(app, protocol_cls, event_loop, **kwargs):
-    loop = MockLoop(event_loop)
-    asyncio._set_running_loop(loop)
     transport = MockTransport()
     config = Config(app=app, **kwargs)
     server_state = ServerState()
-    protocol = protocol_cls(config=config, server_state=server_state, _loop=loop)
+    protocol = protocol_cls(config=config, server_state=server_state, _loop=event_loop)
     protocol.connection_made(transport)
     try:
         yield protocol
     finally:
         protocol.loop.close()
-        asyncio._set_running_loop(None)
 
 
 @pytest.mark.parametrize("protocol_cls", HTTP_PROTOCOLS)
@@ -195,7 +192,8 @@ def test_get_request(protocol_cls, event_loop):
 
     with get_connected_protocol(app, protocol_cls, event_loop) as protocol:
         protocol.data_received(SIMPLE_GET_REQUEST)
-        protocol.loop.run_one()
+        tasks = asyncio.all_tasks(loop=event_loop)
+        protocol.loop.run_until_complete(*tasks)
         assert b"HTTP/1.1 200 OK" in protocol.transport.buffer
         assert b"Hello, world" in protocol.transport.buffer

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The loop is closed twice here (once in the fixture, once in get_connected_protocol(). Looks like the latter could be made into a fixture instead.

Does the protocol create tasks?

Copy link
Member Author

@graingert graingert Jun 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

task = self.loop.create_task(self.cycle.run_asgi(app))
task.add_done_callback(self.tasks.discard)
self.tasks.add(task)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@euri10 I just pushed a fix to make MockLoop wrap the currently running loop get_connected_protocol can be made into a plain function but it makes the PR more difficult to review

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as promised, here's the followup: #1520

tests/conftest.py Show resolved Hide resolved
@Kludex Kludex requested a review from euri10 April 23, 2022 06:49
Comment on lines 192 to 195
@pytest.fixture
def event_loop():
with contextlib.closing(asyncio.new_event_loop()) as loop:
yield loop
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do, the contextlib.closing calls loop.close(). Is that what you mean? thinking

yes, well if it passes tests then the ressources might be closed properly !

What is this fixture even used for here? It feels like an anti-pattern.

iirc we use this pytest_asyncio fixture only in those "protocol" tests to create a bunch of mocks, mocked loop, mocked transport and then we create a protocol we interact with and look at its buffer,
is it an anti-pattern, idk :)

tests/conftest.py Show resolved Hide resolved
@graingert graingert requested a review from euri10 June 14, 2022 12:34
@Kludex Kludex dismissed their stale review June 14, 2022 13:32

discussion still happening

Copy link
Member

@euri10 euri10 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like those changes

it's just test_http whose diff is larger, but in any case I find it way cleaner than before.

I think we can give it a shot this way, all good on my side, if you prefer to separate the rework on the MockLoop from the anyio switch I'd be ok too but this way is also ok to me :)

@graingert graingert requested review from Kludex and a team June 16, 2022 11:17
Copy link
Member

@florimondmanca florimondmanca left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 👍

Looks like I had already viewed this back when it was initially opened. Changes outside test_http.py are straightforward. I reviewed that file as well and the changes are in fact pretty systematic too, so giving my LGTM, though I haven't checked if there's nitty-gritty discussion still ongoing. Nice one!

@@ -22,8 +22,6 @@ cryptography==3.4.8
coverage==6.4
coverage-conditional-plugin==0.5.0
httpx==1.0.0b0
pytest-asyncio==0.15.1
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add an explicit test dependency on anyio, since we now explicitly import it?

anyio==3.6.1

@Kludex Kludex merged commit d2b14ee into encode:master Jun 17, 2022
@graingert graingert deleted the drop-pytest-asyncio branch June 17, 2022 09:08
graingert added a commit that referenced this pull request Jun 17, 2022
…1155 cleanups (#1520)

* make test_http.get_connected_protocol a plain function

* remove MockLoop.is_running method

this was only needed for the asyncio._set_running_loop hack
Kludex added a commit to sephioh/uvicorn that referenced this pull request Oct 29, 2022
* drop pytest-asyncio

* add event_loop fixture for MockLoop tests

* REVERTME: install the latest pypa toolchain

* use a patched anyio that grabs an excgroup of failed tasks

* setuptools_scm!!!

* python -m pip

* python3 isn't installed on GHA?

* ah it's bash quoting

* log task that raised the exception

* Add missing markers

* simplify fixture

* avoid hacking asyncio._set_running_loop in test_http.py

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Kludex pushed a commit to sephioh/uvicorn that referenced this pull request Oct 29, 2022
…ncode#1155 cleanups (encode#1520)

* make test_http.get_connected_protocol a plain function

* remove MockLoop.is_running method

this was only needed for the asyncio._set_running_loop hack
Kludex added a commit that referenced this pull request Oct 29, 2022
* drop pytest-asyncio

* add event_loop fixture for MockLoop tests

* REVERTME: install the latest pypa toolchain

* use a patched anyio that grabs an excgroup of failed tasks

* setuptools_scm!!!

* python -m pip

* python3 isn't installed on GHA?

* ah it's bash quoting

* log task that raised the exception

* Add missing markers

* simplify fixture

* avoid hacking asyncio._set_running_loop in test_http.py

Co-authored-by: Marcelo Trylesinski <marcelotryle@gmail.com>
Kludex pushed a commit that referenced this pull request Oct 29, 2022
…1155 cleanups (#1520)

* make test_http.get_connected_protocol a plain function

* remove MockLoop.is_running method

this was only needed for the asyncio._set_running_loop hack
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants