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

Can not run hypothesis as async test with 6.39.0 #3245

Closed
aquamatthias opened this issue Mar 2, 2022 · 9 comments · Fixed by #3246
Closed

Can not run hypothesis as async test with 6.39.0 #3245

aquamatthias opened this issue Mar 2, 2022 · 9 comments · Fixed by #3246
Labels
bug something is clearly wrong here interop how to play nicely with other packages

Comments

@aquamatthias
Copy link

I use pytest to run the tests:

pytest==7.0.1
pytest-cov==3.0.0
pytest-runner==6.0.0
pytest-asyncio==0.18.1

Following test runs fine with 6.38.0:

@given(json_array_gen)
@pytest.mark.asyncio
async def test_foo(elements: List[JsonElement]) -> None:
    pass

And fails with 6.39.0:

tests/resotocore/web/content_renderer_test.py::test_foo - TypeError: test_foo() takes 0 positional arguments but 1 was given
Complete Log
============================================================================ test session starts ============================================================================
platform darwin -- Python 3.10.0, pytest-7.0.1, pluggy-1.0.0
rootdir: /Users/matthias/Documents/Work/someeng/resoto/resotocore, configfile: tox.ini
plugins: typeguard-2.13.3, asyncio-0.18.1, hypothesis-6.39.0, cov-3.0.0
asyncio: mode=auto
collected 1 item                                                                                                                                                            

tests/resotocore/web/content_renderer_test.py You can add @seed(187717537307108665848683760785146775420) to this test or run pytest with --hypothesis-seed=187717537307108665848683760785146775420 to reproduce this failure.
F

================================================================================= FAILURES ==================================================================================
_________________________________________________________________________________ test_foo __________________________________________________________________________________

    @given(json_array_gen)
>   @settings(max_examples=20, suppress_health_check=HealthCheck.all())

tests/resotocore/web/content_renderer_test.py:41: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:470: in run
    self._run()
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:870: in _run
    self.reuse_existing_examples()
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:548: in reuse_existing_examples
    data = self.cached_test_function(existing)
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:1051: in cached_test_function
    self.test_function(data)
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:208: in test_function
    self.__stoppable_test_function(data)
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:184: in __stoppable_test_function
    self._test_function(data)
venv/lib/python3.10/site-packages/hypothesis/core.py:725: in _execute_once_for_engine
    escalate_hypothesis_internal_error()
venv/lib/python3.10/site-packages/hypothesis/core.py:699: in _execute_once_for_engine
    result = self.execute_once(data)
venv/lib/python3.10/site-packages/hypothesis/core.py:637: in execute_once
    result = self.test_runner(data, run)
venv/lib/python3.10/site-packages/hypothesis/executors.py:47: in default_new_style_executor
    return function(data)
venv/lib/python3.10/site-packages/hypothesis/core.py:633: in run
    return test(*args, **kwargs)
venv/lib/python3.10/site-packages/pytest_asyncio/plugin.py:432: in test_foo
    def inner(**kwargs):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = ([False],), kwargs = {}, initial_draws = 1, start = 540979.612708916

    @proxies(self.test)
    def test(*args, **kwargs):
        self.__test_runtime = None
        initial_draws = len(data.draw_times)
        start = time.perf_counter()
>       result = self.test(*args, **kwargs)
E       TypeError: test_foo() takes 0 positional arguments but 1 was given

venv/lib/python3.10/site-packages/hypothesis/core.py:575: TypeError
-------------------------------------------------------------------------------- Hypothesis ---------------------------------------------------------------------------------
You can add @seed(187717537307108665848683760785146775420) to this test or run pytest with --hypothesis-seed=187717537307108665848683760785146775420 to reproduce this failure.
========================================================================== short test summary info ==========================================================================
FAILED tests/resotocore/web/content_renderer_test.py::test_foo - TypeError: test_foo() takes 0 positional arguments but 1 was given
============================================================================= 1 failed in 0.54s =============================================================================
(resotocore env) 
someeng/resoto/resotocore  mv/bump ✗                                                                                           ⚑ (⎈ |do-fra1-posthog-k8s:posthog) 10:13:26 ⍉
▶ pytest tests/resotocore/web/content_renderer_test.py::test_foo -s
============================================================================ test session starts ============================================================================
platform darwin -- Python 3.10.0, pytest-7.0.1, pluggy-1.0.0
rootdir: /Users/matthias/Documents/Work/someeng/resoto/resotocore, configfile: tox.ini
plugins: typeguard-2.13.3, asyncio-0.18.1, hypothesis-6.39.0, cov-3.0.0
asyncio: mode=auto
collected 1 item                                                                                                                                                            

tests/resotocore/web/content_renderer_test.py You can add @seed(230021627888664504973027713392113020080) to this test or run pytest with --hypothesis-seed=230021627888664504973027713392113020080 to reproduce this failure.
F

================================================================================= FAILURES ==================================================================================
_________________________________________________________________________________ test_foo __________________________________________________________________________________

    @given(json_array_gen)
>   @pytest.mark.asyncio

tests/resotocore/web/content_renderer_test.py:41: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:470: in run
    self._run()
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:872: in _run
    self.generate_new_examples()
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:604: in generate_new_examples
    zero_data = self.cached_test_function(bytes(BUFFER_SIZE))
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:1051: in cached_test_function
    self.test_function(data)
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:208: in test_function
    self.__stoppable_test_function(data)
venv/lib/python3.10/site-packages/hypothesis/internal/conjecture/engine.py:184: in __stoppable_test_function
    self._test_function(data)
venv/lib/python3.10/site-packages/hypothesis/core.py:725: in _execute_once_for_engine
    escalate_hypothesis_internal_error()
venv/lib/python3.10/site-packages/hypothesis/core.py:699: in _execute_once_for_engine
    result = self.execute_once(data)
venv/lib/python3.10/site-packages/hypothesis/core.py:637: in execute_once
    result = self.test_runner(data, run)
venv/lib/python3.10/site-packages/hypothesis/executors.py:47: in default_new_style_executor
    return function(data)
venv/lib/python3.10/site-packages/hypothesis/core.py:633: in run
    return test(*args, **kwargs)
venv/lib/python3.10/site-packages/pytest_asyncio/plugin.py:432: in test_foo
    def inner(**kwargs):
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

args = ([],), kwargs = {}, initial_draws = 1, start = 541046.91061275

    @proxies(self.test)
    def test(*args, **kwargs):
        self.__test_runtime = None
        initial_draws = len(data.draw_times)
        start = time.perf_counter()
>       result = self.test(*args, **kwargs)
E       TypeError: test_foo() takes 0 positional arguments but 1 was given

venv/lib/python3.10/site-packages/hypothesis/core.py:575: TypeError
-------------------------------------------------------------------------------- Hypothesis ---------------------------------------------------------------------------------
You can add @seed(230021627888664504973027713392113020080) to this test or run pytest with --hypothesis-seed=230021627888664504973027713392113020080 to reproduce this failure.
========================================================================== short test summary info ==========================================================================
FAILED tests/resotocore/web/content_renderer_test.py::test_foo - TypeError: test_foo() takes 0 positional arguments but 1 was given
============================================================================= 1 failed in 0.54s =============================================================================
(resotocore env) 

It looks like the combination with pytest-async leads to confusion.
This blocks us from upgrading to latest hypothesis.

@Zac-HD
Copy link
Member

Zac-HD commented Mar 2, 2022

Thanks for reporting - I'll aim to have a fix, or at worst revert, out without 24 hours.

@noblepayne
Copy link

In case this is useful, I hit this problem as well and did a small amount of digging.

Locally, reverting the changes to hypothesis.internal.reflection.proxies is sufficient for tests to pass.

I've also noticed that the inner function used by pytest-asyncio only accepts kwargs. Changing it to def inner(*args, **kwargs) also fixes tests locally.

Not super familiar with hypothesis internals, but my quick guess is that previously, when using reflection.getfullargspec_except_self, hypothesis did not detect positional args in the function after being wrapped by inner and passed it values as kwargs.

Now, with reflection.get_signature, it does detect positional args and then tries to pass values as positional args to inner, which fails.

Here's an example.

Definitions and imports:

import asyncio

import hypothesis.internal.reflection
import pytest_asyncio

async def some_async_fn(a,b):
    return a==b

wrapped_async_fn = pytest_asyncio.plugin.wrap_in_sync(None, some_async_fn, asyncio.get_event_loop())

Output:

# Raw fn can handle both args and kwargs.
In [33]: await some_async_fn(1,2)
Out[33]: False

In [34]: await some_async_fn(a=1,b=2)
Out[34]: False

# Wrapped fn can only handle kwargs.
In [35]: wrapped_async_fn(1,2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
Input In [35], in <cell line: 1>()
----> 1 wrapped_async_fn(1,2)

TypeError: inner() takes 0 positional arguments but 2 were given

# This works.
In [36]: wrapped_async_fn(a=1,b=2)


# Old method of getting signature does not detect positional args for wrapped fn.
In [37]: hypothesis.internal.reflection.getfullargspec_except_self(some_async_fn)
Out[37]: FullArgSpec(args=['a', 'b'], varargs=None, varkw=None, defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})

In [38]: hypothesis.internal.reflection.getfullargspec_except_self(wrapped_async_fn)
Out[38]: FullArgSpec(args=[], varargs=None, varkw='kwargs', defaults=None, kwonlyargs=[], kwonlydefaults=None, annotations={})

# New method has same output for both wrapped and raw.
In [39]: {k:v.kind for k,v in hypothesis.internal.reflection.get_signature(some_async_fn).parameters.items()}
Out[39]:
{'a': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
 'b': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>}

In [40]: {k:v.kind for k,v in hypothesis.internal.reflection.get_signature(wrapped_async_fn).parameters.items()}
Out[40]:
{'a': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>,
 'b': <_ParameterKind.POSITIONAL_OR_KEYWORD: 1>}

@Zac-HD
Copy link
Member

Zac-HD commented Mar 2, 2022

I've also noticed that the inner function used by pytest-asyncio only accepts kwargs. Changing it to def inner(*args, **kwargs) also fixes tests locally.

Ah, that's the bug then, they're misusing our internal @proxies helper.

Nope, it's our fault, we changed from passing by name to passing positionally. This is a bad breaking change - I'll revert tonight and then open an issue to fix it properly soonish.

@Zac-HD Zac-HD added the interop how to play nicely with other packages label Mar 2, 2022
@rsokl
Copy link
Contributor

rsokl commented Mar 2, 2022

Prior to #3241, @given did not follow wrapper chains:

def f(): ...

@given(st.integers())
@wraps(f)
def test(x): pass

Before #3241:

>>> test()  # runs without problem

After #3241:

>>> test() 
TypeError: f() got an unexpected keyword argument 'x'

I believe this is due to changing getfullargspec(f) to signature(f) . The former does not follow wrapper chains, whereas the latter does by default.

@Zac-HD
Copy link
Member

Zac-HD commented Mar 3, 2022

I believe this is due to changing getfullargspec(f) to signature(f) . The former does not follow wrapper chains, whereas the latter does by default.

That part is fine, the bit we need to revert is where we were passing arguments by name and now we're doing it by position.

@Zac-HD Zac-HD added the bug something is clearly wrong here label Mar 3, 2022
@Zac-HD
Copy link
Member

Zac-HD commented Mar 3, 2022

...which is in fact because we started following wrapper chains 🤦‍♂️ , @rsokl called it.

@aquamatthias
Copy link
Author

Thanks for the super fast response and fix 🚀.

@seifertm
Copy link

seifertm commented Mar 3, 2022

While the pytest-asyncio team is not nearly as quick as the Hypothesis maintainers, we just released pytest-asyncio-0.18.2 which addresses the same issue on our end :)

@Zac-HD
Copy link
Member

Zac-HD commented Mar 3, 2022

Legendary! I'd give you bonus points for (a) getting the report later, and (b) not causing the issue in the first place though 😅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug something is clearly wrong here interop how to play nicely with other packages
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants