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

Add matcher for exceptions in asyncio future #171

Merged
merged 1 commit into from
Feb 15, 2023
Merged

Conversation

keis
Copy link
Contributor

@keis keis commented Mar 11, 2021

WIP: Based on raises matcher but adapted to deal with future objects.

Example of use

assert_that(
    await resolved(raise_exception()),
    future_exception(AssertionError))
)

The resolved helper is used to create resolved future objects in async
code. It takes a "future like" object and waits for it to complete.

Ref #155

@brunns
Copy link
Member

brunns commented Mar 12, 2021

Ah - doing this in a way which is compatible with Python versions prior to 3.9 is going to be... interesting. FYI, PyHamcrest works with versions 3.6 and up as of now - all currently supported versions.

@keis
Copy link
Contributor Author

keis commented Mar 12, 2021

I don't think the await syntax should be a problem it was introduced in 3.4 (Or there about) IIRC, and before that there was the 'yield from' stuff that should work even if not as pretty to look at :)

@brunns
Copy link
Member

brunns commented Mar 12, 2021

Oh, cool - but something's not working with older versions. FWIW, I usually do tox -e py36,py39,lint before committing - I find that's enough to give me a good idea it'll work.

Nice change, BTW - I do love to see tests and docs in a PR.

@offbyone has added towncrier to the build, so at some point we;'ll need an entry in changelog.d too.

@keis
Copy link
Contributor Author

keis commented Mar 12, 2021

So turns out there was two issues Future[T] does not work in python3.6 and IsolatedAsyncioTestCase was only added in 3.8

I'll update with some more tests etc later :)

match_description.append_text("%r of type %s was raised." % (exc, type(exc)))


def future_exception(
Copy link
Member

Choose a reason for hiding this comment

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

I wonder just from a literate coding PoV if this should be (or at least have as an alias) asynchronously_raises instead of future_exception)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm a bit torn on this but I'll admit I mostly find the literal coding concerns to be a distraction.

Technically it can match a Future object no matter how you produce it and it's not entirely correct to say it raises the exception because it was already raised but captured.

I'm happy to defer this decision to you though :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

what about assert_that(actual_future, is(future_raising(ValueError))), for clarity I think I'd prefer to have future still in the name.

Also, oops. did not mean to forget about this pull request for 2 years. I'll rebase if there's some interest in this approach :)


Examples::

assert_that(somefuture, future_exception(ValueError))
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 be leaning in to assert_that() here, or conceiving of a different assert wrapper?

Copy link
Contributor Author

@keis keis Mar 12, 2021

Choose a reason for hiding this comment

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

I thought about creating a variant that itself could by awaited but I think that would be much more complicated and impact how matcher and everything works. This is really the huge problem with asyncio (and async code in general) it tends to just infect everything and spread to all corners of your code.

Copy link
Member

@offbyone offbyone left a comment

Choose a reason for hiding this comment

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

I'm not averse to seeing this merge, but I think what I'm missing before we do is documentation that shows how one would write a test with this matcher. Can you please add docs showing how it'll handle event loops, resolving the future, etc, not from the perspective of our side where we're implementing it, but from the perspective of the writer of tests using it.

return False

def describe_to(self, description: Description) -> None:
description.append_text("Expected a future with exception %s" % self.expected)
Copy link
Member

Choose a reason for hiding this comment

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

If I'm reading the above right, it'd be more true to say "Expected a completed future ..." etc, right?

Example of use

```
assert_that(
    await resolved(raise_exception()),
    future_raising(AssertionError))
)
```

The resolved helper is used to create resolved future objects in async
code. It takes a "future like" object and waits for it to complete.

Ref hamcrest#155
@keis
Copy link
Contributor Author

keis commented Feb 14, 2023

Made an attempt at some docs not sure if the tutorial is the right place though.

@offbyone offbyone merged commit db767cf into hamcrest:main Feb 15, 2023
@javrasya
Copy link

When will a new release be published with this change?

@Dogrtt
Copy link

Dogrtt commented Oct 22, 2023

That's very helpful feature. It would be nice to get it there.

@keis keis deleted the future branch October 22, 2023 12:04
@offbyone
Copy link
Member

offbyone commented Oct 22, 2023

I just published 2.1.0 to PyPi for you; enjoy!

netbsd-srcmastr pushed a commit to NetBSD/pkgsrc that referenced this pull request Nov 5, 2023
Hamcrest 2.1.0 (2023-10-22)
===========================

Features
--------

- Add a matcher for exceptions in asyncio future (`#171 <https://github.com/hamcrest/PyHamcrest/issues/171>`_)


Bugfixes
--------

- Use the correct generic type in the internal ``describe_keyvalue`` method (`#182 <https://github.com/hamcrest/PyHamcrest/issues/182>`_)
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