-
Notifications
You must be signed in to change notification settings - Fork 984
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
Make 'async def' tests work #2583
Conversation
Strilanc
commented
Nov 20, 2019
- Add pytest-asyncio dev dependency
- Detect undecorated async def methods (which would otherwise vacuously pass) via conftest.py/pytest_pyfunc_call
- Refactor existing async tests into async def test methods
- Remove cirq.testing.assert_asyncio_will_have_result
- Remove cirq.testing.assert_asyncio_will_raise
- Remove cirq.testing.assert_asyncio_still_running
- Add cirq.testing.asyncio_not_finishing with forgot-to-await detection
- Add pytest-asyncio dev dependency - Detect undecorated async def methods (which would otherwise vacuously pass) via conftest.py/pytest_pyfunc_call - Refactor existing async tests into async def test methods - Remove cirq.testing.assert_asyncio_will_have_result - Remove cirq.testing.assert_asyncio_will_raise - Remove cirq.testing.assert_asyncio_still_running - Add cirq.testing.asyncio_not_finishing with forgot-to-await detection
@@ -299,7 +300,7 @@ def _run(self, circuit, param_resolver, repetitions): | |||
|
|||
q = cirq.LineQubit(0) | |||
f = MockSimulator().run_async(cirq.Circuit(cirq.measure(q)), repetitions=10) | |||
result = cirq.testing.assert_asyncio_will_have_result(f) | |||
result = await f |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does the asyncio decorator handle timeouts?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't, which sucks. I didn't want to add a custom cirq-specific async test decorator if possible. There is pytest-timeout
plugin that might potentially work.
cirq/testing/asynchronous.py
Outdated
|
||
def asyncio_not_finishing(future: Union[Awaitable, asyncio.Future, Coroutine], |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest having this return True if a future does complete, just to avoid the negation in the name (and the double negative if you want to assert that a future finishes). Could call this asyncio_finishes_soon
, say, and then users can call it like:
assert await asyncio_finishes_soon(f)
assert not await asyncio_finishes_soon(g)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This method is essentially always used in the context of expecting it to return true, so it makes sense to not invert. The name could potentially be changed, but the logic should stay the same.
asyncio_still_running
asyncio_pending
or something like that
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed to asyncio_pending
.
cirq/testing/asynchronous.py
Outdated
def __bool__(self): | ||
raise RuntimeError( | ||
'You forgot the "await" in ' | ||
'"assert await cirq.testing.asyncio_not_finishing(...)".') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice.
cirq/testing/asynchronous_test.py
Outdated
e = asyncio.Future() | ||
e.set_exception(ValueError('test fail')) | ||
cirq.testing.assert_asyncio_will_raise(e, ValueError, match="test fail") | ||
f = cirq.testing.asyncio_not_finishing(f) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: use a different variable name for the return value here, to distinguish from the future itself.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -52,7 +54,7 @@ def on_job_result(self, job, result): | |||
completion = TestCollector().collect_async(sampler=cirq.Simulator(), | |||
max_total_samples=100, | |||
concurrency=5) | |||
cirq.testing.assert_asyncio_will_have_result(completion, None) | |||
assert await completion is None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest avoiding assert await
wherever possible:
result = await TestCollector().collect_async(...)
assert result is None
As written it's not clear that await
binds as assert (await completion) is None
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged, but the alternative interpretation results in a runtime error because bool is not awaitable. Rule of thumb is that await
binds super tightly. Tighter than x**y
, not as tight as x[y]
or x.y
.
@@ -24,18 +27,19 @@ def test_pauli_string_sample_collector(): | |||
4 * cirq.Z(a) * cirq.Z(b), | |||
samples_per_term=100) | |||
completion = p.collect_async(sampler=cirq.Simulator()) | |||
cirq.testing.assert_asyncio_will_have_result(completion, None) | |||
assert await completion is None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here too, can avoid assert await
:
result = await p.collect_async(...)
assert result is None
Also below.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged.
@@ -153,5 +153,5 @@ def run_sweep(self, *args, **kwargs): | |||
|
|||
a = S().run_sweep_async(cirq.Circuit(), params=None) | |||
assert not ran | |||
cirq.testing.assert_asyncio_will_have_result(a, []) | |||
assert await a == [] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
result = await a
assert result == []
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged.
cirq/work/work_pool_test.py
Outdated
pool = CompletionOrderedAsyncWorkPool() | ||
work = asyncio.Future() | ||
pool.include_work(work) | ||
result = pool.__anext__() | ||
cirq.testing.assert_asyncio_still_running(result) | ||
assert await cirq.testing.asynchronous.asyncio_not_finishing(result) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking assert await foo
and assert not await foo
are maybe ok, but I definitely think assert await foo == bar
is hard too parse. One possibility would be too absorb the assertion into the cirq.testing.asynchronous
function, like in numpy.testing.assert_allclose
and friends. Then you could just do
await cirq.testing.asynchronous.assert_not_finishing(result)
You'd need a different function to assert that a future does finish, but maybe it's worth it. Might be good to get some other people's opinions about what would be least confusing here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Acknowledged. The function actually used to work that way. I changed it intentionally, to move the assert to the outside. IMO the only reason not to assert on the outside is for utility methods that give significantly better error messages on failure.
@maffoo We can discuss the style choices tomorrow if you'd like. My personal inclination is to not worry too much about internal method styling when people disagree, and to make tools that enforce things if people really disagree. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanations. LGTM
cirq/testing/asynchronous.py
Outdated
Works by running the asyncio event loop for a short amount of time. | ||
This method is used in tests checking that a future actually depends on some | ||
given event having happened. The test can assert, before the event, that the | ||
future is not finishing and then assert, after the event, that the future |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "not finishing" -> "still pending"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
cirq/testing/asynchronous.py
Outdated
AssertError: The future completed or failed within the timeout. | ||
Returns: | ||
True if the future did not complete despite being given a bit of time to | ||
do so. False if the future did complete (or fail) or was already |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: "True if the future is still pending after the timeout elapses. False if the future did complete..."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.