mirror of
https://github.com/Av0idD4rk/firegex.git
synced 2026-10-23 16:31:00 +00:00
97 lines
3.6 KiB
Python
97 lines
3.6 KiB
Python
"""The HTTP parser binding stands on its own.
|
|
|
|
`firegex._llhttp` is a fork of `pyllhttp`, and for a while it was still *calling* it: the
|
|
error path imported the module `pyllhttp` to fetch the exception class to raise. That
|
|
package is archived, is not a dependency, and is installed on no machine the wheel is
|
|
installed on — so the import failed, its traceback went to stderr (which on a filter is
|
|
the service log), and the function returned without setting an exception. CPython reports
|
|
that to the caller as `SystemError: ... returned NULL without setting an exception`, and
|
|
a malformed request — the traffic this parser exists to look at — said exactly that
|
|
instead of naming the problem.
|
|
|
|
It went unnoticed because a machine that once installed `pyllhttp` has it, and both the
|
|
laptop and the VM these tests were written on did. So the cases below take it away first.
|
|
"""
|
|
|
|
import sys
|
|
|
|
import pytest
|
|
|
|
|
|
class _Blocked:
|
|
"""A meta-path finder that makes one module unimportable, as if it were not there."""
|
|
|
|
def __init__(self, name: str):
|
|
self.name = name
|
|
|
|
def find_spec(self, name, path=None, target=None):
|
|
if name == self.name:
|
|
raise ImportError(f"No module named {self.name}")
|
|
return None
|
|
|
|
|
|
@pytest.fixture
|
|
def without_pyllhttp():
|
|
"""The state every machine that installs the wheel is in."""
|
|
blocker = _Blocked("pyllhttp")
|
|
sys.meta_path.insert(0, blocker)
|
|
saved = sys.modules.pop("pyllhttp", None)
|
|
try:
|
|
yield
|
|
finally:
|
|
sys.meta_path.remove(blocker)
|
|
if saved is not None:
|
|
sys.modules["pyllhttp"] = saved
|
|
|
|
|
|
def test_the_parser_names_itself():
|
|
"""The types and the exceptions belong to this module, under this module's name."""
|
|
from firegex import _llhttp
|
|
|
|
assert _llhttp.Request.__module__ == "firegex._llhttp"
|
|
assert _llhttp.Response.__module__ == "firegex._llhttp"
|
|
assert _llhttp.Error.__module__ == "firegex._llhttp"
|
|
|
|
|
|
def test_a_parse_error_raises_this_modules_exception(without_pyllhttp):
|
|
"""…and raises it with nothing else installed to borrow it from."""
|
|
from firegex import _llhttp
|
|
|
|
parser = _llhttp.Request()
|
|
with pytest.raises(_llhttp.InvalidMethodError) as raised:
|
|
parser.execute(b"NOTAMETHOD / HTTP/1.1\r\n\r\n")
|
|
|
|
assert isinstance(raised.value, _llhttp.Error)
|
|
assert type(raised.value).__module__ == "firegex._llhttp"
|
|
# The message is llhttp's own reason, not the shape of the failure to find a class.
|
|
assert "method" in str(raised.value).lower()
|
|
|
|
|
|
def test_every_error_code_has_a_class_to_raise(without_pyllhttp):
|
|
"""A code with no class would be the silent half of the old bug coming back.
|
|
|
|
`RuntimeError` is the floor here, deliberately: whatever goes wrong looking the class
|
|
up, something is raised, because returning without an exception set is what produced
|
|
a `SystemError` naming a package the operator has never heard of.
|
|
"""
|
|
from firegex import _llhttp
|
|
|
|
# One representative per kind of thing that can go wrong mid-message.
|
|
cases = [
|
|
(b"NOTAMETHOD / HTTP/1.1\r\n\r\n", "request"),
|
|
(b"GET / HTTP/1.1\r\nHost: a\r\nTransfer-Encoding: \r\n\r\n", "request"),
|
|
(b"HTTP/1.1 200 OK\nContent-Length: 0\r\n\r\n", "response"),
|
|
]
|
|
for payload, kind in cases:
|
|
parser = _llhttp.Request() if kind == "request" else _llhttp.Response()
|
|
with pytest.raises(_llhttp.Error):
|
|
parser.execute(payload)
|
|
|
|
|
|
def test_a_valid_exchange_is_unaffected(without_pyllhttp):
|
|
from firegex import _llhttp
|
|
|
|
response = _llhttp.Response()
|
|
response.execute(b"HTTP/1.1 404 Not Found\r\nContent-Length: 0\r\n\r\n")
|
|
assert response.status_code == 404
|