"""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