-
-
Notifications
You must be signed in to change notification settings - Fork 95
Fix parsing of the --bind
option for abstract UNIX sockets
#248
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
Conversation
wsgi application parsing from the CLI. Hint: there is an issue with abstract unix sockets.
Codecov Report
@@ Coverage Diff @@
## master #248 +/- ##
==========================================
+ Coverage 76.82% 77.91% +1.08%
==========================================
Files 24 25 +1
Lines 3806 3830 +24
==========================================
+ Hits 2924 2984 +60
+ Misses 882 846 -36 |
cheroot/test/test_cli.py
Outdated
) | ||
|
||
|
||
def test_parse_wsgi_bind_location_for_tcpip(): |
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.
Could you plz use @pytest.mark.parametrize
here?
cheroot/test/test_cli.py
Outdated
def main(self): | ||
pass | ||
try: | ||
wsgi_app_mock = WSGIAppMock() |
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.
Plz make a fixture providing this.
cheroot/test/test_cli.py
Outdated
pass | ||
try: | ||
wsgi_app_mock = WSGIAppMock() | ||
sys.modules['mypkg.wsgi'] = wsgi_app_mock |
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 think it's best to rely on the builtin monkeypatch
fixture provided by pytest.
Hey Joel! The change looks okay, but we need linters to pass and please use pytest's way of organizing tests as I outlined in the comments. |
cheroot/test/test_cli.py
Outdated
try: | ||
wsgi_app_mock = WSGIAppMock() | ||
sys.modules['mypkg.wsgi'] = wsgi_app_mock | ||
app = Application.resolve('mypkg.wsgi') |
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.
Please use parametrize
for this as well.
cheroot/test/test_cli.py
Outdated
|
||
|
||
def test_Aplication_resolve(): | ||
import sys |
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.
Imports like this belong to the top of the module. But maybe you could even avoid it with the use of monkeypatch
fixture.
--bind
option for abstract unix sockets
P.S. Running |
Hey @webknjaz! Thank you for your detailed review! I'll make the changes later in the day. π |
π the idea of using |
As a side effect... after running the test in python2, another error existed with the use of `contextlib.suppress`, `suppress` was introduced in python 3.4, so, to support both versions I replaced the context manager with the explicit verification of `inspect.isclass` instead of suppressing `TypeError`.
Note to self: mark the PR as |
cheroot/cli.py
Outdated
return cls(app) | ||
# verify that `app` is a class before the issubclass verification, | ||
# otherwise a TypeError will be rised. | ||
if inspect.isclass(app) and issubclass(app, server.Gateway): |
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.
EAFP is more natural to Python. If you worry about the Py2 compat, I could cherry-pick this https://github.com/cherrypy/cheroot/pull/243/files#diff-544070cbdc8d4e10b3faf72ba309abc6R18-R31 shim to master.
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.
Yeah the original approach was a plain try/except TypeError
and ignore with a comment on why are we ignoring that exception, but I end up preferring the LBFL in this case. But it seem that the suppress
context manager is used a lot in the other PR. If that's going to be included, then yeah I could use that here and be done by leaving this fragment as it was.
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's in master already.
@pytest.mark.parametrize( | ||
'raw_bind_addr, expected_bind_addr', ( | ||
('192.168.1.1:80', ('192.168.1.1', 80)), | ||
('[::1]:8000', ('::1', 8000)), |
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 reminds me that we should probably also test catch-all addrs like
('[::1]:8000', ('::1', 8000)), | |
('[::1]:8000', ('::1', 8000)), | |
('[::]:8000', ('::', 8000)), | |
('12345', ('::', 12345)), |
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.
Yeah... I was just avoiding doing the extra work of thinking of future possible uses cases and all the possible values that urllib may support.
cheroot/test/test_cli.py
Outdated
) | ||
def test_parse_wsgi_bind_addr_for_tcpip(raw_bind_addr, expected_bind_addr): | ||
"""Check the parsing of the --bind option for TCP/IP addresses.""" | ||
assert parse_wsgi_bind_addr(raw_bind_addr) == expected_bind_addr |
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'd merge these all similar tests into parametrize
of the first one. Just make sure to use pytest.param(..., ..., id=)
(http://doc.pytest.org/en/latest/example/parametrize.html#different-options-for-test-ids / https://docs.pytest.org/en/latest/reference.html#pytest-param)
cheroot/test/test_cli.py
Outdated
|
||
@pytest.mark.parametrize( | ||
'wsgi_app_spec, pkg_name, app_method, mocked_app', ( | ||
('mypkg.wsgi', 'mypkg.wsgi', 'application', WSGIAppMock()), |
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.
WSGIAppMock()
is evaluated during the test module import time. Better use a fixture.
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.
The mock instance is directly related to the other params. I did liked the idea of passing it along the others params to express the direct relationship.
For example a future param ('a.b:foo', 'a.b', 'foo')
will break the test, because the mock doesn't define a foo
method. This is not obvious via the implicit mock injection of pytest.
But I understand if you have a strong opinion on going over the pytest mock injection route.
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.
In this case, you'd have to deal with this on the fixture level. This is a pytest way.
cheroot/test/test_cli.py
Outdated
assert parse_wsgi_bind_addr('@cheroot') == '\0cheroot' | ||
|
||
|
||
class WSGIAppMock: |
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 is how you make a fixture with pytest:
class WSGIAppMock: | |
@pytest.fixture | |
def wsgi_app(monkeypatch): | |
"""Return a WSGI app stub.""" | |
class WSGIAppMock: | |
"""Mock of a wsgi module.""" | |
def application(self): | |
"""Empty application method. | |
Default method to be called when no specific callable | |
is defined in the wsgi application identifier. | |
It has an empty body because we are expecting to verify that | |
the same method is return no the actual execution of it. | |
""" | |
def main(self): | |
"""Empty custom method (callable) inside the mocked WSGI app. | |
It has an empty body because we are expecting to verify that | |
the same method is return no the actual execution of it. | |
""" | |
mocked_app = WSGIAppMock() | |
if six.PY2: | |
# python2 requires the previous namespaces to be part of sys.modules | |
οΏΌ # (e.g. for 'a.b.c' we need to insert 'a', 'a.b' and 'a.b.c') | |
οΏΌ # otherwise it fails, we're setting the same instance on each level, | |
οΏΌ # we don't really care about those, just the last one. | |
full_path, *pkg_parts, _last_part = pkg_name.split('.') | |
οΏΌ for p in pkg_parts: | |
οΏΌ full_path = '.'.join((full_path, p)) | |
οΏΌ monkeypatch.setitem(sys.modules, full_path, mocked_app) | |
οΏΌ monkeypatch.setitem(sys.modules, pkg_name, mocked_app) | |
return mocked_app |
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.
BTW that unpacking syntax doesn't work on Python 2.
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.
Wow, I guess I'm too addicted to py3 :)
cheroot/test/test_cli.py
Outdated
) | ||
def test_Aplication_resolve( | ||
monkeypatch, | ||
wsgi_app_spec, pkg_name, app_method, mocked_app, |
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.
To use the fixture from https://github.com/cherrypy/cheroot/pull/248/files#r347003745:
wsgi_app_spec, pkg_name, app_method, mocked_app, | |
wsgi_app_spec, pkg_name, app_method, wsgi_app, |
cheroot/test/test_cli.py
Outdated
wsgi_app_spec, pkg_name, app_method, mocked_app, | ||
): | ||
"""Check the wsgi application name conversion.""" | ||
if six.PY2: |
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 whole monkeypatching can be also moved to the fixture so that only testing remains in the test function: https://github.com/cherrypy/cheroot/pull/248/files#r347003745.
cheroot/test/test_core.py
Outdated
# then this will result in an `OverflowError: Python int too large to convert to C ssize_t` | ||
# in the server. | ||
# then this will result in an `OverflowError: Python int too large to | ||
# convert to C `ssize_t` in the server. |
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.
Wow, I probably missed this in the previous PR. I'll commit it to master directly because it's completely unrelated to this PR.
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 one's in master already.
to make it python2 compatible using a EAFP style, instead of using `inspect.isclass`.
--bind
option for abstract unix sockets--bind
option for abstract unix sockets
- Unify the bind_addr test into a single function parametrized by pytest. - Add additional parameters in the `test_parse_wsgi_bind_addr` function to further test some additional cases. - Encapsulate the WSGI mock definition, patching, and injection in the test using pytest conventions.
--bind
option for abstract unix sockets--bind
option for abstract unix sockets
# this is a valid input, but foo gets discarted | ||
('foo@bar:5000', ('bar', 5000)), | ||
('foo', ('foo', None)), | ||
('123456789', ('123456789', 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.
How about :8080
?
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.
and catch-all addresses like 0.0.0.0
/::
/[::]
?
def test_parse_wsgi_bind_addr(raw_bind_addr, expected_bind_addr): | ||
"""Check the parsing of the --bind option. | ||
|
||
Verify some of the supported addresses and the excpected return value. |
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'd be nice to also have a negative test with invalid data. But that could be done separately.
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's good enough to be accepted. But feel free to follow up with more improvements :)
--bind
option for abstract unix sockets--bind
option for abstract UNIX sockets
β What kind of change does this PR introduce?
π What is the related issue number (starting with
#
)It wasn't reported.
β What is the current behavior? (You can also link to an open issue here)
The
--bind
option wasn't properly parsed for abstract unix socket.Something like
cheroot --bind @mysocket
would immediately fail with something like:Because the server would try to connect using TCP/IP at
MYSOCK
portNone
, given the faulty parsing of the CLI.β What is the new behavior (if this is a feature change)?
The
--bind
option now works for abstract UNIX sockets.π Other information:
π Checklist:
and description in grammatically correct, complete sentences
This change isβ