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 tests for ASGI specification compliance #50

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

LucidDan
Copy link

WIP: test suite for ASGI specification compliance.

It seems like a great idea for a ASGI test library to have tests itself
to check it is compliant with the spec, and if there are new versions
that come out, will make it easier to update to match the spec.

There are a number of tests that currently xfail in this suite -
one or two of those tests might be things that I'm wrong in my
interpretation of the spec, but I'm mostly pretty confident in them.

I've intentionally made these xfails rather than just fixing the code in
the same PR, so the idea would be to fix the issues one by one and
remove the xfail, or in cases where the issue is a WONTFIX, change
the test and/or indicate as such in the xfail reason.

There is still a need for a couple more tests in the websocket
section, and probably could do with some more type-oriented
negative tests (e.g. testing what happens if you send a message
with the wrong type in the event values, like with 'status').

Of particular note, the scope['path'] test (test_http:test_http_path_is_not_escaped)
I'm not entirely sure that test should exist, it might not be valid.
More explanation in the comments. This one is one of the main things
that makes me want to keep this PR draft a bit longer, both for comment
and while I decide what to do with that test.

There are a number of tests that currently xfail in this suite.
The intention is to fix the issues one by one, or in cases where
the issue is a WONTFIX, change the test and/or indicate as such
in the xfail reason.

There is still a need for a couple more tests in the websocket
section, and probably could do with some more type-oriented
negative tests (e.g. testing what happens if you send a message
with the wrong type in the event values, like with 'status')
@codecov-commenter
Copy link

codecov-commenter commented Dec 29, 2021

Codecov Report

Merging #50 (2037ec9) into master (0e92f98) will increase coverage by 1.33%.
The diff coverage is 95.00%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master      #50      +/-   ##
==========================================
+ Coverage   87.53%   88.86%   +1.33%     
==========================================
  Files           6        7       +1     
  Lines         409      422      +13     
==========================================
+ Hits          358      375      +17     
+ Misses         51       47       -4     
Impacted Files Coverage Δ
async_asgi_testclient/websocket.py 74.11% <ø> (ø)
async_asgi_testclient/testing.py 91.50% <92.85%> (+2.46%) ⬆️
async_asgi_testclient/exceptions.py 100.00% <100.00%> (ø)
async_asgi_testclient/utils.py 95.00% <0.00%> (+1.25%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0e92f98...2037ec9. Read the comment docs.

Fixes test_http:test_response_body_can_be_missing()
Fixes test_http:test_response_headers_can_be_missing()
Fixes test_websocket :
 test_asgi_version_is_present_in_websocket_scope() and
 test_websocket_spec_version_is_missing_or_correct()
Fixes test_lifespan:test_lifespan_not_supported_is_allowed()

This fix was somewhat challenging to achieve without breaking
other things or backward compatibility issues.

In the end, I think the most elegant solution is to adopt
more Pythonic exceptions in the TestClient, as demonstrated in
the commit. However, since this is a less trivial change, I
have only used TestClientError in the lifespan related code,
 leaving all other code using raw Exceptions still.

I think TestClientError (or possibly even multiple exception classes
for different purposes) should actually be adopted throughout
all the code, albeit with the caveat that it might cause some issues
if anyone is doing really rigid exception assertions in their test
code. For most cases (including my own code that is already using
async_asgi_testclient) I believe the average
 'with pytest.raises(Exception)' assertions should still work,
 since TestClientError is a sub-class of Exception.

A side-benefit of the TestClientError is it would allow access to
the original Message structure, instead of having to decode a string
repr of the event message.
Removed test for contamination between calls with a custom scope.
Probably deserves documentation because it could be a gotcha for people,
but it doesn't deserve to be in this test suite.
@LucidDan
Copy link
Author

LucidDan commented Dec 29, 2021

The CI black is pretty old, and I can't get it to run on my computer at all (symbol not found in flat namespace '__PyUnicode_DecodeUnicodeEscape')...but the newer black that I could get to work doesn't match formatting...so I can't get this to pass. Ugh. :-( (fixed it eventually)

In any case...

I broke the commits out so it is easy to read through and see the fixes for each xfail test, but they don't necessarily need to be split out like that; might be better to just squash them when merging.

@LucidDan LucidDan force-pushed the master branch 2 times, most recently from d1bfa57 to 797b876 Compare December 29, 2021 12:25
Had a few issues with formatting and the black version, this commit cleans that up.
@LucidDan LucidDan marked this pull request as ready for review December 29, 2021 12:30
@LucidDan
Copy link
Author

This PR now includes fixes for a few of the most prominent issues I wrote tests for (read: the ones personally causing me immediate problems right now :-D).

The main one that deserves the most attention is the one that originally sent me down this rabbit hole - the lifespan protocol handling. When finding a solution for that, I considered a few approaches, but I ended up using a custom exception - I would actually recommend using TestClientError everywhere instead of the current use of raw Exceptions which is generally considered bad practice. The commit has some detailed notes on the thinking behind it.

@masipcat masipcat self-requested a review December 30, 2021 15:47
@masipcat
Copy link
Contributor

Hi @LucidDan, I've done a quick review and it looks really good! I'll do a proper review when I come back from the holiday in about two weeks. Let me know when this is no longer a WIP and it's ready for review.

@LucidDan
Copy link
Author

Great!
Yeah it's no longer a WIP, I think there is a little more that could be done (I don't have any test for websocket send/receive) but otherwise it is in good shape.

Comment on lines +82 to +84
if msg[-1]["type"] == "http.disconnect" or not msg[-1].get(
"more_body", False
):
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we check that if have not received a http.disconnect, it is a http.request?

Like:

Suggested change
if msg[-1]["type"] == "http.disconnect" or not msg[-1].get(
"more_body", False
):
if msg[-1]["type"] == "http.disconnect" or (msg[-1] == "http.request" and not msg[-1].get(
"more_body", False
)):

So we can catch unexpected events sent by asgi-testclient.

Copy link
Author

Choose a reason for hiding this comment

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

Yes this makes sense. That whole function could probably be rewritten to be more readable too, now that I look at it after a break. ;-)

Copy link
Contributor

Choose a reason for hiding this comment

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

great!



# Ok, this one I'm not sure about...
# The grey area here is TestClient doesn't currently decode an encoded URL; but should it?
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the TestClient should encode the path internally, the same way we are doing for the query_string:

if query_string is None:
path, _, query_string_raw = path.partition("?")
query_string_raw = quote(query_string_raw, safe="&=")
else:
query_string_raw = urlencode(query_string, doseq=True)

If you want to change it and add/modify one of the integration tests to ensure the frameworks still work I think it's enough :)

@LucidDan
Copy link
Author

LucidDan commented Jun 2, 2023

Been a long time, but I'm taking another look at this...still a couple of things to resolve but have addressed a few of the questions from the (Jan 2022!!) review.

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.

3 participants