From 0b98b244b5fd1fe96100ac14905417a3b70a4286 Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Mon, 23 Dec 2024 12:12:52 +0100 Subject: [PATCH] tests: linting++ --- src/h2/events.py | 2 +- src/h2/utilities.py | 4 +- tests/conftest.py | 2 + tests/coroutine_tests.py | 14 +- tests/h2spectest.sh | 1 + tests/helpers.py | 59 ++- tests/test_basic_logic.py | 611 +++++++++++++------------- tests/test_closed_streams.py | 143 +++--- tests/test_complex_logic.py | 153 +++---- tests/test_config.py | 74 ++-- tests/test_events.py | 106 +++-- tests/test_exceptions.py | 11 +- tests/test_flow_control_window.py | 251 +++++------ tests/test_h2_upgrade.py | 96 ++-- tests/test_head_request.py | 46 +- tests/test_header_indexing.py | 398 ++++++++--------- tests/test_informational_responses.py | 173 ++++---- tests/test_interacting_stacks.py | 38 +- tests/test_invalid_content_lengths.py | 60 +-- tests/test_invalid_frame_sequences.py | 147 +++---- tests/test_invalid_headers.py | 430 +++++++++--------- tests/test_priority.py | 93 ++-- tests/test_related_events.py | 96 ++-- tests/test_rfc7838.py | 137 +++--- tests/test_rfc8441.py | 37 +- tests/test_settings.py | 85 ++-- tests/test_state_machines.py | 38 +- tests/test_stream_reset.py | 46 +- tests/test_utility_functions.py | 62 +-- 29 files changed, 1701 insertions(+), 1712 deletions(-) diff --git a/src/h2/events.py b/src/h2/events.py index eb92cc104..b81fd1a63 100644 --- a/src/h2/events.py +++ b/src/h2/events.py @@ -34,7 +34,7 @@ class RequestReceived(Event): The RequestReceived event is fired whenever all of a request's headers are received. This event carries the HTTP headers for the given request and the stream ID of the new stream. - + In HTTP/2, headers may be sent as a HEADERS frame followed by zero or more CONTINUATION frames with the final frame setting the END_HEADERS flag. This event is fired after the entire sequence is received. diff --git a/src/h2/utilities.py b/src/h2/utilities.py index 5e1809513..8cafdbd50 100644 --- a/src/h2/utilities.py +++ b/src/h2/utilities.py @@ -11,13 +11,15 @@ from string import whitespace from typing import TYPE_CHECKING, Any, NamedTuple -from hpack.struct import Header, HeaderTuple, HeaderWeaklyTyped, NeverIndexedHeaderTuple +from hpack.struct import HeaderTuple, NeverIndexedHeaderTuple from .exceptions import FlowControlError, ProtocolError if TYPE_CHECKING: # pragma: no cover from collections.abc import Generator, Iterable + from hpack.struct import Header, HeaderWeaklyTyped + UPPER_RE = re.compile(b"[A-Z]") SIGIL = ord(b":") INFORMATIONAL_START = ord(b"1") diff --git a/tests/conftest.py b/tests/conftest.py index ff1f0f63f..acb395c45 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from . import helpers diff --git a/tests/coroutine_tests.py b/tests/coroutine_tests.py index f5b2ffe35..42a10d738 100644 --- a/tests/coroutine_tests.py +++ b/tests/coroutine_tests.py @@ -1,7 +1,4 @@ """ -coroutine_tests -~~~~~~~~~~~~~~~ - This file gives access to a coroutine-based test class. This allows each test case to be defined as a pair of interacting coroutines, sending data to each other by yielding the flow of control. @@ -12,13 +9,15 @@ makes them behave identically on all platforms, as well as ensuring they both succeed and fail quickly. """ -import itertools +from __future__ import annotations + import functools +import itertools import pytest -class CoroutineTestCase(object): +class CoroutineTestCase: """ A base class for tests that use interacting coroutines. @@ -28,7 +27,8 @@ class CoroutineTestCase(object): its first action is to receive data), the calling code should prime it by using the 'server' decorator on this class. """ - def run_until_complete(self, *coroutines): + + def run_until_complete(self, *coroutines) -> None: """ Executes a set of coroutines that communicate between each other. Each one is, in order, passed the output of the previous coroutine until @@ -55,7 +55,7 @@ def run_until_complete(self, *coroutines): except StopIteration: continue else: - pytest.fail("Coroutine %s not exhausted" % coro) + pytest.fail(f"Coroutine {coro} not exhausted") def server(self, func): """ diff --git a/tests/h2spectest.sh b/tests/h2spectest.sh index 02e38d0c3..37e7dfa9a 100755 --- a/tests/h2spectest.sh +++ b/tests/h2spectest.sh @@ -1,4 +1,5 @@ #!/usr/bin/env bash + # A test script that runs the example Python Twisted server and then runs # h2spec against it. Prints the output of h2spec. This script does not expect # to be run directly, but instead via `tox -e h2spec`. diff --git a/tests/helpers.py b/tests/helpers.py index 82f8b10a6..a23a79ec6 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,16 +1,22 @@ """ -helpers -~~~~~~~ - -This module contains helpers for the h2 tests. +Helper module for the h2 tests. """ +from __future__ import annotations + +from hpack.hpack import Encoder from hyperframe.frame import ( - HeadersFrame, DataFrame, SettingsFrame, WindowUpdateFrame, PingFrame, - GoAwayFrame, RstStreamFrame, PushPromiseFrame, PriorityFrame, - ContinuationFrame, AltSvcFrame + AltSvcFrame, + ContinuationFrame, + DataFrame, + GoAwayFrame, + HeadersFrame, + PingFrame, + PriorityFrame, + PushPromiseFrame, + RstStreamFrame, + SettingsFrame, + WindowUpdateFrame, ) -from hpack.hpack import Encoder - SAMPLE_SETTINGS = { SettingsFrame.HEADER_TABLE_SIZE: 4096, @@ -19,32 +25,35 @@ } -class FrameFactory(object): +class FrameFactory: """ A class containing lots of helper methods and state to build frames. This allows test cases to easily build correct HTTP/2 frames to feed to hyper-h2. """ - def __init__(self): + + def __init__(self) -> None: self.encoder = Encoder() - def refresh_encoder(self): + def refresh_encoder(self) -> None: self.encoder = Encoder() - def preamble(self): - return b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + def preamble(self) -> bytes: + return b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" def build_headers_frame(self, headers, - flags=[], + flags=None, stream_id=1, **priority_kwargs): """ Builds a single valid headers frame out of the contained headers. """ + if flags is None: + flags = [] f = HeadersFrame(stream_id) f.data = self.encoder.encode(headers) - f.flags.add('END_HEADERS') + f.flags.add("END_HEADERS") for flag in flags: f.flags.add(flag) @@ -53,10 +62,12 @@ def build_headers_frame(self, return f - def build_continuation_frame(self, header_block, flags=[], stream_id=1): + def build_continuation_frame(self, header_block, flags=None, stream_id=1): """ Builds a single continuation frame out of the binary header block. """ + if flags is None: + flags = [] f = ContinuationFrame(stream_id) f.data = header_block f.flags = set(flags) @@ -73,7 +84,7 @@ def build_data_frame(self, data, flags=None, stream_id=1, padding_len=0): f.flags = flags if padding_len: - flags.add('PADDED') + flags.add("PADDED") f.pad_length = padding_len return f @@ -84,7 +95,7 @@ def build_settings_frame(self, settings, ack=False): """ f = SettingsFrame(0) if ack: - f.flags.add('ACK') + f.flags.add("ACK") f.settings = settings return f @@ -111,7 +122,7 @@ def build_ping_frame(self, ping_data, flags=None): def build_goaway_frame(self, last_stream_id, error_code=0, - additional_data=b''): + additional_data=b""): """ Builds a single GOAWAY frame. """ @@ -133,15 +144,17 @@ def build_push_promise_frame(self, stream_id, promised_stream_id, headers, - flags=[]): + flags=None): """ Builds a single PUSH_PROMISE frame. """ + if flags is None: + flags = [] f = PushPromiseFrame(stream_id) f.promised_stream_id = promised_stream_id f.data = self.encoder.encode(headers) f.flags = set(flags) - f.flags.add('END_HEADERS') + f.flags.add("END_HEADERS") return f def build_priority_frame(self, @@ -167,7 +180,7 @@ def build_alt_svc_frame(self, stream_id, origin, field): f.field = field return f - def change_table_size(self, new_size): + def change_table_size(self, new_size) -> None: """ Causes the encoder to send a dynamic size update in the next header block it sends. diff --git a/tests/test_basic_logic.py b/tests/test_basic_logic.py index 7f779b7a3..0ea7e47bc 100644 --- a/tests/test_basic_logic.py +++ b/tests/test_basic_logic.py @@ -1,14 +1,15 @@ """ -test_basic_logic -~~~~~~~~~~~~~~~~ - Test the basic logic of the h2 state machines. """ +from __future__ import annotations + import random import hyperframe import pytest from hpack import HeaderTuple +from hypothesis import HealthCheck, given, settings +from hypothesis.strategies import integers import h2.config import h2.connection @@ -21,52 +22,50 @@ from . import helpers -from hypothesis import given, settings, HealthCheck -from hypothesis.strategies import integers - -class TestBasicClient(object): +class TestBasicClient: """ Basic client-side tests. """ + example_request_headers = [ - (u':authority', u'example.com'), - (u':path', u'/'), - (u':scheme', u'https'), - (u':method', u'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] bytes_example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (u':status', u'200'), - (u'server', u'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] bytes_example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), ] - def test_begin_connection(self, frame_factory): + def test_begin_connection(self, frame_factory) -> None: """ Client connections emit the HTTP/2 preamble. """ c = h2.connection.H2Connection() expected_settings = frame_factory.build_settings_frame( - c.local_settings + c.local_settings, ) expected_data = ( - b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + expected_settings.serialize() + b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" + expected_settings.serialize() ) events = c.initiate_connection() assert not events assert c.data_to_send() == expected_data - def test_sending_headers(self): + def test_sending_headers(self) -> None: """ Single headers frames are correctly encoded. """ @@ -78,11 +77,11 @@ def test_sending_headers(self): events = c.send_headers(1, self.example_request_headers) assert not events assert c.data_to_send() == ( - b'\x00\x00\r\x01\x04\x00\x00\x00\x01' - b'A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x87\x82' + b"\x00\x00\r\x01\x04\x00\x00\x00\x01" + b"A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x87\x82" ) - def test_sending_data(self): + def test_sending_data(self) -> None: """ Single data frames are encoded correctly. """ @@ -92,25 +91,25 @@ def test_sending_data(self): # Clear the data, then send some data. c.clear_outbound_data_buffer() - events = c.send_data(1, b'some data') + events = c.send_data(1, b"some data") assert not events data_to_send = c.data_to_send() assert ( - data_to_send == b'\x00\x00\t\x00\x00\x00\x00\x00\x01some data' + data_to_send == b"\x00\x00\t\x00\x00\x00\x00\x00\x01some data" ) buffer = h2.frame_buffer.FrameBuffer(server=False) buffer.max_frame_size = 65535 buffer.add_data(data_to_send) - data_frame = list(buffer)[0] + data_frame = next(iter(buffer)) sanity_check_data_frame( data_frame=data_frame, - expected_flow_controlled_length=len(b'some data'), + expected_flow_controlled_length=len(b"some data"), expect_padded_flag=False, - expected_data_frame_pad_length=0 + expected_data_frame_pad_length=0, ) - def test_sending_data_in_memoryview(self): + def test_sending_data_in_memoryview(self) -> None: """ Support memoryview for sending data. """ @@ -120,14 +119,14 @@ def test_sending_data_in_memoryview(self): # Clear the data, then send some data. c.clear_outbound_data_buffer() - events = c.send_data(1, memoryview(b'some data')) + events = c.send_data(1, memoryview(b"some data")) assert not events data_to_send = c.data_to_send() assert ( - data_to_send == b'\x00\x00\t\x00\x00\x00\x00\x00\x01some data' + data_to_send == b"\x00\x00\t\x00\x00\x00\x00\x00\x01some data" ) - def test_sending_data_with_padding(self): + def test_sending_data_with_padding(self) -> None: """ Single data frames with padding are encoded correctly. """ @@ -137,26 +136,26 @@ def test_sending_data_with_padding(self): # Clear the data, then send some data. c.clear_outbound_data_buffer() - events = c.send_data(1, b'some data', pad_length=5) + events = c.send_data(1, b"some data", pad_length=5) assert not events data_to_send = c.data_to_send() assert data_to_send == ( - b'\x00\x00\x0f\x00\x08\x00\x00\x00\x01' - b'\x05some data\x00\x00\x00\x00\x00' + b"\x00\x00\x0f\x00\x08\x00\x00\x00\x01" + b"\x05some data\x00\x00\x00\x00\x00" ) buffer = h2.frame_buffer.FrameBuffer(server=False) buffer.max_frame_size = 65535 buffer.add_data(data_to_send) - data_frame = list(buffer)[0] + data_frame = next(iter(buffer)) sanity_check_data_frame( data_frame=data_frame, - expected_flow_controlled_length=len(b'some data') + 1 + 5, + expected_flow_controlled_length=len(b"some data") + 1 + 5, expect_padded_flag=True, - expected_data_frame_pad_length=5 + expected_data_frame_pad_length=5, ) - def test_sending_data_with_zero_length_padding(self): + def test_sending_data_with_zero_length_padding(self) -> None: """ Single data frames with zero-length padding are encoded correctly. @@ -167,36 +166,36 @@ def test_sending_data_with_zero_length_padding(self): # Clear the data, then send some data. c.clear_outbound_data_buffer() - events = c.send_data(1, b'some data', pad_length=0) + events = c.send_data(1, b"some data", pad_length=0) assert not events data_to_send = c.data_to_send() assert data_to_send == ( - b'\x00\x00\x0a\x00\x08\x00\x00\x00\x01' - b'\x00some data' + b"\x00\x00\x0a\x00\x08\x00\x00\x00\x01" + b"\x00some data" ) buffer = h2.frame_buffer.FrameBuffer(server=False) buffer.max_frame_size = 65535 buffer.add_data(data_to_send) - data_frame = list(buffer)[0] + data_frame = next(iter(buffer)) sanity_check_data_frame( data_frame=data_frame, - expected_flow_controlled_length=len(b'some data') + 1, + expected_flow_controlled_length=len(b"some data") + 1, expect_padded_flag=True, - expected_data_frame_pad_length=0 + expected_data_frame_pad_length=0, ) - @pytest.mark.parametrize("expected_error,pad_length", [ + @pytest.mark.parametrize(("expected_error", "pad_length"), [ (None, 0), (None, 255), (None, None), (ValueError, -1), (ValueError, 256), - (TypeError, 'invalid'), - (TypeError, ''), - (TypeError, '10'), + (TypeError, "invalid"), + (TypeError, ""), + (TypeError, "10"), (TypeError, {}), - (TypeError, ['1', '2', '3']), + (TypeError, ["1", "2", "3"]), (TypeError, []), (TypeError, 1.5), (TypeError, 1.0), @@ -204,7 +203,7 @@ def test_sending_data_with_zero_length_padding(self): ]) def test_sending_data_with_invalid_padding_length(self, expected_error, - pad_length): + pad_length) -> None: """ ``send_data`` with a ``pad_length`` parameter that is an integer outside the range of [0, 255] throws a ``ValueError``, and a @@ -218,11 +217,11 @@ def test_sending_data_with_invalid_padding_length(self, c.clear_outbound_data_buffer() if expected_error is not None: with pytest.raises(expected_error): - c.send_data(1, b'some data', pad_length=pad_length) + c.send_data(1, b"some data", pad_length=pad_length) else: - c.send_data(1, b'some data', pad_length=pad_length) + c.send_data(1, b"some data", pad_length=pad_length) - def test_closing_stream_sending_data(self, frame_factory): + def test_closing_stream_sending_data(self, frame_factory) -> None: """ We can close a stream with a data frame. """ @@ -231,28 +230,28 @@ def test_closing_stream_sending_data(self, frame_factory): c.send_headers(1, self.example_request_headers) f = frame_factory.build_data_frame( - data=b'some data', - flags=['END_STREAM'], + data=b"some data", + flags=["END_STREAM"], ) # Clear the data, then send some data. c.clear_outbound_data_buffer() - events = c.send_data(1, b'some data', end_stream=True) + events = c.send_data(1, b"some data", end_stream=True) assert not events assert c.data_to_send() == f.serialize() - def test_receiving_a_response(self, frame_factory): + def test_receiving_a_response(self, frame_factory) -> None: """ When receiving a response, the ResponseReceived event fires. """ - config = h2.config.H2Configuration(header_encoding='utf-8') + config = h2.config.H2Configuration(header_encoding="utf-8") c = h2.connection.H2Connection(config=config) c.initiate_connection() c.send_headers(1, self.example_request_headers, end_stream=True) # Clear the data f = frame_factory.build_headers_frame( - self.example_response_headers + self.example_response_headers, ) events = c.receive_data(f.serialize()) @@ -263,7 +262,7 @@ def test_receiving_a_response(self, frame_factory): assert event.stream_id == 1 assert event.headers == self.example_response_headers - def test_receiving_a_response_bytes(self, frame_factory): + def test_receiving_a_response_bytes(self, frame_factory) -> None: """ When receiving a response, the ResponseReceived event fires with bytes headers if the encoding is set appropriately. @@ -275,7 +274,7 @@ def test_receiving_a_response_bytes(self, frame_factory): # Clear the data f = frame_factory.build_headers_frame( - self.example_response_headers + self.example_response_headers, ) events = c.receive_data(f.serialize()) @@ -286,7 +285,7 @@ def test_receiving_a_response_bytes(self, frame_factory): assert event.stream_id == 1 assert event.headers == self.bytes_example_response_headers - def test_receiving_a_response_change_encoding(self, frame_factory): + def test_receiving_a_response_change_encoding(self, frame_factory) -> None: """ When receiving a response, the ResponseReceived event fires with bytes headers if the encoding is set appropriately, but if this changes then @@ -298,7 +297,7 @@ def test_receiving_a_response_change_encoding(self, frame_factory): c.send_headers(1, self.example_request_headers, end_stream=True) f = frame_factory.build_headers_frame( - self.example_response_headers + self.example_response_headers, ) events = c.receive_data(f.serialize()) @@ -310,7 +309,7 @@ def test_receiving_a_response_change_encoding(self, frame_factory): assert event.headers == self.bytes_example_response_headers c.send_headers(3, self.example_request_headers, end_stream=True) - c.config.header_encoding = 'utf-8' + c.config.header_encoding = "utf-8" f = frame_factory.build_headers_frame( self.example_response_headers, stream_id=3, @@ -324,7 +323,7 @@ def test_receiving_a_response_change_encoding(self, frame_factory): assert event.stream_id == 3 assert event.headers == self.example_response_headers - def test_end_stream_without_data(self, frame_factory): + def test_end_stream_without_data(self, frame_factory) -> None: """ Ending a stream without data emits a zero-length DATA frame with END_STREAM set. @@ -335,13 +334,13 @@ def test_end_stream_without_data(self, frame_factory): # Clear the data c.clear_outbound_data_buffer() - f = frame_factory.build_data_frame(b'', flags=['END_STREAM']) + f = frame_factory.build_data_frame(b"", flags=["END_STREAM"]) events = c.end_stream(1) assert not events assert c.data_to_send() == f.serialize() - def test_cannot_send_headers_on_lower_stream_id(self): + def test_cannot_send_headers_on_lower_stream_id(self) -> None: """ Once stream ID x has been used, cannot use stream ID y where y < x. """ @@ -355,30 +354,30 @@ def test_cannot_send_headers_on_lower_stream_id(self): assert e.value.stream_id == 1 assert e.value.max_stream_id == 3 - def test_receiving_pushed_stream(self, frame_factory): + def test_receiving_pushed_stream(self, frame_factory) -> None: """ Pushed streams fire a PushedStreamReceived event, followed by ResponseReceived when the response headers are received. """ - config = h2.config.H2Configuration(header_encoding='utf-8') + config = h2.config.H2Configuration(header_encoding="utf-8") c = h2.connection.H2Connection(config=config) c.initiate_connection() c.send_headers(1, self.example_request_headers, end_stream=False) f1 = frame_factory.build_headers_frame( - self.example_response_headers + self.example_response_headers, ) f2 = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, headers=self.example_request_headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) f3 = frame_factory.build_headers_frame( self.example_response_headers, stream_id=2, ) - data = b''.join(x.serialize() for x in [f1, f2, f3]) + data = b"".join(x.serialize() for x in [f1, f2, f3]) events = c.receive_data(data) @@ -396,7 +395,7 @@ def test_receiving_pushed_stream(self, frame_factory): assert response_event.stream_id == 2 assert response_event.headers == self.example_response_headers - def test_receiving_pushed_stream_bytes(self, frame_factory): + def test_receiving_pushed_stream_bytes(self, frame_factory) -> None: """ Pushed headers are not decoded if the header encoding is set to False. """ @@ -406,19 +405,19 @@ def test_receiving_pushed_stream_bytes(self, frame_factory): c.send_headers(1, self.example_request_headers, end_stream=False) f1 = frame_factory.build_headers_frame( - self.example_response_headers + self.example_response_headers, ) f2 = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, headers=self.example_request_headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) f3 = frame_factory.build_headers_frame( self.example_response_headers, stream_id=2, ) - data = b''.join(x.serialize() for x in [f1, f2, f3]) + data = b"".join(x.serialize() for x in [f1, f2, f3]) events = c.receive_data(data) @@ -437,7 +436,7 @@ def test_receiving_pushed_stream_bytes(self, frame_factory): assert response_event.headers == self.bytes_example_response_headers def test_cannot_receive_pushed_stream_when_enable_push_is_0(self, - frame_factory): + frame_factory) -> None: """ If we have set SETTINGS_ENABLE_PUSH to 0, receiving PUSH_PROMISE frames triggers the connection to be closed. @@ -449,13 +448,13 @@ def test_cannot_receive_pushed_stream_when_enable_push_is_0(self, f1 = frame_factory.build_settings_frame({}, ack=True) f2 = frame_factory.build_headers_frame( - self.example_response_headers + self.example_response_headers, ) f3 = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, headers=self.example_request_headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) c.receive_data(f1.serialize()) c.receive_data(f2.serialize()) @@ -465,11 +464,11 @@ def test_cannot_receive_pushed_stream_when_enable_push_is_0(self, c.receive_data(f3.serialize()) expected_frame = frame_factory.build_goaway_frame( - 0, h2.errors.ErrorCodes.PROTOCOL_ERROR + 0, h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() - def test_receiving_response_no_body(self, frame_factory): + def test_receiving_response_no_body(self, frame_factory) -> None: """ Receiving a response without a body fires two events, ResponseReceived and StreamEnded. @@ -480,7 +479,7 @@ def test_receiving_response_no_body(self, frame_factory): f = frame_factory.build_headers_frame( self.example_response_headers, - flags=['END_STREAM'] + flags=["END_STREAM"], ) events = c.receive_data(f.serialize()) @@ -491,22 +490,22 @@ def test_receiving_response_no_body(self, frame_factory): assert isinstance(response_event, h2.events.ResponseReceived) assert isinstance(end_stream, h2.events.StreamEnded) - def test_oversize_headers(self): + def test_oversize_headers(self) -> None: """ Sending headers that are oversized generates a stream of CONTINUATION frames. """ - all_bytes = [chr(x).encode('latin1') for x in range(0, 256)] + all_bytes = [chr(x).encode("latin1") for x in range(256)] - large_binary_string = b''.join( - random.choice(all_bytes) for _ in range(0, 256) + large_binary_string = b"".join( + random.choice(all_bytes) for _ in range(256) ) test_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':method', 'GET'), - (':scheme', 'https'), - ('key', large_binary_string) + (":authority", "example.com"), + (":path", "/"), + (":method", "GET"), + (":scheme", "https"), + ("key", large_binary_string), ] c = h2.connection.H2Connection() @@ -537,21 +536,19 @@ def test_oversize_headers(self): assert isinstance(headers_frame, hyperframe.frame.HeadersFrame) assert all( - map( - lambda f: isinstance(f, hyperframe.frame.ContinuationFrame), - continuation_frames) + (isinstance(f, hyperframe.frame.ContinuationFrame) for f in continuation_frames), ) assert all( - map(lambda f: len(f.data) <= c.max_outbound_frame_size, frames) + (len(f.data) <= c.max_outbound_frame_size for f in frames), ) - assert frames[0].flags == {'END_STREAM'} + assert frames[0].flags == {"END_STREAM"} buffer.add_data(data[-1:]) - headers = list(buffer)[0] + headers = next(iter(buffer)) assert isinstance(headers, hyperframe.frame.HeadersFrame) - def test_handle_stream_reset(self, frame_factory): + def test_handle_stream_reset(self, frame_factory) -> None: """ Streams being remotely reset fires a StreamReset event. """ @@ -575,7 +572,7 @@ def test_handle_stream_reset(self, frame_factory): assert isinstance(event.error_code, h2.errors.ErrorCodes) assert event.remote_reset - def test_handle_stream_reset_with_unknown_erorr_code(self, frame_factory): + def test_handle_stream_reset_with_unknown_erorr_code(self, frame_factory) -> None: """ Streams being remotely reset with unknown error codes behave exactly as they do with known error codes, but the error code on the event is an @@ -598,7 +595,7 @@ def test_handle_stream_reset_with_unknown_erorr_code(self, frame_factory): assert not isinstance(event.error_code, h2.errors.ErrorCodes) assert event.remote_reset - def test_can_consume_partial_data_from_connection(self): + def test_can_consume_partial_data_from_connection(self) -> None: """ We can do partial reads from the connection. """ @@ -611,7 +608,7 @@ def test_can_consume_partial_data_from_connection(self): assert len(c.data_to_send(10)) == 0 assert len(c.data_to_send()) == 0 - def test_we_can_update_settings(self, frame_factory): + def test_we_can_update_settings(self, frame_factory) -> None: """ Updating the settings emits a SETTINGS frame. """ @@ -629,7 +626,7 @@ def test_we_can_update_settings(self, frame_factory): f = frame_factory.build_settings_frame(new_settings) assert c.data_to_send() == f.serialize() - def test_settings_get_acked_correctly(self, frame_factory): + def test_settings_get_acked_correctly(self, frame_factory) -> None: """ When settings changes are ACKed, they contain the changed settings. """ @@ -653,7 +650,7 @@ def test_settings_get_acked_correctly(self, frame_factory): for setting, value in new_settings.items(): assert event.changed_settings[setting].new_value == value - def test_cannot_create_new_outbound_stream_over_limit(self, frame_factory): + def test_cannot_create_new_outbound_stream_over_limit(self, frame_factory) -> None: """ When the number of outbound streams exceeds the remote peer's MAX_CONCURRENT_STREAMS setting, attempting to open new streams fails. @@ -662,7 +659,7 @@ def test_cannot_create_new_outbound_stream_over_limit(self, frame_factory): c.initiate_connection() f = frame_factory.build_settings_frame( - {h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 1} + {h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 1}, ) c.receive_data(f.serialize())[0] @@ -671,12 +668,12 @@ def test_cannot_create_new_outbound_stream_over_limit(self, frame_factory): with pytest.raises(h2.exceptions.TooManyStreamsError): c.send_headers(3, self.example_request_headers) - def test_can_receive_trailers(self, frame_factory): + def test_can_receive_trailers(self, frame_factory) -> None: """ When two HEADERS blocks are received in the same stream from a server, the second set are trailers. """ - config = h2.config.H2Configuration(header_encoding='utf-8') + config = h2.config.H2Configuration(header_encoding="utf-8") c = h2.connection.H2Connection(config=config) c.initiate_connection() c.send_headers(1, self.example_request_headers) @@ -684,10 +681,10 @@ def test_can_receive_trailers(self, frame_factory): c.receive_data(f.serialize()) # Send in trailers. - trailers = [('content-length', '0')] + trailers = [("content-length", "0")] f = frame_factory.build_headers_frame( trailers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) events = c.receive_data(f.serialize()) assert len(events) == 2 @@ -697,7 +694,7 @@ def test_can_receive_trailers(self, frame_factory): assert event.headers == trailers assert event.stream_id == 1 - def test_reject_trailers_not_ending_stream(self, frame_factory): + def test_reject_trailers_not_ending_stream(self, frame_factory) -> None: """ When trailers are received without the END_STREAM flag being present, this is a ProtocolError. @@ -710,7 +707,7 @@ def test_reject_trailers_not_ending_stream(self, frame_factory): # Send in trailers. c.clear_outbound_data_buffer() - trailers = [('content-length', '0')] + trailers = [("content-length", "0")] f = frame_factory.build_headers_frame( trailers, flags=[], @@ -724,7 +721,7 @@ def test_reject_trailers_not_ending_stream(self, frame_factory): ) assert c.data_to_send() == expected_frame.serialize() - def test_can_send_trailers(self, frame_factory): + def test_can_send_trailers(self, frame_factory) -> None: """ When a second set of headers are sent, they are properly trailers. """ @@ -734,7 +731,7 @@ def test_can_send_trailers(self, frame_factory): c.send_headers(1, self.example_request_headers) # Now send trailers. - trailers = [('content-length', '0')] + trailers = [("content-length", "0")] c.send_headers(1, trailers, end_stream=True) frame_factory.refresh_encoder() @@ -743,11 +740,11 @@ def test_can_send_trailers(self, frame_factory): ) f2 = frame_factory.build_headers_frame( trailers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) assert c.data_to_send() == f1.serialize() + f2.serialize() - def test_trailers_must_have_end_stream(self, frame_factory): + def test_trailers_must_have_end_stream(self, frame_factory) -> None: """ A set of trailers must carry the END_STREAM flag. """ @@ -758,25 +755,17 @@ def test_trailers_must_have_end_stream(self, frame_factory): c.send_headers(1, self.example_request_headers) # Now send trailers. - trailers = [('content-length', '0')] + trailers = [("content-length", "0")] with pytest.raises(h2.exceptions.ProtocolError): c.send_headers(1, trailers) - def test_headers_are_lowercase(self, frame_factory): + def test_headers_are_lowercase(self, frame_factory) -> None: """ When headers are sent, they are forced to lower-case. """ - weird_headers = self.example_request_headers + [ - ('ChAnGiNg-CaSe', 'AlsoHere'), - ('alllowercase', 'alllowercase'), - ('ALLCAPS', 'ALLCAPS'), - ] - expected_headers = self.example_request_headers + [ - ('changing-case', 'AlsoHere'), - ('alllowercase', 'alllowercase'), - ('allcaps', 'ALLCAPS'), - ] + weird_headers = [*self.example_request_headers, ("ChAnGiNg-CaSe", "AlsoHere"), ("alllowercase", "alllowercase"), ("ALLCAPS", "ALLCAPS")] + expected_headers = [*self.example_request_headers, ("changing-case", "AlsoHere"), ("alllowercase", "alllowercase"), ("allcaps", "ALLCAPS")] c = h2.connection.H2Connection() c.initiate_connection() @@ -784,40 +773,40 @@ def test_headers_are_lowercase(self, frame_factory): c.send_headers(1, weird_headers) expected_frame = frame_factory.build_headers_frame( - headers=expected_headers + headers=expected_headers, ) assert c.data_to_send() == expected_frame.serialize() - def test_outbound_cookie_headers_are_split(self): + def test_outbound_cookie_headers_are_split(self) -> None: """ We should split outbound cookie headers according to RFC 7540 - 8.1.2.5 """ cookie_headers = [ - HeaderTuple('cookie', - 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), - ('cookie', 'path=1'), - ('cookie', 'test1=val1; test2=val2') + HeaderTuple("cookie", + "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC"), + ("cookie", "path=1"), + ("cookie", "test1=val1; test2=val2"), ] expected_cookie_headers = [ - HeaderTuple('cookie', 'username=John Doe'), - HeaderTuple('cookie', 'expires=Thu, 18 Dec 2013 12:00:00 UTC'), - ('cookie', 'path=1'), - ('cookie', 'test1=val1'), - ('cookie', 'test2=val2'), + HeaderTuple("cookie", "username=John Doe"), + HeaderTuple("cookie", "expires=Thu, 18 Dec 2013 12:00:00 UTC"), + ("cookie", "path=1"), + ("cookie", "test1=val1"), + ("cookie", "test2=val2"), ] client_config = h2.config.H2Configuration( client_side=True, - header_encoding='utf-8', - split_outbound_cookies=True + header_encoding="utf-8", + split_outbound_cookies=True, ) server_config = h2.config.H2Configuration( client_side=False, normalize_inbound_headers=False, - header_encoding='utf-8' + header_encoding="utf-8", ) client = h2.connection.H2Connection(config=client_config) server = h2.connection.H2Connection(config=server_config) @@ -827,13 +816,13 @@ def test_outbound_cookie_headers_are_split(self): e = server.receive_data(client.data_to_send()) - cookie_fields = [(n, v) for n, v in e[1].headers if n == 'cookie'] + cookie_fields = [(n, v) for n, v in e[1].headers if n == "cookie"] assert cookie_fields == expected_cookie_headers @given(frame_size=integers(min_value=2**14, max_value=(2**24 - 1))) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) - def test_changing_max_frame_size(self, frame_factory, frame_size): + def test_changing_max_frame_size(self, frame_factory, frame_size) -> None: """ When the user changes the max frame size and the change is ACKed, the remote peer is now bound by the new frame size. @@ -856,7 +845,7 @@ def test_changing_max_frame_size(self, frame_factory, frame_size): # Change the max frame size. c.update_settings( - {h2.settings.SettingCodes.MAX_FRAME_SIZE: frame_size} + {h2.settings.SettingCodes.MAX_FRAME_SIZE: frame_size}, ) settings_ack = frame_factory.build_settings_frame({}, ack=True) c.receive_data(settings_ack.serialize()) @@ -865,13 +854,13 @@ def test_changing_max_frame_size(self, frame_factory, frame_size): # flow control today. c.increment_flow_control_window(increment=(2 * frame_size) + 1) c.increment_flow_control_window( - increment=(2 * frame_size) + 1, stream_id=1 + increment=(2 * frame_size) + 1, stream_id=1, ) # Send one DATA frame that is exactly the max frame size: confirm it's # fine. data = frame_factory.build_data_frame( - data=(b'\x00' * frame_size), + data=(b"\x00" * frame_size), ) events = c.receive_data(data.serialize()) assert len(events) == 1 @@ -880,11 +869,11 @@ def test_changing_max_frame_size(self, frame_factory, frame_size): # Send one that is one byte too large: confirm a protocol error is # raised. - data.data += b'\x00' + data.data += b"\x00" with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(data.serialize()) - def test_cookies_are_joined_on_push(self, frame_factory): + def test_cookies_are_joined_on_push(self, frame_factory) -> None: """ RFC 7540 Section 8.1.2.5 requires that we join multiple Cookie headers in a header block together when they're received on a push. @@ -892,17 +881,17 @@ def test_cookies_are_joined_on_push(self, frame_factory): # This is a moderately varied set of cookie headers: some combined, # some split. cookie_headers = [ - ('cookie', - 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), - ('cookie', 'path=1'), - ('cookie', 'test1=val1; test2=val2') + ("cookie", + "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC"), + ("cookie", "path=1"), + ("cookie", "test1=val1; test2=val2"), ] expected = ( - 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC; ' - 'path=1; test1=val1; test2=val2' + "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC; " + "path=1; test1=val1; test2=val2" ) - config = h2.config.H2Configuration(header_encoding='utf-8') + config = h2.config.H2Configuration(header_encoding="utf-8") c = h2.connection.H2Connection(config=config) c.initiate_connection() c.send_headers(1, self.example_request_headers, end_stream=True) @@ -910,20 +899,20 @@ def test_cookies_are_joined_on_push(self, frame_factory): f = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, - headers=self.example_request_headers + cookie_headers + headers=self.example_request_headers + cookie_headers, ) events = c.receive_data(f.serialize()) assert len(events) == 1 e = events[0] - cookie_fields = [(n, v) for n, v in e.headers if n == 'cookie'] + cookie_fields = [(n, v) for n, v in e.headers if n == "cookie"] assert len(cookie_fields) == 1 _, v = cookie_fields[0] assert v == expected - def test_cookies_arent_joined_without_normalization(self, frame_factory): + def test_cookies_arent_joined_without_normalization(self, frame_factory) -> None: """ If inbound header normalization is disabled, cookie headers aren't joined. @@ -931,16 +920,16 @@ def test_cookies_arent_joined_without_normalization(self, frame_factory): # This is a moderately varied set of cookie headers: some combined, # some split. cookie_headers = [ - ('cookie', - 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), - ('cookie', 'path=1'), - ('cookie', 'test1=val1; test2=val2') + ("cookie", + "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC"), + ("cookie", "path=1"), + ("cookie", "test1=val1; test2=val2"), ] config = h2.config.H2Configuration( client_side=True, normalize_inbound_headers=False, - header_encoding='utf-8' + header_encoding="utf-8", ) c = h2.connection.H2Connection(config=config) c.initiate_connection() @@ -949,60 +938,61 @@ def test_cookies_arent_joined_without_normalization(self, frame_factory): f = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, - headers=self.example_request_headers + cookie_headers + headers=self.example_request_headers + cookie_headers, ) events = c.receive_data(f.serialize()) assert len(events) == 1 e = events[0] - received_cookies = [(n, v) for n, v in e.headers if n == 'cookie'] + received_cookies = [(n, v) for n, v in e.headers if n == "cookie"] assert len(received_cookies) == 3 assert cookie_headers == received_cookies -class TestBasicServer(object): +class TestBasicServer: """ Basic server-side tests. """ + example_request_headers = [ - (u':authority', u'example.com'), - (u':path', u'/'), - (u':scheme', u'https'), - (u':method', u'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] bytes_example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'hyper-h2/0.1.0') + (":status", "200"), + ("server", "hyper-h2/0.1.0"), ] server_config = h2.config.H2Configuration( - client_side=False, header_encoding='utf-8' + client_side=False, header_encoding="utf-8", ) - def test_ignores_preamble(self): + def test_ignores_preamble(self) -> None: """ The preamble does not cause any events or frames to be written. """ c = h2.connection.H2Connection(config=self.server_config) - preamble = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + preamble = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" events = c.receive_data(preamble) assert not events assert not c.data_to_send() @pytest.mark.parametrize("chunk_size", range(1, 24)) - def test_drip_feed_preamble(self, chunk_size): + def test_drip_feed_preamble(self, chunk_size) -> None: """ The preamble can be sent in in less than a single buffer. """ c = h2.connection.H2Connection(config=self.server_config) - preamble = b'PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n' + preamble = b"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n" events = [] for i in range(0, len(preamble), chunk_size): @@ -1011,14 +1001,14 @@ def test_drip_feed_preamble(self, chunk_size): assert not events assert not c.data_to_send() - def test_initiate_connection_sends_server_preamble(self, frame_factory): + def test_initiate_connection_sends_server_preamble(self, frame_factory) -> None: """ For server-side connections, initiate_connection sends a server preamble. """ c = h2.connection.H2Connection(config=self.server_config) expected_settings = frame_factory.build_settings_frame( - c.local_settings + c.local_settings, ) expected_data = expected_settings.serialize() @@ -1026,7 +1016,7 @@ def test_initiate_connection_sends_server_preamble(self, frame_factory): assert not events assert c.data_to_send() == expected_data - def test_headers_event(self, frame_factory): + def test_headers_event(self, frame_factory) -> None: """ When a headers frame is received a RequestReceived event fires. """ @@ -1044,13 +1034,13 @@ def test_headers_event(self, frame_factory): assert event.stream_id == 1 assert event.headers == self.example_request_headers - def test_headers_event_bytes(self, frame_factory): + def test_headers_event_bytes(self, frame_factory) -> None: """ When a headers frame is received a RequestReceived event fires with bytes headers if the encoding is set appropriately. """ config = h2.config.H2Configuration( - client_side=False, header_encoding=False + client_side=False, header_encoding=False, ) c = h2.connection.H2Connection(config=config) c.receive_data(frame_factory.preamble()) @@ -1066,7 +1056,7 @@ def test_headers_event_bytes(self, frame_factory): assert event.stream_id == 1 assert event.headers == self.bytes_example_request_headers - def test_data_event(self, frame_factory): + def test_data_event(self, frame_factory) -> None: """ Test that data received on a stream fires a DataReceived event. """ @@ -1074,13 +1064,13 @@ def test_data_event(self, frame_factory): c.receive_data(frame_factory.preamble()) f1 = frame_factory.build_headers_frame( - self.example_request_headers, stream_id=3 + self.example_request_headers, stream_id=3, ) f2 = frame_factory.build_data_frame( - b'some request data', + b"some request data", stream_id=3, ) - data = b''.join(map(lambda f: f.serialize(), [f1, f2])) + data = b"".join(f.serialize() for f in [f1, f2]) events = c.receive_data(data) assert len(events) == 2 @@ -1088,10 +1078,10 @@ def test_data_event(self, frame_factory): assert isinstance(event, h2.events.DataReceived) assert event.stream_id == 3 - assert event.data == b'some request data' + assert event.data == b"some request data" assert event.flow_controlled_length == 17 - def test_data_event_with_padding(self, frame_factory): + def test_data_event_with_padding(self, frame_factory) -> None: """ Test that data received on a stream fires a DataReceived event that accounts for padding. @@ -1100,14 +1090,14 @@ def test_data_event_with_padding(self, frame_factory): c.receive_data(frame_factory.preamble()) f1 = frame_factory.build_headers_frame( - self.example_request_headers, stream_id=3 + self.example_request_headers, stream_id=3, ) f2 = frame_factory.build_data_frame( - b'some request data', + b"some request data", stream_id=3, - padding_len=20 + padding_len=20, ) - data = b''.join(map(lambda f: f.serialize(), [f1, f2])) + data = b"".join(f.serialize() for f in [f1, f2]) events = c.receive_data(data) assert len(events) == 2 @@ -1115,20 +1105,20 @@ def test_data_event_with_padding(self, frame_factory): assert isinstance(event, h2.events.DataReceived) assert event.stream_id == 3 - assert event.data == b'some request data' + assert event.data == b"some request data" assert event.flow_controlled_length == 17 + 20 + 1 - def test_receiving_ping_frame(self, frame_factory): + def test_receiving_ping_frame(self, frame_factory) -> None: """ Ping frames should be immediately ACKed. """ c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) - ping_data = b'\x01' * 8 + ping_data = b"\x01" * 8 sent_frame = frame_factory.build_ping_frame(ping_data) expected_frame = frame_factory.build_ping_frame( - ping_data, flags=["ACK"] + ping_data, flags=["ACK"], ) expected_data = expected_frame.serialize() @@ -1143,7 +1133,7 @@ def test_receiving_ping_frame(self, frame_factory): assert c.data_to_send() == expected_data - def test_receiving_settings_frame_event(self, frame_factory): + def test_receiving_settings_frame_event(self, frame_factory) -> None: """ Settings frames should cause a RemoteSettingsChanged event to fire. """ @@ -1151,7 +1141,7 @@ def test_receiving_settings_frame_event(self, frame_factory): c.receive_data(frame_factory.preamble()) f = frame_factory.build_settings_frame( - settings=helpers.SAMPLE_SETTINGS + settings=helpers.SAMPLE_SETTINGS, ) events = c.receive_data(f.serialize()) @@ -1161,7 +1151,7 @@ def test_receiving_settings_frame_event(self, frame_factory): assert isinstance(event, h2.events.RemoteSettingsChanged) assert len(event.changed_settings) == len(helpers.SAMPLE_SETTINGS) - def test_acknowledging_settings(self, frame_factory): + def test_acknowledging_settings(self, frame_factory) -> None: """ Acknowledging settings causes appropriate Settings frame to be emitted. """ @@ -1169,10 +1159,10 @@ def test_acknowledging_settings(self, frame_factory): c.receive_data(frame_factory.preamble()) received_frame = frame_factory.build_settings_frame( - settings=helpers.SAMPLE_SETTINGS + settings=helpers.SAMPLE_SETTINGS, ) expected_frame = frame_factory.build_settings_frame( - settings={}, ack=True + settings={}, ack=True, ) expected_data = expected_frame.serialize() @@ -1182,7 +1172,7 @@ def test_acknowledging_settings(self, frame_factory): assert len(events) == 1 assert c.data_to_send() == expected_data - def test_close_connection(self, frame_factory): + def test_close_connection(self, frame_factory) -> None: """ Closing the connection with no error code emits a GOAWAY frame with error code 0. @@ -1199,7 +1189,7 @@ def test_close_connection(self, frame_factory): assert c.data_to_send() == expected_data @pytest.mark.parametrize("error_code", h2.errors.ErrorCodes) - def test_close_connection_with_error_code(self, frame_factory, error_code): + def test_close_connection_with_error_code(self, frame_factory, error_code) -> None: """ Closing the connection with an error code emits a GOAWAY frame with that error code. @@ -1207,7 +1197,7 @@ def test_close_connection_with_error_code(self, frame_factory, error_code): c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) f = frame_factory.build_goaway_frame( - error_code=error_code, last_stream_id=0 + error_code=error_code, last_stream_id=0, ) expected_data = f.serialize() @@ -1217,13 +1207,13 @@ def test_close_connection_with_error_code(self, frame_factory, error_code): assert not events assert c.data_to_send() == expected_data - @pytest.mark.parametrize("last_stream_id,output", [ + @pytest.mark.parametrize(("last_stream_id", "output"), [ (None, 23), (0, 0), - (42, 42) + (42, 42), ]) def test_close_connection_with_last_stream_id(self, frame_factory, - last_stream_id, output): + last_stream_id, output) -> None: """ Closing the connection with last_stream_id set emits a GOAWAY frame with that value. @@ -1232,16 +1222,16 @@ def test_close_connection_with_last_stream_id(self, frame_factory, c.receive_data(frame_factory.preamble()) headers_frame = frame_factory.build_headers_frame( [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ], stream_id=23) c.receive_data(headers_frame.serialize()) f = frame_factory.build_goaway_frame( - last_stream_id=output + last_stream_id=output, ) expected_data = f.serialize() @@ -1251,13 +1241,13 @@ def test_close_connection_with_last_stream_id(self, frame_factory, assert not events assert c.data_to_send() == expected_data - @pytest.mark.parametrize("additional_data,output", [ - (None, b''), - (b'', b''), - (b'foobar', b'foobar') + @pytest.mark.parametrize(("additional_data", "output"), [ + (None, b""), + (b"", b""), + (b"foobar", b"foobar"), ]) def test_close_connection_with_additional_data(self, frame_factory, - additional_data, output): + additional_data, output) -> None: """ Closing the connection with additional debug data emits a GOAWAY frame with that data attached. @@ -1265,7 +1255,7 @@ def test_close_connection_with_additional_data(self, frame_factory, c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) f = frame_factory.build_goaway_frame( - last_stream_id=0, additional_data=output + last_stream_id=0, additional_data=output, ) expected_data = f.serialize() @@ -1275,7 +1265,7 @@ def test_close_connection_with_additional_data(self, frame_factory, assert not events assert c.data_to_send() == expected_data - def test_reset_stream(self, frame_factory): + def test_reset_stream(self, frame_factory) -> None: """ Resetting a stream with no error code emits a RST_STREAM frame with error code 0. @@ -1295,7 +1285,7 @@ def test_reset_stream(self, frame_factory): assert c.data_to_send() == expected_data @pytest.mark.parametrize("error_code", h2.errors.ErrorCodes) - def test_reset_stream_with_error_code(self, frame_factory, error_code): + def test_reset_stream_with_error_code(self, frame_factory, error_code) -> None: """ Resetting a stream with an error code emits a RST_STREAM frame with that error code. @@ -1304,13 +1294,13 @@ def test_reset_stream_with_error_code(self, frame_factory, error_code): c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( self.example_request_headers, - stream_id=3 + stream_id=3, ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() expected_frame = frame_factory.build_rst_stream_frame( - stream_id=3, error_code=error_code + stream_id=3, error_code=error_code, ) expected_data = expected_frame.serialize() @@ -1319,7 +1309,7 @@ def test_reset_stream_with_error_code(self, frame_factory, error_code): assert not events assert c.data_to_send() == expected_data - def test_cannot_reset_nonexistent_stream(self, frame_factory): + def test_cannot_reset_nonexistent_stream(self, frame_factory) -> None: """ Resetting nonexistent streams raises NoSuchStreamError. """ @@ -1327,7 +1317,7 @@ def test_cannot_reset_nonexistent_stream(self, frame_factory): c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( self.example_request_headers, - stream_id=3 + stream_id=3, ) c.receive_data(f.serialize()) @@ -1341,7 +1331,7 @@ def test_cannot_reset_nonexistent_stream(self, frame_factory): assert e.value.stream_id == 5 - def test_basic_sending_ping_frame_logic(self, frame_factory): + def test_basic_sending_ping_frame_logic(self, frame_factory) -> None: """ Sending ping frames serializes a ping frame on stream 0 with appropriate opaque data. @@ -1350,7 +1340,7 @@ def test_basic_sending_ping_frame_logic(self, frame_factory): c.receive_data(frame_factory.preamble()) c.clear_outbound_data_buffer() - ping_data = b'\x01\x02\x03\x04\x05\x06\x07\x08' + ping_data = b"\x01\x02\x03\x04\x05\x06\x07\x08" expected_frame = frame_factory.build_ping_frame(ping_data) expected_data = expected_frame.serialize() @@ -1361,17 +1351,17 @@ def test_basic_sending_ping_frame_logic(self, frame_factory): assert c.data_to_send() == expected_data @pytest.mark.parametrize( - 'opaque_data', + "opaque_data", [ - b'', - b'\x01\x02\x03\x04\x05\x06\x07', - u'abcdefgh', - b'too many bytes', - ] + b"", + b"\x01\x02\x03\x04\x05\x06\x07", + "abcdefgh", + b"too many bytes", + ], ) def test_ping_frame_opaque_data_must_be_length_8_bytestring(self, frame_factory, - opaque_data): + opaque_data) -> None: """ Sending a ping frame only works with 8-byte bytestrings. """ @@ -1381,17 +1371,17 @@ def test_ping_frame_opaque_data_must_be_length_8_bytestring(self, with pytest.raises(ValueError): c.ping(opaque_data) - def test_receiving_ping_acknowledgement(self, frame_factory): + def test_receiving_ping_acknowledgement(self, frame_factory) -> None: """ Receiving a PING acknowledgement fires a PingAckReceived event. """ c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) - ping_data = b'\x01\x02\x03\x04\x05\x06\x07\x08' + ping_data = b"\x01\x02\x03\x04\x05\x06\x07\x08" f = frame_factory.build_ping_frame( - ping_data, flags=['ACK'] + ping_data, flags=["ACK"], ) events = c.receive_data(f.serialize()) @@ -1401,7 +1391,7 @@ def test_receiving_ping_acknowledgement(self, frame_factory): assert isinstance(event, h2.events.PingAckReceived) assert event.ping_data == ping_data - def test_stream_ended_remotely(self, frame_factory): + def test_stream_ended_remotely(self, frame_factory) -> None: """ When the remote stream ends with a non-empty data frame a DataReceived event and a StreamEnded event are fired. @@ -1410,14 +1400,14 @@ def test_stream_ended_remotely(self, frame_factory): c.receive_data(frame_factory.preamble()) f1 = frame_factory.build_headers_frame( - self.example_request_headers, stream_id=3 + self.example_request_headers, stream_id=3, ) f2 = frame_factory.build_data_frame( - b'some request data', - flags=['END_STREAM'], + b"some request data", + flags=["END_STREAM"], stream_id=3, ) - data = b''.join(map(lambda f: f.serialize(), [f1, f2])) + data = b"".join(f.serialize() for f in [f1, f2]) events = c.receive_data(data) assert len(events) == 3 @@ -1428,14 +1418,14 @@ def test_stream_ended_remotely(self, frame_factory): assert isinstance(stream_ended_event, h2.events.StreamEnded) stream_ended_event.stream_id == 3 - def test_can_push_stream(self, frame_factory): + def test_can_push_stream(self, frame_factory) -> None: """ Pushing a stream causes a PUSH_PROMISE frame to be emitted. """ c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - self.example_request_headers + self.example_request_headers, ) c.receive_data(f.serialize()) @@ -1444,31 +1434,31 @@ def test_can_push_stream(self, frame_factory): stream_id=1, promised_stream_id=2, headers=self.example_request_headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) c.clear_outbound_data_buffer() c.push_stream( stream_id=1, promised_stream_id=2, - request_headers=self.example_request_headers + request_headers=self.example_request_headers, ) assert c.data_to_send() == expected_frame.serialize() - def test_cannot_push_streams_when_disabled(self, frame_factory): + def test_cannot_push_streams_when_disabled(self, frame_factory) -> None: """ When the remote peer has disabled stream pushing, we should fail. """ c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) f = frame_factory.build_settings_frame( - {h2.settings.SettingCodes.ENABLE_PUSH: 0} + {h2.settings.SettingCodes.ENABLE_PUSH: 0}, ) c.receive_data(f.serialize()) f = frame_factory.build_headers_frame( - self.example_request_headers + self.example_request_headers, ) c.receive_data(f.serialize()) @@ -1476,10 +1466,10 @@ def test_cannot_push_streams_when_disabled(self, frame_factory): c.push_stream( stream_id=1, promised_stream_id=2, - request_headers=self.example_request_headers + request_headers=self.example_request_headers, ) - def test_settings_remote_change_header_table_size(self, frame_factory): + def test_settings_remote_change_header_table_size(self, frame_factory) -> None: """ Acknowledging a remote HEADER_TABLE_SIZE settings change causes us to change the header table size of our encoder. @@ -1490,13 +1480,13 @@ def test_settings_remote_change_header_table_size(self, frame_factory): assert c.encoder.header_table_size == 4096 received_frame = frame_factory.build_settings_frame( - {h2.settings.SettingCodes.HEADER_TABLE_SIZE: 80} + {h2.settings.SettingCodes.HEADER_TABLE_SIZE: 80}, ) c.receive_data(received_frame.serialize())[0] assert c.encoder.header_table_size == 80 - def test_settings_local_change_header_table_size(self, frame_factory): + def test_settings_local_change_header_table_size(self, frame_factory) -> None: """ The remote peer acknowledging a local HEADER_TABLE_SIZE settings change does not cause us to change the header table size of our decoder. @@ -1510,14 +1500,14 @@ def test_settings_local_change_header_table_size(self, frame_factory): expected_frame = frame_factory.build_settings_frame({}, ack=True) c.update_settings( - {h2.settings.SettingCodes.HEADER_TABLE_SIZE: 80} + {h2.settings.SettingCodes.HEADER_TABLE_SIZE: 80}, ) c.receive_data(expected_frame.serialize()) c.clear_outbound_data_buffer() assert c.decoder.header_table_size == 4096 - def test_restricting_outbound_frame_size_by_settings(self, frame_factory): + def test_restricting_outbound_frame_size_by_settings(self, frame_factory) -> None: """ The remote peer can shrink the maximum outbound frame size using settings. @@ -1531,17 +1521,17 @@ def test_restricting_outbound_frame_size_by_settings(self, frame_factory): c.clear_outbound_data_buffer() with pytest.raises(h2.exceptions.FrameTooLargeError): - c.send_data(1, b'\x01' * 17000) + c.send_data(1, b"\x01" * 17000) received_frame = frame_factory.build_settings_frame( - {h2.settings.SettingCodes.MAX_FRAME_SIZE: 17001} + {h2.settings.SettingCodes.MAX_FRAME_SIZE: 17001}, ) c.receive_data(received_frame.serialize()) - c.send_data(1, b'\x01' * 17000) + c.send_data(1, b"\x01" * 17000) assert c.data_to_send() - def test_restricting_inbound_frame_size_by_settings(self, frame_factory): + def test_restricting_inbound_frame_size_by_settings(self, frame_factory) -> None: """ We throw ProtocolErrors and tear down connections if oversize frames are received. @@ -1552,17 +1542,17 @@ def test_restricting_inbound_frame_size_by_settings(self, frame_factory): c.receive_data(h.serialize()) c.clear_outbound_data_buffer() - data_frame = frame_factory.build_data_frame(b'\x01' * 17000) + data_frame = frame_factory.build_data_frame(b"\x01" * 17000) with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(data_frame.serialize()) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=1, error_code=h2.errors.ErrorCodes.FRAME_SIZE_ERROR + last_stream_id=1, error_code=h2.errors.ErrorCodes.FRAME_SIZE_ERROR, ) assert c.data_to_send() == expected_frame.serialize() - def test_cannot_receive_new_streams_over_limit(self, frame_factory): + def test_cannot_receive_new_streams_over_limit(self, frame_factory) -> None: """ When the number of inbound streams exceeds our MAX_CONCURRENT_STREAMS setting, their attempt to open new streams fails. @@ -1570,7 +1560,7 @@ def test_cannot_receive_new_streams_over_limit(self, frame_factory): c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) c.update_settings( - {h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 1} + {h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 1}, ) f = frame_factory.build_settings_frame({}, ack=True) c.receive_data(f.serialize()) @@ -1594,7 +1584,7 @@ def test_cannot_receive_new_streams_over_limit(self, frame_factory): ) assert c.data_to_send() == expected_frame.serialize() - def test_can_receive_trailers(self, frame_factory): + def test_can_receive_trailers(self, frame_factory) -> None: """ When two HEADERS blocks are received in the same stream from a client, the second set are trailers. @@ -1605,10 +1595,10 @@ def test_can_receive_trailers(self, frame_factory): c.receive_data(f.serialize()) # Send in trailers. - trailers = [('content-length', '0')] + trailers = [("content-length", "0")] f = frame_factory.build_headers_frame( trailers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) events = c.receive_data(f.serialize()) assert len(events) == 2 @@ -1618,7 +1608,7 @@ def test_can_receive_trailers(self, frame_factory): assert event.headers == trailers assert event.stream_id == 1 - def test_reject_trailers_not_ending_stream(self, frame_factory): + def test_reject_trailers_not_ending_stream(self, frame_factory) -> None: """ When trailers are received without the END_STREAM flag being present, this is a ProtocolError. @@ -1630,7 +1620,7 @@ def test_reject_trailers_not_ending_stream(self, frame_factory): # Send in trailers. c.clear_outbound_data_buffer() - trailers = [('content-length', '0')] + trailers = [("content-length", "0")] f = frame_factory.build_headers_frame( trailers, flags=[], @@ -1644,7 +1634,7 @@ def test_reject_trailers_not_ending_stream(self, frame_factory): ) assert c.data_to_send() == expected_frame.serialize() - def test_can_send_trailers(self, frame_factory): + def test_can_send_trailers(self, frame_factory) -> None: """ When a second set of headers are sent, they are properly trailers. """ @@ -1658,7 +1648,7 @@ def test_can_send_trailers(self, frame_factory): c.send_headers(1, self.example_response_headers) # Now send trailers. - trailers = [('content-length', '0')] + trailers = [("content-length", "0")] c.send_headers(1, trailers, end_stream=True) frame_factory.refresh_encoder() @@ -1667,11 +1657,11 @@ def test_can_send_trailers(self, frame_factory): ) f2 = frame_factory.build_headers_frame( trailers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) assert c.data_to_send() == f1.serialize() + f2.serialize() - def test_trailers_must_have_end_stream(self, frame_factory): + def test_trailers_must_have_end_stream(self, frame_factory) -> None: """ A set of trailers must carry the END_STREAM flag. """ @@ -1684,18 +1674,18 @@ def test_trailers_must_have_end_stream(self, frame_factory): c.send_headers(1, self.example_response_headers) # Now send trailers. - trailers = [('content-length', '0')] + trailers = [("content-length", "0")] with pytest.raises(h2.exceptions.ProtocolError): c.send_headers(1, trailers) @pytest.mark.parametrize("frame_id", range(12, 256)) - def test_unknown_frames_are_ignored(self, frame_factory, frame_id): + def test_unknown_frames_are_ignored(self, frame_factory, frame_id) -> None: c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) c.clear_outbound_data_buffer() - f = frame_factory.build_data_frame(data=b'abcdefghtdst') + f = frame_factory.build_data_frame(data=b"abcdefghtdst") f.type = frame_id events = c.receive_data(f.serialize()) @@ -1704,7 +1694,7 @@ def test_unknown_frames_are_ignored(self, frame_factory, frame_id): assert isinstance(events[0], h2.events.UnknownFrameReceived) assert isinstance(events[0].frame, hyperframe.frame.ExtensionFrame) - def test_can_send_goaway_repeatedly(self, frame_factory): + def test_can_send_goaway_repeatedly(self, frame_factory) -> None: """ We can send a GOAWAY frame as many times as we like. """ @@ -1720,7 +1710,7 @@ def test_can_send_goaway_repeatedly(self, frame_factory): assert c.data_to_send() == (f.serialize() * 3) - def test_receiving_goaway_frame(self, frame_factory): + def test_receiving_goaway_frame(self, frame_factory) -> None: """ Receiving a GOAWAY frame causes a ConnectionTerminated event to be fired and transitions the connection to the CLOSED state, and clears @@ -1731,7 +1721,7 @@ def test_receiving_goaway_frame(self, frame_factory): c.receive_data(frame_factory.preamble()) f = frame_factory.build_goaway_frame( - last_stream_id=5, error_code=h2.errors.ErrorCodes.SETTINGS_TIMEOUT + last_stream_id=5, error_code=h2.errors.ErrorCodes.SETTINGS_TIMEOUT, ) events = c.receive_data(f.serialize()) @@ -1747,7 +1737,7 @@ def test_receiving_goaway_frame(self, frame_factory): assert not c.data_to_send() - def test_receiving_multiple_goaway_frames(self, frame_factory): + def test_receiving_multiple_goaway_frames(self, frame_factory) -> None: """ Multiple GOAWAY frames can be received at once, and are allowed. Each one fires a ConnectionTerminated event. @@ -1765,7 +1755,7 @@ def test_receiving_multiple_goaway_frames(self, frame_factory): for event in events ) - def test_receiving_goaway_frame_with_additional_data(self, frame_factory): + def test_receiving_goaway_frame_with_additional_data(self, frame_factory) -> None: """ GOAWAY frame can contain additional data, it should be available via ConnectionTerminated event. @@ -1774,7 +1764,7 @@ def test_receiving_goaway_frame_with_additional_data(self, frame_factory): c.initiate_connection() c.receive_data(frame_factory.preamble()) - additional_data = b'debug data' + additional_data = b"debug data" f = frame_factory.build_goaway_frame(last_stream_id=0, additional_data=additional_data) events = c.receive_data(f.serialize()) @@ -1785,7 +1775,7 @@ def test_receiving_goaway_frame_with_additional_data(self, frame_factory): assert isinstance(event, h2.events.ConnectionTerminated) assert event.additional_data == additional_data - def test_receiving_goaway_frame_with_unknown_error(self, frame_factory): + def test_receiving_goaway_frame_with_unknown_error(self, frame_factory) -> None: """ Receiving a GOAWAY frame with an unknown error code behaves exactly the same as receiving one we know about, but the code is reported as an @@ -1796,7 +1786,7 @@ def test_receiving_goaway_frame_with_unknown_error(self, frame_factory): c.receive_data(frame_factory.preamble()) f = frame_factory.build_goaway_frame( - last_stream_id=5, error_code=0xFA + last_stream_id=5, error_code=0xFA, ) events = c.receive_data(f.serialize()) @@ -1812,7 +1802,7 @@ def test_receiving_goaway_frame_with_unknown_error(self, frame_factory): assert not c.data_to_send() - def test_cookies_are_joined(self, frame_factory): + def test_cookies_are_joined(self, frame_factory) -> None: """ RFC 7540 Section 8.1.2.5 requires that we join multiple Cookie headers in a header block together. @@ -1820,14 +1810,14 @@ def test_cookies_are_joined(self, frame_factory): # This is a moderately varied set of cookie headers: some combined, # some split. cookie_headers = [ - ('cookie', - 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), - ('cookie', 'path=1'), - ('cookie', 'test1=val1; test2=val2') + ("cookie", + "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC"), + ("cookie", "path=1"), + ("cookie", "test1=val1; test2=val2"), ] expected = ( - 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC; ' - 'path=1; test1=val1; test2=val2' + "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC; " + "path=1; test1=val1; test2=val2" ) c = h2.connection.H2Connection(config=self.server_config) @@ -1835,20 +1825,20 @@ def test_cookies_are_joined(self, frame_factory): c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - self.example_request_headers + cookie_headers + self.example_request_headers + cookie_headers, ) events = c.receive_data(f.serialize()) assert len(events) == 1 e = events[0] - cookie_fields = [(n, v) for n, v in e.headers if n == 'cookie'] + cookie_fields = [(n, v) for n, v in e.headers if n == "cookie"] assert len(cookie_fields) == 1 _, v = cookie_fields[0] assert v == expected - def test_cookies_arent_joined_without_normalization(self, frame_factory): + def test_cookies_arent_joined_without_normalization(self, frame_factory) -> None: """ If inbound header normalization is disabled, cookie headers aren't joined. @@ -1856,34 +1846,34 @@ def test_cookies_arent_joined_without_normalization(self, frame_factory): # This is a moderately varied set of cookie headers: some combined, # some split. cookie_headers = [ - ('cookie', - 'username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC'), - ('cookie', 'path=1'), - ('cookie', 'test1=val1; test2=val2') + ("cookie", + "username=John Doe; expires=Thu, 18 Dec 2013 12:00:00 UTC"), + ("cookie", "path=1"), + ("cookie", "test1=val1; test2=val2"), ] config = h2.config.H2Configuration( client_side=False, normalize_inbound_headers=False, - header_encoding='utf-8' + header_encoding="utf-8", ) c = h2.connection.H2Connection(config=config) c.initiate_connection() c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - self.example_request_headers + cookie_headers + self.example_request_headers + cookie_headers, ) events = c.receive_data(f.serialize()) assert len(events) == 1 e = events[0] - received_cookies = [(n, v) for n, v in e.headers if n == 'cookie'] + received_cookies = [(n, v) for n, v in e.headers if n == "cookie"] assert len(received_cookies) == 3 assert cookie_headers == received_cookies - def test_stream_repr(self): + def test_stream_repr(self) -> None: """ Ensure stream string representation is appropriate. """ @@ -1894,20 +1884,19 @@ def test_stream_repr(self): def sanity_check_data_frame(data_frame, expected_flow_controlled_length, expect_padded_flag, - expected_data_frame_pad_length): + expected_data_frame_pad_length) -> None: """ ``data_frame`` is a frame of type ``hyperframe.frame.DataFrame``, and the ``flags`` and ``flow_controlled_length`` of ``data_frame`` match expectations. """ - assert isinstance(data_frame, hyperframe.frame.DataFrame) assert data_frame.flow_controlled_length == expected_flow_controlled_length if expect_padded_flag: - assert 'PADDED' in data_frame.flags + assert "PADDED" in data_frame.flags else: - assert 'PADDED' not in data_frame.flags + assert "PADDED" not in data_frame.flags assert data_frame.pad_length == expected_data_frame_pad_length diff --git a/tests/test_closed_streams.py b/tests/test_closed_streams.py index 346321bf7..aaf1f5d34 100644 --- a/tests/test_closed_streams.py +++ b/tests/test_closed_streams.py @@ -1,9 +1,8 @@ """ -test_closed_streams -~~~~~~~~~~~~~~~~~~~ - Tests that we handle closed streams correctly. """ +from __future__ import annotations + import pytest import h2.config @@ -13,20 +12,20 @@ import h2.exceptions -class TestClosedStreams(object): +class TestClosedStreams: example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) - def test_can_receive_multiple_rst_stream_frames(self, frame_factory): + def test_can_receive_multiple_rst_stream_frames(self, frame_factory) -> None: """ Multiple RST_STREAM frames can be received, either at once or well after one another. Only the first fires an event. @@ -49,7 +48,7 @@ def test_can_receive_multiple_rst_stream_frames(self, frame_factory): assert isinstance(event, h2.events.StreamReset) - def test_receiving_low_stream_id_causes_goaway(self, frame_factory): + def test_receiving_low_stream_id_causes_goaway(self, frame_factory) -> None: """ The remote peer creating a stream with a lower ID than one we've seen causes a GOAWAY frame. @@ -82,7 +81,7 @@ def test_receiving_low_stream_id_causes_goaway(self, frame_factory): ) assert c.data_to_send() == f.serialize() - def test_closed_stream_not_present_in_streams_dict(self, frame_factory): + def test_closed_stream_not_present_in_streams_dict(self, frame_factory) -> None: """ When streams have been closed, they get removed from the streams dictionary the next time we count the open streams. @@ -106,7 +105,7 @@ def test_closed_stream_not_present_in_streams_dict(self, frame_factory): # The streams dictionary should be empty. assert not c.streams - def test_receive_rst_stream_on_closed_stream(self, frame_factory): + def test_receive_rst_stream_on_closed_stream(self, frame_factory) -> None: """ RST_STREAM frame should be ignored if stream is in a closed state. See RFC 7540 Section 5.1 (closed state) @@ -119,15 +118,15 @@ def test_receive_rst_stream_on_closed_stream(self, frame_factory): # Some time passes and client sends DATA frame and closes stream, # so it is in a half-closed state - c.send_data(1, b'some data', end_stream=True) + c.send_data(1, b"some data", end_stream=True) # Server received HEADERS frame but DATA frame is still on the way. # Stream is in open state on the server-side. In this state server is # allowed to end stream and reset it - this trick helps immediately # close stream on the server-side. headers_frame = frame_factory.build_headers_frame( - [(':status', '200')], - flags=['END_STREAM'], + [(":status", "200")], + flags=["END_STREAM"], stream_id=1, ) events = c.receive_data(headers_frame.serialize()) @@ -140,7 +139,7 @@ def test_receive_rst_stream_on_closed_stream(self, frame_factory): events = c.receive_data(rst_stream_frame.serialize()) assert not events - def test_receive_window_update_on_closed_stream(self, frame_factory): + def test_receive_window_update_on_closed_stream(self, frame_factory) -> None: """ WINDOW_UPDATE frame should be ignored if stream is in a closed state. See RFC 7540 Section 5.1 (closed state) @@ -153,15 +152,15 @@ def test_receive_window_update_on_closed_stream(self, frame_factory): # Some time passes and client sends DATA frame and closes stream, # so it is in a half-closed state - c.send_data(1, b'some data', end_stream=True) + c.send_data(1, b"some data", end_stream=True) # Server received HEADERS frame but DATA frame is still on the way. # Stream is in open state on the server-side. In this state server is # allowed to end stream and after that acknowledge received data by # sending WINDOW_UPDATE frames. headers_frame = frame_factory.build_headers_frame( - [(':status', '200')], - flags=['END_STREAM'], + [(":status", "200")], + flags=["END_STREAM"], stream_id=1, ) events = c.receive_data(headers_frame.serialize()) @@ -186,16 +185,16 @@ def test_receive_window_update_on_closed_stream(self, frame_factory): assert not events -class TestStreamsClosedByEndStream(object): +class TestStreamsClosedByEndStream: example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) @@ -203,16 +202,16 @@ class TestStreamsClosedByEndStream(object): "frame", [ lambda self, ff: ff.build_headers_frame( - self.example_request_headers, flags=['END_STREAM']), + self.example_request_headers, flags=["END_STREAM"]), lambda self, ff: ff.build_headers_frame( self.example_request_headers), - ] + ], ) @pytest.mark.parametrize("clear_streams", [True, False]) def test_frames_after_recv_end_will_error(self, frame_factory, frame, - clear_streams): + clear_streams) -> None: """ A stream that is closed by receiving END_STREAM raises ProtocolError when it receives an unexpected frame. @@ -222,13 +221,13 @@ def test_frames_after_recv_end_will_error(self, c.initiate_connection() f = frame_factory.build_headers_frame( - self.example_request_headers, flags=['END_STREAM'] + self.example_request_headers, flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.send_headers( stream_id=1, headers=self.example_response_headers, - end_stream=True + end_stream=True, ) if clear_streams: @@ -252,16 +251,16 @@ def test_frames_after_recv_end_will_error(self, "frame", [ lambda self, ff: ff.build_headers_frame( - self.example_response_headers, flags=['END_STREAM']), + self.example_response_headers, flags=["END_STREAM"]), lambda self, ff: ff.build_headers_frame( self.example_response_headers), - ] + ], ) @pytest.mark.parametrize("clear_streams", [True, False]) def test_frames_after_send_end_will_error(self, frame_factory, frame, - clear_streams): + clear_streams) -> None: """ A stream that is closed by sending END_STREAM raises ProtocolError when it receives an unexpected frame. @@ -272,7 +271,7 @@ def test_frames_after_send_end_will_error(self, end_stream=True) f = frame_factory.build_headers_frame( - self.example_response_headers, flags=['END_STREAM'] + self.example_response_headers, flags=["END_STREAM"], ) c.receive_data(f.serialize()) @@ -297,12 +296,12 @@ def test_frames_after_send_end_will_error(self, "frame", [ lambda self, ff: ff.build_window_update_frame(1, 1), - lambda self, ff: ff.build_rst_stream_frame(1) - ] + lambda self, ff: ff.build_rst_stream_frame(1), + ], ) def test_frames_after_send_end_will_be_ignored(self, frame_factory, - frame): + frame) -> None: """ A stream that is closed by sending END_STREAM will raise ProtocolError when received unexpected frame. @@ -312,13 +311,13 @@ def test_frames_after_send_end_will_be_ignored(self, c.initiate_connection() f = frame_factory.build_headers_frame( - self.example_request_headers, flags=['END_STREAM'] + self.example_request_headers, flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.send_headers( stream_id=1, headers=self.example_response_headers, - end_stream=True + end_stream=True, ) c.clear_outbound_data_buffer() @@ -329,16 +328,16 @@ def test_frames_after_send_end_will_be_ignored(self, assert not events -class TestStreamsClosedByRstStream(object): +class TestStreamsClosedByRstStream: example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) @@ -348,12 +347,12 @@ class TestStreamsClosedByRstStream(object): lambda self, ff: ff.build_headers_frame( self.example_request_headers), lambda self, ff: ff.build_headers_frame( - self.example_request_headers, flags=['END_STREAM']), - ] + self.example_request_headers, flags=["END_STREAM"]), + ], ) def test_resets_further_frames_after_recv_reset(self, frame_factory, - frame): + frame) -> None: """ A stream that is closed by receive RST_STREAM can receive further frames: it simply sends RST_STREAM for it, and additionally @@ -364,18 +363,18 @@ def test_resets_further_frames_after_recv_reset(self, c.initiate_connection() header_frame = frame_factory.build_headers_frame( - self.example_request_headers, flags=['END_STREAM'] + self.example_request_headers, flags=["END_STREAM"], ) c.receive_data(header_frame.serialize()) c.send_headers( stream_id=1, headers=self.example_response_headers, - end_stream=False + end_stream=False, ) rst_frame = frame_factory.build_rst_stream_frame( - 1, h2.errors.ErrorCodes.STREAM_CLOSED + 1, h2.errors.ErrorCodes.STREAM_CLOSED, ) c.receive_data(rst_frame.serialize()) c.clear_outbound_data_buffer() @@ -384,7 +383,7 @@ def test_resets_further_frames_after_recv_reset(self, events = c.receive_data(f.serialize()) rst_frame = frame_factory.build_rst_stream_frame( - 1, h2.errors.ErrorCodes.STREAM_CLOSED + 1, h2.errors.ErrorCodes.STREAM_CLOSED, ) assert not events assert c.data_to_send() == rst_frame.serialize() @@ -402,7 +401,7 @@ def test_resets_further_frames_after_recv_reset(self, assert c.data_to_send() == rst_frame.serialize() * 3 def test_resets_further_data_frames_after_recv_reset(self, - frame_factory): + frame_factory) -> None: """ A stream that is closed by receive RST_STREAM can receive further DATA frames: it simply sends WINDOW_UPDATE for the connection flow @@ -413,24 +412,24 @@ def test_resets_further_data_frames_after_recv_reset(self, c.initiate_connection() header_frame = frame_factory.build_headers_frame( - self.example_request_headers, flags=['END_STREAM'] + self.example_request_headers, flags=["END_STREAM"], ) c.receive_data(header_frame.serialize()) c.send_headers( stream_id=1, headers=self.example_response_headers, - end_stream=False + end_stream=False, ) rst_frame = frame_factory.build_rst_stream_frame( - 1, h2.errors.ErrorCodes.STREAM_CLOSED + 1, h2.errors.ErrorCodes.STREAM_CLOSED, ) c.receive_data(rst_frame.serialize()) c.clear_outbound_data_buffer() f = frame_factory.build_data_frame( - data=b'some data' + data=b"some data", ) events = c.receive_data(f.serialize()) @@ -460,12 +459,12 @@ def test_resets_further_data_frames_after_recv_reset(self, lambda self, ff: ff.build_headers_frame( self.example_request_headers), lambda self, ff: ff.build_headers_frame( - self.example_request_headers, flags=['END_STREAM']), - ] + self.example_request_headers, flags=["END_STREAM"]), + ], ) def test_resets_further_frames_after_send_reset(self, frame_factory, - frame): + frame) -> None: """ A stream that is closed by sent RST_STREAM can receive further frames: it simply sends RST_STREAM for it. @@ -475,20 +474,20 @@ def test_resets_further_frames_after_send_reset(self, c.initiate_connection() header_frame = frame_factory.build_headers_frame( - self.example_request_headers, flags=['END_STREAM'] + self.example_request_headers, flags=["END_STREAM"], ) c.receive_data(header_frame.serialize()) c.send_headers( stream_id=1, headers=self.example_response_headers, - end_stream=False + end_stream=False, ) c.reset_stream(1, h2.errors.ErrorCodes.INTERNAL_ERROR) rst_frame = frame_factory.build_rst_stream_frame( - 1, h2.errors.ErrorCodes.STREAM_CLOSED + 1, h2.errors.ErrorCodes.STREAM_CLOSED, ) c.clear_outbound_data_buffer() @@ -496,7 +495,7 @@ def test_resets_further_frames_after_send_reset(self, events = c.receive_data(f.serialize()) rst_frame = frame_factory.build_rst_stream_frame( - 1, h2.errors.ErrorCodes.STREAM_CLOSED + 1, h2.errors.ErrorCodes.STREAM_CLOSED, ) assert not events assert c.data_to_send() == rst_frame.serialize() @@ -514,7 +513,7 @@ def test_resets_further_frames_after_send_reset(self, assert c.data_to_send() == rst_frame.serialize() * 3 def test_resets_further_data_frames_after_send_reset(self, - frame_factory): + frame_factory) -> None: """ A stream that is closed by sent RST_STREAM can receive further data frames: it simply sends WINDOW_UPDATE and RST_STREAM for it. @@ -524,14 +523,14 @@ def test_resets_further_data_frames_after_send_reset(self, c.initiate_connection() header_frame = frame_factory.build_headers_frame( - self.example_request_headers, flags=['END_STREAM'] + self.example_request_headers, flags=["END_STREAM"], ) c.receive_data(header_frame.serialize()) c.send_headers( stream_id=1, headers=self.example_response_headers, - end_stream=False + end_stream=False, ) c.reset_stream(1, h2.errors.ErrorCodes.INTERNAL_ERROR) @@ -539,7 +538,7 @@ def test_resets_further_data_frames_after_send_reset(self, c.clear_outbound_data_buffer() f = frame_factory.build_data_frame( - data=b'some data' + data=b"some data", ) events = c.receive_data(f.serialize()) assert not events diff --git a/tests/test_complex_logic.py b/tests/test_complex_logic.py index 57f82e10e..937748218 100644 --- a/tests/test_complex_logic.py +++ b/tests/test_complex_logic.py @@ -1,13 +1,12 @@ """ -test_complex_logic -~~~~~~~~~~~~~~~~ - More complex tests that try to do more. Certain tests don't really eliminate incorrect behaviour unless they do quite a bit. These tests should live here, to keep the pain in once place rather than hide it in the other parts of the test suite. """ +from __future__ import annotations + import pytest import h2 @@ -15,22 +14,23 @@ import h2.connection -class TestComplexClient(object): +class TestComplexClient: """ Complex tests for client-side stacks. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] - def test_correctly_count_server_streams(self, frame_factory): + def test_correctly_count_server_streams(self, frame_factory) -> None: """ We correctly count the number of server streams, both inbound and outbound. @@ -82,7 +82,7 @@ def test_correctly_count_server_streams(self, frame_factory): f = frame_factory.build_headers_frame( stream_id=stream_id, headers=self.example_response_headers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) c.receive_data(f.serialize()) expected_outbound_streams -= 1 @@ -92,8 +92,8 @@ def test_correctly_count_server_streams(self, frame_factory): # Pushed streams can only be closed remotely. f = frame_factory.build_data_frame( stream_id=stream_id+1, - data=b'the content', - flags=['END_STREAM'], + data=b"the content", + flags=["END_STREAM"], ) c.receive_data(f.serialize()) expected_inbound_streams -= 1 @@ -104,23 +104,24 @@ def test_correctly_count_server_streams(self, frame_factory): assert c.open_outbound_streams == 0 -class TestComplexServer(object): +class TestComplexServer: """ Complex tests for server-side stacks. """ + example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) - def test_correctly_count_server_streams(self, frame_factory): + def test_correctly_count_server_streams(self, frame_factory) -> None: """ We correctly count the number of server streams, both inbound and outbound. @@ -159,8 +160,8 @@ def test_correctly_count_server_streams(self, frame_factory): for stream_id in range(13, 0, -2): # Close an inbound stream. f = frame_factory.build_data_frame( - data=b'', - flags=['END_STREAM'], + data=b"", + flags=["END_STREAM"], stream_id=stream_id, ) c.receive_data(f.serialize()) @@ -169,7 +170,7 @@ def test_correctly_count_server_streams(self, frame_factory): assert c.open_inbound_streams == expected_inbound_streams assert c.open_outbound_streams == expected_outbound_streams - c.send_data(stream_id, b'', end_stream=True) + c.send_data(stream_id, b"", end_stream=True) expected_inbound_streams -= 1 assert c.open_inbound_streams == expected_inbound_streams assert c.open_outbound_streams == expected_outbound_streams @@ -177,7 +178,7 @@ def test_correctly_count_server_streams(self, frame_factory): # Pushed streams, however, we can close ourselves. c.send_data( stream_id=stream_id+1, - data=b'', + data=b"", end_stream=True, ) expected_outbound_streams -= 1 @@ -188,15 +189,16 @@ def test_correctly_count_server_streams(self, frame_factory): assert c.open_outbound_streams == 0 -class TestContinuationFrames(object): +class TestContinuationFrames: """ Tests for the relatively complex CONTINUATION frame logic. """ + example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] server_config = h2.config.H2Configuration(client_side=False) @@ -211,12 +213,12 @@ def _build_continuation_sequence(self, headers, block_size, frame_factory): frames = [ frame_factory.build_continuation_frame(c) for c in chunks ] - f.flags = {'END_STREAM'} - frames[-1].flags.add('END_HEADERS') + f.flags = {"END_STREAM"} + frames[-1].flags.add("END_HEADERS") frames.insert(0, f) return frames - def test_continuation_frame_basic(self, frame_factory): + def test_continuation_frame_basic(self, frame_factory) -> None: """ Test that we correctly decode a header block split across continuation frames. @@ -230,7 +232,7 @@ def test_continuation_frame_basic(self, frame_factory): block_size=5, frame_factory=frame_factory, ) - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) events = c.receive_data(data) assert len(events) == 2 @@ -243,10 +245,10 @@ def test_continuation_frame_basic(self, frame_factory): assert isinstance(second_event, h2.events.StreamEnded) assert second_event.stream_id == 1 - @pytest.mark.parametrize('stream_id', [3, 1]) + @pytest.mark.parametrize("stream_id", [3, 1]) def test_continuation_cannot_interleave_headers(self, frame_factory, - stream_id): + stream_id) -> None: """ We cannot interleave a new headers block with a CONTINUATION sequence. """ @@ -265,17 +267,17 @@ def test_continuation_cannot_interleave_headers(self, bogus_frame = frame_factory.build_headers_frame( headers=self.example_request_headers, stream_id=stream_id, - flags=['END_STREAM'], + flags=["END_STREAM"], ) frames.insert(len(frames) - 2, bogus_frame) - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) with pytest.raises(h2.exceptions.ProtocolError) as e: c.receive_data(data) assert "invalid frame" in str(e.value).lower() - def test_continuation_cannot_interleave_data(self, frame_factory): + def test_continuation_cannot_interleave_data(self, frame_factory) -> None: """ We cannot interleave a data frame with a CONTINUATION sequence. """ @@ -292,18 +294,18 @@ def test_continuation_cannot_interleave_data(self, frame_factory): assert len(frames) > 2 # This is mostly defensive. bogus_frame = frame_factory.build_data_frame( - data=b'hello', + data=b"hello", stream_id=1, ) frames.insert(len(frames) - 2, bogus_frame) - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) with pytest.raises(h2.exceptions.ProtocolError) as e: c.receive_data(data) assert "invalid frame" in str(e.value).lower() - def test_continuation_cannot_interleave_unknown_frame(self, frame_factory): + def test_continuation_cannot_interleave_unknown_frame(self, frame_factory) -> None: """ We cannot interleave an unknown frame with a CONTINUATION sequence. """ @@ -320,19 +322,19 @@ def test_continuation_cannot_interleave_unknown_frame(self, frame_factory): assert len(frames) > 2 # This is mostly defensive. bogus_frame = frame_factory.build_data_frame( - data=b'hello', + data=b"hello", stream_id=1, ) bogus_frame.type = 88 frames.insert(len(frames) - 2, bogus_frame) - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) with pytest.raises(h2.exceptions.ProtocolError) as e: c.receive_data(data) assert "invalid frame" in str(e.value).lower() - def test_continuation_frame_multiple_blocks(self, frame_factory): + def test_continuation_frame_multiple_blocks(self, frame_factory) -> None: """ Test that we correctly decode several header blocks split across continuation frames. @@ -350,7 +352,7 @@ def test_continuation_frame_multiple_blocks(self, frame_factory): for frame in frames: frame.stream_id = stream_id - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) events = c.receive_data(data) assert len(events) == 2 @@ -364,25 +366,26 @@ def test_continuation_frame_multiple_blocks(self, frame_factory): assert second_event.stream_id == stream_id -class TestContinuationFramesPushPromise(object): +class TestContinuationFramesPushPromise: """ Tests for the relatively complex CONTINUATION frame logic working with PUSH_PROMISE frames. """ + example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), ] def _build_continuation_sequence(self, headers, block_size, frame_factory): f = frame_factory.build_push_promise_frame( - stream_id=1, promised_stream_id=2, headers=headers + stream_id=1, promised_stream_id=2, headers=headers, ) header_data = f.data chunks = [ @@ -393,12 +396,12 @@ def _build_continuation_sequence(self, headers, block_size, frame_factory): frames = [ frame_factory.build_continuation_frame(c) for c in chunks ] - f.flags = {'END_STREAM'} - frames[-1].flags.add('END_HEADERS') + f.flags = {"END_STREAM"} + frames[-1].flags.add("END_HEADERS") frames.insert(0, f) return frames - def test_continuation_frame_basic_push_promise(self, frame_factory): + def test_continuation_frame_basic_push_promise(self, frame_factory) -> None: """ Test that we correctly decode a header block split across continuation frames when that header block is initiated with a PUSH_PROMISE. @@ -412,7 +415,7 @@ def test_continuation_frame_basic_push_promise(self, frame_factory): block_size=5, frame_factory=frame_factory, ) - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) events = c.receive_data(data) assert len(events) == 1 @@ -423,10 +426,10 @@ def test_continuation_frame_basic_push_promise(self, frame_factory): assert event.parent_stream_id == 1 assert event.pushed_stream_id == 2 - @pytest.mark.parametrize('stream_id', [3, 1, 2]) + @pytest.mark.parametrize("stream_id", [3, 1, 2]) def test_continuation_cannot_interleave_headers_pp(self, frame_factory, - stream_id): + stream_id) -> None: """ We cannot interleave a new headers block with a CONTINUATION sequence when the headers block is based on a PUSH_PROMISE frame. @@ -445,17 +448,17 @@ def test_continuation_cannot_interleave_headers_pp(self, bogus_frame = frame_factory.build_headers_frame( headers=self.example_response_headers, stream_id=stream_id, - flags=['END_STREAM'], + flags=["END_STREAM"], ) frames.insert(len(frames) - 2, bogus_frame) - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) with pytest.raises(h2.exceptions.ProtocolError) as e: c.receive_data(data) assert "invalid frame" in str(e.value).lower() - def test_continuation_cannot_interleave_data(self, frame_factory): + def test_continuation_cannot_interleave_data(self, frame_factory) -> None: """ We cannot interleave a data frame with a CONTINUATION sequence when that sequence began with a PUSH_PROMISE frame. @@ -472,18 +475,18 @@ def test_continuation_cannot_interleave_data(self, frame_factory): assert len(frames) > 2 # This is mostly defensive. bogus_frame = frame_factory.build_data_frame( - data=b'hello', + data=b"hello", stream_id=1, ) frames.insert(len(frames) - 2, bogus_frame) - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) with pytest.raises(h2.exceptions.ProtocolError) as e: c.receive_data(data) assert "invalid frame" in str(e.value).lower() - def test_continuation_cannot_interleave_unknown_frame(self, frame_factory): + def test_continuation_cannot_interleave_unknown_frame(self, frame_factory) -> None: """ We cannot interleave an unknown frame with a CONTINUATION sequence when that sequence began with a PUSH_PROMISE frame. @@ -500,22 +503,22 @@ def test_continuation_cannot_interleave_unknown_frame(self, frame_factory): assert len(frames) > 2 # This is mostly defensive. bogus_frame = frame_factory.build_data_frame( - data=b'hello', + data=b"hello", stream_id=1, ) bogus_frame.type = 88 frames.insert(len(frames) - 2, bogus_frame) - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) with pytest.raises(h2.exceptions.ProtocolError) as e: c.receive_data(data) assert "invalid frame" in str(e.value).lower() - @pytest.mark.parametrize('evict', [True, False]) + @pytest.mark.parametrize("evict", [True, False]) def test_stream_remotely_closed_disallows_push_promise(self, evict, - frame_factory): + frame_factory) -> None: """ Streams closed normally by the remote peer disallow PUSH_PROMISE frames, and cause a GOAWAY. @@ -525,13 +528,13 @@ def test_stream_remotely_closed_disallows_push_promise(self, c.send_headers( stream_id=1, headers=self.example_request_headers, - end_stream=True + end_stream=True, ) f = frame_factory.build_headers_frame( stream_id=1, headers=self.example_response_headers, - flags=['END_STREAM'] + flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() @@ -556,7 +559,7 @@ def test_stream_remotely_closed_disallows_push_promise(self, ) assert c.data_to_send() == f.serialize() - def test_continuation_frame_multiple_push_promise(self, frame_factory): + def test_continuation_frame_multiple_push_promise(self, frame_factory) -> None: """ Test that we correctly decode header blocks split across continuation frames when those header block is initiated with a PUSH_PROMISE, for @@ -573,7 +576,7 @@ def test_continuation_frame_multiple_push_promise(self, frame_factory): frame_factory=frame_factory, ) frames[0].promised_stream_id = promised_stream_id - data = b''.join(f.serialize() for f in frames) + data = b"".join(f.serialize() for f in frames) events = c.receive_data(data) assert len(events) == 1 diff --git a/tests/test_config.py b/tests/test_config.py index cd03a1b69..5651f7bab 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,21 +1,21 @@ """ -test_config -~~~~~~~~~~~ - Test the configuration object. """ +from __future__ import annotations + import logging + import pytest import h2.config -class TestH2Config(object): +class TestH2Config: """ Tests of the H2 config object. """ - def test_defaults(self): + def test_defaults(self) -> None: """ The default values of the HTTP/2 config object are sensible. """ @@ -25,18 +25,18 @@ def test_defaults(self): assert isinstance(config.logger, h2.config.DummyLogger) boolean_config_options = [ - 'client_side', - 'validate_outbound_headers', - 'normalize_outbound_headers', - 'validate_inbound_headers', - 'normalize_inbound_headers', + "client_side", + "validate_outbound_headers", + "normalize_outbound_headers", + "validate_inbound_headers", + "normalize_inbound_headers", ] - @pytest.mark.parametrize('option_name', boolean_config_options) - @pytest.mark.parametrize('value', [None, 'False', 1]) + @pytest.mark.parametrize("option_name", boolean_config_options) + @pytest.mark.parametrize("value", [None, "False", 1]) def test_boolean_config_options_reject_non_bools_init( - self, option_name, value - ): + self, option_name, value, + ) -> None: """ The boolean config options raise an error if you try to set a value that isn't a boolean via the initializer. @@ -44,11 +44,11 @@ def test_boolean_config_options_reject_non_bools_init( with pytest.raises(ValueError): h2.config.H2Configuration(**{option_name: value}) - @pytest.mark.parametrize('option_name', boolean_config_options) - @pytest.mark.parametrize('value', [None, 'False', 1]) + @pytest.mark.parametrize("option_name", boolean_config_options) + @pytest.mark.parametrize("value", [None, "False", 1]) def test_boolean_config_options_reject_non_bools_attr( - self, option_name, value - ): + self, option_name, value, + ) -> None: """ The boolean config options raise an error if you try to set a value that isn't a boolean via attribute setter. @@ -57,9 +57,9 @@ def test_boolean_config_options_reject_non_bools_attr( with pytest.raises(ValueError): setattr(config, option_name, value) - @pytest.mark.parametrize('option_name', boolean_config_options) - @pytest.mark.parametrize('value', [True, False]) - def test_boolean_config_option_is_reflected_init(self, option_name, value): + @pytest.mark.parametrize("option_name", boolean_config_options) + @pytest.mark.parametrize("value", [True, False]) + def test_boolean_config_option_is_reflected_init(self, option_name, value) -> None: """ The value of the boolean config options, when set, is reflected in the value via the initializer. @@ -67,9 +67,9 @@ def test_boolean_config_option_is_reflected_init(self, option_name, value): config = h2.config.H2Configuration(**{option_name: value}) assert getattr(config, option_name) == value - @pytest.mark.parametrize('option_name', boolean_config_options) - @pytest.mark.parametrize('value', [True, False]) - def test_boolean_config_option_is_reflected_attr(self, option_name, value): + @pytest.mark.parametrize("option_name", boolean_config_options) + @pytest.mark.parametrize("value", [True, False]) + def test_boolean_config_option_is_reflected_attr(self, option_name, value) -> None: """ The value of the boolean config options, when set, is reflected in the value via attribute setter. @@ -78,10 +78,10 @@ def test_boolean_config_option_is_reflected_attr(self, option_name, value): setattr(config, option_name, value) assert getattr(config, option_name) == value - @pytest.mark.parametrize('header_encoding', [True, 1, object()]) + @pytest.mark.parametrize("header_encoding", [True, 1, object()]) def test_header_encoding_must_be_false_str_none_init( - self, header_encoding - ): + self, header_encoding, + ) -> None: """ The value of the ``header_encoding`` setting must be False, a string, or None via the initializer. @@ -89,10 +89,10 @@ def test_header_encoding_must_be_false_str_none_init( with pytest.raises(ValueError): h2.config.H2Configuration(header_encoding=header_encoding) - @pytest.mark.parametrize('header_encoding', [True, 1, object()]) + @pytest.mark.parametrize("header_encoding", [True, 1, object()]) def test_header_encoding_must_be_false_str_none_attr( - self, header_encoding - ): + self, header_encoding, + ) -> None: """ The value of the ``header_encoding`` setting must be False, a string, or None via attribute setter. @@ -101,8 +101,8 @@ def test_header_encoding_must_be_false_str_none_attr( with pytest.raises(ValueError): config.header_encoding = header_encoding - @pytest.mark.parametrize('header_encoding', [False, 'ascii', None]) - def test_header_encoding_is_reflected_init(self, header_encoding): + @pytest.mark.parametrize("header_encoding", [False, "ascii", None]) + def test_header_encoding_is_reflected_init(self, header_encoding) -> None: """ The value of ``header_encoding``, when set, is reflected in the value via the initializer. @@ -110,8 +110,8 @@ def test_header_encoding_is_reflected_init(self, header_encoding): config = h2.config.H2Configuration(header_encoding=header_encoding) assert config.header_encoding == header_encoding - @pytest.mark.parametrize('header_encoding', [False, 'ascii', None]) - def test_header_encoding_is_reflected_attr(self, header_encoding): + @pytest.mark.parametrize("header_encoding", [False, "ascii", None]) + def test_header_encoding_is_reflected_attr(self, header_encoding) -> None: """ The value of ``header_encoding``, when set, is reflected in the value via the attribute setter. @@ -120,17 +120,17 @@ def test_header_encoding_is_reflected_attr(self, header_encoding): config.header_encoding = header_encoding assert config.header_encoding == header_encoding - def test_logger_instance_is_reflected(self): + def test_logger_instance_is_reflected(self) -> None: """ The value of ``logger``, when set, is reflected in the value. """ - logger = logging.Logger('hyper-h2.test') + logger = logging.getLogger("hyper-h2.test") config = h2.config.H2Configuration() config.logger = logger assert config.logger is logger @pytest.mark.parametrize("trace_level", [False, True]) - def test_output_logger(self, capsys, trace_level): + def test_output_logger(self, capsys, trace_level) -> None: logger = h2.config.OutputLogger(trace_level=trace_level) logger.debug("This is a debug message %d.", 123) diff --git a/tests/test_events.py b/tests/test_events.py index c910c01b1..aac913586 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -1,24 +1,20 @@ """ -test_events.py -~~~~~~~~~~~~~~ - Specific tests for any function that is logically self-contained as part of events.py. """ +from __future__ import annotations + import inspect import sys -from hypothesis import given -from hypothesis.strategies import ( - integers, lists, tuples -) import pytest +from hypothesis import given +from hypothesis.strategies import integers, lists, tuples import h2.errors import h2.events import h2.settings - # We define a fairly complex Hypothesis strategy here. We want to build a list # of two tuples of (Setting, value). For Setting we want to make sure we can # handle settings that the rest of hyper knows nothing about, so we want to @@ -28,16 +24,17 @@ tuples( integers(min_value=0, max_value=2**16-1), integers(min_value=0, max_value=2**32-1), - ) + ), ) -class TestRemoteSettingsChanged(object): +class TestRemoteSettingsChanged: """ Validate the function of the RemoteSettingsChanged event. """ + @given(SETTINGS_STRATEGY) - def test_building_settings_from_scratch(self, settings_list): + def test_building_settings_from_scratch(self, settings_list) -> None: """ Missing old settings are defaulted to None. """ @@ -55,7 +52,7 @@ def test_building_settings_from_scratch(self, settings_list): @given(SETTINGS_STRATEGY, SETTINGS_STRATEGY) def test_only_reports_changed_settings(self, old_settings_list, - new_settings_list): + new_settings_list) -> None: """ Settings that were not changed are not reported. """ @@ -68,14 +65,14 @@ def test_only_reports_changed_settings(self, assert len(e.changed_settings) == len(new_settings_dict) assert ( - sorted(list(e.changed_settings.keys())) == - sorted(list(new_settings_dict.keys())) + sorted(e.changed_settings.keys()) == + sorted(new_settings_dict.keys()) ) @given(SETTINGS_STRATEGY, SETTINGS_STRATEGY) def test_correctly_reports_changed_settings(self, old_settings_list, - new_settings_list): + new_settings_list) -> None: """ Settings that are changed are correctly reported. """ @@ -93,26 +90,27 @@ def test_correctly_reports_changed_settings(self, assert e.changed_settings[setting].new_value == new_value -class TestEventReprs(object): +class TestEventReprs: """ Events have useful representations. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_informational_headers = [ - (':status', '100'), - ('server', 'fake-serv/0.1.0') + (":status", "100"), + ("server", "fake-serv/0.1.0"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] - def test_requestreceived_repr(self): + def test_requestreceived_repr(self) -> None: """ RequestReceived has a useful debug representation. """ @@ -128,7 +126,7 @@ def test_requestreceived_repr(self): "(':method', 'GET')]>" ) - def test_responsereceived_repr(self): + def test_responsereceived_repr(self) -> None: """ ResponseReceived has a useful debug representation. """ @@ -142,7 +140,7 @@ def test_responsereceived_repr(self): "('server', 'fake-serv/0.1.0')]>" ) - def test_trailersreceived_repr(self): + def test_trailersreceived_repr(self) -> None: """ TrailersReceived has a useful debug representation. """ @@ -156,7 +154,7 @@ def test_trailersreceived_repr(self): "('server', 'fake-serv/0.1.0')]>" ) - def test_informationalresponsereceived_repr(self): + def test_informationalresponsereceived_repr(self) -> None: """ InformationalResponseReceived has a useful debug representation. """ @@ -170,7 +168,7 @@ def test_informationalresponsereceived_repr(self): "('server', 'fake-serv/0.1.0')]>" ) - def test_datareceived_repr(self): + def test_datareceived_repr(self) -> None: """ DataReceived has a useful debug representation. """ @@ -184,7 +182,7 @@ def test_datareceived_repr(self): "data:6162636465666768696a6b6c6d6e6f7071727374>" ) - def test_windowupdated_repr(self): + def test_windowupdated_repr(self) -> None: """ WindowUpdated has a useful debug representation. """ @@ -194,7 +192,7 @@ def test_windowupdated_repr(self): assert repr(e) == "" - def test_remotesettingschanged_repr(self): + def test_remotesettingschanged_repr(self) -> None: """ RemoteSettingsChanged has a useful debug representation. """ @@ -202,7 +200,7 @@ def test_remotesettingschanged_repr(self): e.changed_settings = { h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: h2.settings.ChangedSetting( - h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**16, 2**15 + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**16, 2**15, ), } @@ -219,25 +217,25 @@ def test_remotesettingschanged_repr(self): "new_value=32768)}>" ) - def test_pingreceived_repr(self): + def test_pingreceived_repr(self) -> None: """ PingReceived has a useful debug representation. """ e = h2.events.PingReceived() - e.ping_data = b'abcdefgh' + e.ping_data = b"abcdefgh" assert repr(e) == "" - def test_pingackreceived_repr(self): + def test_pingackreceived_repr(self) -> None: """ PingAckReceived has a useful debug representation. """ e = h2.events.PingAckReceived() - e.ping_data = b'abcdefgh' + e.ping_data = b"abcdefgh" assert repr(e) == "" - def test_streamended_repr(self): + def test_streamended_repr(self) -> None: """ StreamEnded has a useful debug representation. """ @@ -246,7 +244,7 @@ def test_streamended_repr(self): assert repr(e) == "" - def test_streamreset_repr(self): + def test_streamreset_repr(self) -> None: """ StreamEnded has a useful debug representation. """ @@ -266,7 +264,7 @@ def test_streamreset_repr(self): "error_code:ErrorCodes.ENHANCE_YOUR_CALM, remote_reset:False>" ) - def test_pushedstreamreceived_repr(self): + def test_pushedstreamreceived_repr(self) -> None: """ PushedStreamReceived has a useful debug representation. """ @@ -284,7 +282,7 @@ def test_pushedstreamreceived_repr(self): "(':method', 'GET')]>" ) - def test_settingsacknowledged_repr(self): + def test_settingsacknowledged_repr(self) -> None: """ SettingsAcknowledged has a useful debug representation. """ @@ -292,7 +290,7 @@ def test_settingsacknowledged_repr(self): e.changed_settings = { h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: h2.settings.ChangedSetting( - h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**16, 2**15 + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**16, 2**15, ), } @@ -309,7 +307,7 @@ def test_settingsacknowledged_repr(self): "new_value=32768)}>" ) - def test_priorityupdated_repr(self): + def test_priorityupdated_repr(self) -> None: """ PriorityUpdated has a useful debug representation. """ @@ -324,11 +322,11 @@ def test_priorityupdated_repr(self): "exclusive:True>" ) - @pytest.mark.parametrize("additional_data,data_repr", [ + @pytest.mark.parametrize(("additional_data", "data_repr"), [ (None, "None"), - (b'some data', "736f6d652064617461") + (b"some data", "736f6d652064617461"), ]) - def test_connectionterminated_repr(self, additional_data, data_repr): + def test_connectionterminated_repr(self, additional_data, data_repr) -> None: """ ConnectionTerminated has a useful debug representation. """ @@ -340,15 +338,15 @@ def test_connectionterminated_repr(self, additional_data, data_repr): if sys.version_info >= (3, 11): assert repr(e) == ( "" % data_repr + f"last_stream_id:33, additional_data:{data_repr}>" ) else: assert repr(e) == ( "" % data_repr + f"last_stream_id:33, additional_data:{data_repr}>" ) - def test_alternativeserviceavailable_repr(self): + def test_alternativeserviceavailable_repr(self) -> None: """ AlternativeServiceAvailable has a useful debug representation. """ @@ -361,31 +359,31 @@ def test_alternativeserviceavailable_repr(self): 'field_value:h2=":8000"; ma=60>' ) - def test_unknownframereceived_repr(self): + def test_unknownframereceived_repr(self) -> None: """ UnknownFrameReceived has a useful debug representation. """ e = h2.events.UnknownFrameReceived() - assert repr(e) == '' + assert repr(e) == "" def all_events(): """ Generates all the classes (i.e., events) defined in h2.events. """ - for _, obj in inspect.getmembers(sys.modules['h2.events']): + for _, obj in inspect.getmembers(sys.modules["h2.events"]): # We are only interested in objects that are defined in h2.events; # objects that are imported from other modules are not of interest. - if hasattr(obj, '__module__') and (obj.__module__ != 'h2.events'): + if hasattr(obj, "__module__") and (obj.__module__ != "h2.events"): continue if inspect.isclass(obj): yield obj -@pytest.mark.parametrize('event', all_events()) -def test_all_events_subclass_from_event(event): +@pytest.mark.parametrize("event", all_events()) +def test_all_events_subclass_from_event(event) -> None: """ Every event defined in h2.events subclasses from h2.events.Event. """ diff --git a/tests/test_exceptions.py b/tests/test_exceptions.py index 996eee3ea..fa4e379b1 100644 --- a/tests/test_exceptions.py +++ b/tests/test_exceptions.py @@ -1,14 +1,13 @@ """ -test_exceptions -~~~~~~~~~~~~~~~ - Tests that verify logic local to exceptions. """ +from __future__ import annotations + import h2.exceptions -class TestExceptions(object): - def test_stream_id_too_low_prints_properly(self): +class TestExceptions: + def test_stream_id_too_low_prints_properly(self) -> None: x = h2.exceptions.StreamIDTooLowError(5, 10) - assert "StreamIDTooLowError: 5 is lower than 10" == str(x) + assert str(x) == "StreamIDTooLowError: 5 is lower than 10" diff --git a/tests/test_flow_control_window.py b/tests/test_flow_control_window.py index a685a1292..21cc7b8f0 100644 --- a/tests/test_flow_control_window.py +++ b/tests/test_flow_control_window.py @@ -4,9 +4,10 @@ Tests of the flow control management in h2 """ -import pytest +from __future__ import annotations -from hypothesis import given, settings, HealthCheck +import pytest +from hypothesis import HealthCheck, given, settings from hypothesis.strategies import integers import h2.config @@ -17,21 +18,22 @@ import h2.settings -class TestFlowControl(object): +class TestFlowControl: """ Tests of the flow control management in the connection objects. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] server_config = h2.config.H2Configuration(client_side=False) DEFAULT_FLOW_WINDOW = 65535 - def test_flow_control_initializes_properly(self): + def test_flow_control_initializes_properly(self) -> None: """ The flow control window for a stream should initially be the default flow control value. @@ -42,20 +44,20 @@ def test_flow_control_initializes_properly(self): assert c.local_flow_control_window(1) == self.DEFAULT_FLOW_WINDOW assert c.remote_flow_control_window(1) == self.DEFAULT_FLOW_WINDOW - def test_flow_control_decreases_with_sent_data(self): + def test_flow_control_decreases_with_sent_data(self) -> None: """ When data is sent on a stream, the flow control window should drop. """ c = h2.connection.H2Connection() c.send_headers(1, self.example_request_headers) - c.send_data(1, b'some data') + c.send_data(1, b"some data") - remaining_length = self.DEFAULT_FLOW_WINDOW - len(b'some data') + remaining_length = self.DEFAULT_FLOW_WINDOW - len(b"some data") assert (c.local_flow_control_window(1) == remaining_length) @pytest.mark.parametrize("pad_length", [5, 0]) def test_flow_control_decreases_with_sent_data_with_padding(self, - pad_length): + pad_length) -> None: """ When padded data is sent on a stream, the flow control window drops by the length of the padding plus 1 for the 1-byte padding length @@ -64,13 +66,13 @@ def test_flow_control_decreases_with_sent_data_with_padding(self, c = h2.connection.H2Connection() c.send_headers(1, self.example_request_headers) - c.send_data(1, b'some data', pad_length=pad_length) + c.send_data(1, b"some data", pad_length=pad_length) remaining_length = ( - self.DEFAULT_FLOW_WINDOW - len(b'some data') - pad_length - 1 + self.DEFAULT_FLOW_WINDOW - len(b"some data") - pad_length - 1 ) assert c.local_flow_control_window(1) == remaining_length - def test_flow_control_decreases_with_received_data(self, frame_factory): + def test_flow_control_decreases_with_received_data(self, frame_factory) -> None: """ When data is received on a stream, the remote flow control window should drop. @@ -78,14 +80,14 @@ def test_flow_control_decreases_with_received_data(self, frame_factory): c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) f1 = frame_factory.build_headers_frame(self.example_request_headers) - f2 = frame_factory.build_data_frame(b'some data') + f2 = frame_factory.build_data_frame(b"some data") c.receive_data(f1.serialize() + f2.serialize()) - remaining_length = self.DEFAULT_FLOW_WINDOW - len(b'some data') + remaining_length = self.DEFAULT_FLOW_WINDOW - len(b"some data") assert (c.remote_flow_control_window(1) == remaining_length) - def test_flow_control_decreases_with_padded_data(self, frame_factory): + def test_flow_control_decreases_with_padded_data(self, frame_factory) -> None: """ When padded data is received on a stream, the remote flow control window drops by an amount that includes the padding. @@ -93,29 +95,29 @@ def test_flow_control_decreases_with_padded_data(self, frame_factory): c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) f1 = frame_factory.build_headers_frame(self.example_request_headers) - f2 = frame_factory.build_data_frame(b'some data', padding_len=10) + f2 = frame_factory.build_data_frame(b"some data", padding_len=10) c.receive_data(f1.serialize() + f2.serialize()) remaining_length = ( - self.DEFAULT_FLOW_WINDOW - len(b'some data') - 10 - 1 + self.DEFAULT_FLOW_WINDOW - len(b"some data") - 10 - 1 ) assert (c.remote_flow_control_window(1) == remaining_length) - def test_flow_control_is_limited_by_connection(self): + def test_flow_control_is_limited_by_connection(self) -> None: """ The flow control window is limited by the flow control of the connection. """ c = h2.connection.H2Connection() c.send_headers(1, self.example_request_headers) - c.send_data(1, b'some data') + c.send_data(1, b"some data") c.send_headers(3, self.example_request_headers) - remaining_length = self.DEFAULT_FLOW_WINDOW - len(b'some data') + remaining_length = self.DEFAULT_FLOW_WINDOW - len(b"some data") assert (c.local_flow_control_window(3) == remaining_length) - def test_remote_flow_control_is_limited_by_connection(self, frame_factory): + def test_remote_flow_control_is_limited_by_connection(self, frame_factory) -> None: """ The remote flow control window is limited by the flow control of the connection. @@ -123,17 +125,17 @@ def test_remote_flow_control_is_limited_by_connection(self, frame_factory): c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) f1 = frame_factory.build_headers_frame(self.example_request_headers) - f2 = frame_factory.build_data_frame(b'some data') + f2 = frame_factory.build_data_frame(b"some data") f3 = frame_factory.build_headers_frame( self.example_request_headers, stream_id=3, ) c.receive_data(f1.serialize() + f2.serialize() + f3.serialize()) - remaining_length = self.DEFAULT_FLOW_WINDOW - len(b'some data') + remaining_length = self.DEFAULT_FLOW_WINDOW - len(b"some data") assert (c.remote_flow_control_window(3) == remaining_length) - def test_cannot_send_more_data_than_window(self): + def test_cannot_send_more_data_than_window(self) -> None: """ Sending more data than the remaining flow control window raises a FlowControlError. @@ -143,9 +145,9 @@ def test_cannot_send_more_data_than_window(self): c.outbound_flow_control_window = 5 with pytest.raises(h2.exceptions.FlowControlError): - c.send_data(1, b'some data') + c.send_data(1, b"some data") - def test_increasing_connection_window_allows_sending(self, frame_factory): + def test_increasing_connection_window_allows_sending(self, frame_factory) -> None: """ Confirm that sending a WindowUpdate frame on the connection frees up space for further frames. @@ -155,7 +157,7 @@ def test_increasing_connection_window_allows_sending(self, frame_factory): c.outbound_flow_control_window = 5 with pytest.raises(h2.exceptions.FlowControlError): - c.send_data(1, b'some data') + c.send_data(1, b"some data") f = frame_factory.build_window_update_frame( stream_id=0, @@ -164,10 +166,10 @@ def test_increasing_connection_window_allows_sending(self, frame_factory): c.receive_data(f.serialize()) c.clear_outbound_data_buffer() - c.send_data(1, b'some data') + c.send_data(1, b"some data") assert c.data_to_send() - def test_increasing_stream_window_allows_sending(self, frame_factory): + def test_increasing_stream_window_allows_sending(self, frame_factory) -> None: """ Confirm that sending a WindowUpdate frame on the connection frees up space for further frames. @@ -177,7 +179,7 @@ def test_increasing_stream_window_allows_sending(self, frame_factory): c._get_stream_by_id(1).outbound_flow_control_window = 5 with pytest.raises(h2.exceptions.FlowControlError): - c.send_data(1, b'some data') + c.send_data(1, b"some data") f = frame_factory.build_window_update_frame( stream_id=1, @@ -186,10 +188,10 @@ def test_increasing_stream_window_allows_sending(self, frame_factory): c.receive_data(f.serialize()) c.clear_outbound_data_buffer() - c.send_data(1, b'some data') + c.send_data(1, b"some data") assert c.data_to_send() - def test_flow_control_shrinks_in_response_to_settings(self, frame_factory): + def test_flow_control_shrinks_in_response_to_settings(self, frame_factory) -> None: """ Acknowledging SETTINGS_INITIAL_WINDOW_SIZE shrinks the flow control window. @@ -200,13 +202,13 @@ def test_flow_control_shrinks_in_response_to_settings(self, frame_factory): assert c.local_flow_control_window(1) == 65535 f = frame_factory.build_settings_frame( - settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 1280} + settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 1280}, ) c.receive_data(f.serialize()) assert c.local_flow_control_window(1) == 1280 - def test_flow_control_grows_in_response_to_settings(self, frame_factory): + def test_flow_control_grows_in_response_to_settings(self, frame_factory) -> None: """ Acknowledging SETTINGS_INITIAL_WINDOW_SIZE grows the flow control window. @@ -216,7 +218,7 @@ def test_flow_control_grows_in_response_to_settings(self, frame_factory): # Greatly increase the connection flow control window. f = frame_factory.build_window_update_frame( - stream_id=0, increment=128000 + stream_id=0, increment=128000, ) c.receive_data(f.serialize()) @@ -224,14 +226,14 @@ def test_flow_control_grows_in_response_to_settings(self, frame_factory): assert c.local_flow_control_window(1) == 65535 f = frame_factory.build_settings_frame( - settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000}, ) c.receive_data(f.serialize()) # The stream window is still the bottleneck, but larger now. assert c.local_flow_control_window(1) == 128000 - def test_flow_control_settings_blocked_by_conn_window(self, frame_factory): + def test_flow_control_settings_blocked_by_conn_window(self, frame_factory) -> None: """ Changing SETTINGS_INITIAL_WINDOW_SIZE does not affect the effective flow control window if the connection window isn't changed. @@ -242,13 +244,13 @@ def test_flow_control_settings_blocked_by_conn_window(self, frame_factory): assert c.local_flow_control_window(1) == 65535 f = frame_factory.build_settings_frame( - settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000}, ) c.receive_data(f.serialize()) assert c.local_flow_control_window(1) == 65535 - def test_new_streams_have_flow_control_per_settings(self, frame_factory): + def test_new_streams_have_flow_control_per_settings(self, frame_factory) -> None: """ After a SETTINGS_INITIAL_WINDOW_SIZE change is received, new streams have appropriate new flow control windows. @@ -256,20 +258,20 @@ def test_new_streams_have_flow_control_per_settings(self, frame_factory): c = h2.connection.H2Connection() f = frame_factory.build_settings_frame( - settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + settings={h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000}, ) c.receive_data(f.serialize()) # Greatly increase the connection flow control window. f = frame_factory.build_window_update_frame( - stream_id=0, increment=128000 + stream_id=0, increment=128000, ) c.receive_data(f.serialize()) c.send_headers(1, self.example_request_headers) assert c.local_flow_control_window(1) == 128000 - def test_window_update_no_stream(self, frame_factory): + def test_window_update_no_stream(self, frame_factory) -> None: """ WindowUpdate frames received without streams fire an appropriate WindowUpdated event. @@ -279,7 +281,7 @@ def test_window_update_no_stream(self, frame_factory): f = frame_factory.build_window_update_frame( stream_id=0, - increment=5 + increment=5, ) events = c.receive_data(f.serialize()) @@ -290,7 +292,7 @@ def test_window_update_no_stream(self, frame_factory): assert event.stream_id == 0 assert event.delta == 5 - def test_window_update_with_stream(self, frame_factory): + def test_window_update_with_stream(self, frame_factory) -> None: """ WindowUpdate frames received with streams fire an appropriate WindowUpdated event. @@ -301,9 +303,9 @@ def test_window_update_with_stream(self, frame_factory): f1 = frame_factory.build_headers_frame(self.example_request_headers) f2 = frame_factory.build_window_update_frame( stream_id=1, - increment=66 + increment=66, ) - data = b''.join(map(lambda f: f.serialize(), [f1, f2])) + data = b"".join(f.serialize() for f in [f1, f2]) events = c.receive_data(data) assert len(events) == 2 @@ -313,7 +315,7 @@ def test_window_update_with_stream(self, frame_factory): assert event.stream_id == 1 assert event.delta == 66 - def test_we_can_increment_stream_flow_control(self, frame_factory): + def test_we_can_increment_stream_flow_control(self, frame_factory) -> None: """ It is possible for the user to increase the flow control window for streams. @@ -325,14 +327,14 @@ def test_we_can_increment_stream_flow_control(self, frame_factory): expected_frame = frame_factory.build_window_update_frame( stream_id=1, - increment=5 + increment=5, ) events = c.increment_flow_control_window(increment=5, stream_id=1) assert not events assert c.data_to_send() == expected_frame.serialize() - def test_we_can_increment_connection_flow_control(self, frame_factory): + def test_we_can_increment_connection_flow_control(self, frame_factory) -> None: """ It is possible for the user to increase the flow control window for the entire connection. @@ -344,14 +346,14 @@ def test_we_can_increment_connection_flow_control(self, frame_factory): expected_frame = frame_factory.build_window_update_frame( stream_id=0, - increment=5 + increment=5, ) events = c.increment_flow_control_window(increment=5) assert not events assert c.data_to_send() == expected_frame.serialize() - def test_we_enforce_our_flow_control_window(self, frame_factory): + def test_we_enforce_our_flow_control_window(self, frame_factory) -> None: """ The user can set a low flow control window, which leads to connection teardown if violated. @@ -361,7 +363,7 @@ def test_we_enforce_our_flow_control_window(self, frame_factory): # Change the flow control window to 80 bytes. c.update_settings( - {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 80} + {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 80}, ) f = frame_factory.build_settings_frame({}, ack=True) c.receive_data(f.serialize()) @@ -372,7 +374,7 @@ def test_we_enforce_our_flow_control_window(self, frame_factory): # Attempt to violate the flow control window. c.clear_outbound_data_buffer() - f = frame_factory.build_data_frame(b'\x01' * 100) + f = frame_factory.build_data_frame(b"\x01" * 100) with pytest.raises(h2.exceptions.FlowControlError): c.receive_data(f.serialize()) @@ -384,7 +386,7 @@ def test_we_enforce_our_flow_control_window(self, frame_factory): ) assert c.data_to_send() == expected_frame.serialize() - def test_shrink_remote_flow_control_settings(self, frame_factory): + def test_shrink_remote_flow_control_settings(self, frame_factory) -> None: """ The remote peer acknowledging our SETTINGS_INITIAL_WINDOW_SIZE shrinks the flow control window. @@ -401,7 +403,7 @@ def test_shrink_remote_flow_control_settings(self, frame_factory): assert c.remote_flow_control_window(1) == 1280 - def test_grow_remote_flow_control_settings(self, frame_factory): + def test_grow_remote_flow_control_settings(self, frame_factory) -> None: """ The remote peer acknowledging our SETTINGS_INITIAL_WINDOW_SIZE grows the flow control window. @@ -415,14 +417,14 @@ def test_grow_remote_flow_control_settings(self, frame_factory): assert c.remote_flow_control_window(1) == 65535 c.update_settings( - {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000}, ) f = frame_factory.build_settings_frame({}, ack=True) c.receive_data(f.serialize()) assert c.remote_flow_control_window(1) == 128000 - def test_new_streams_have_remote_flow_control(self, frame_factory): + def test_new_streams_have_remote_flow_control(self, frame_factory) -> None: """ After a SETTINGS_INITIAL_WINDOW_SIZE change is acknowledged by the remote peer, new streams have appropriate new flow control windows. @@ -430,7 +432,7 @@ def test_new_streams_have_remote_flow_control(self, frame_factory): c = h2.connection.H2Connection() c.update_settings( - {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000} + {h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 128000}, ) f = frame_factory.build_settings_frame({}, ack=True) c.receive_data(f.serialize()) @@ -442,9 +444,9 @@ def test_new_streams_have_remote_flow_control(self, frame_factory): assert c.remote_flow_control_window(1) == 128000 @pytest.mark.parametrize( - 'increment', [0, -15, 2**31] + "increment", [0, -15, 2**31], ) - def test_reject_bad_attempts_to_increment_flow_control(self, increment): + def test_reject_bad_attempts_to_increment_flow_control(self, increment) -> None: """ Attempting to increment a flow control increment outside the valid range causes a ValueError to be raised. @@ -461,8 +463,8 @@ def test_reject_bad_attempts_to_increment_flow_control(self, increment): with pytest.raises(ValueError): c.increment_flow_control_window(increment=increment) - @pytest.mark.parametrize('stream_id', [0, 1]) - def test_reject_bad_remote_increments(self, frame_factory, stream_id): + @pytest.mark.parametrize("stream_id", [0, 1]) + def test_reject_bad_remote_increments(self, frame_factory, stream_id) -> None: """ Remote peers attempting to increment flow control outside the valid range cause connection errors of type PROTOCOL_ERROR. @@ -475,7 +477,7 @@ def test_reject_bad_remote_increments(self, frame_factory, stream_id): c.clear_outbound_data_buffer() f = frame_factory.build_window_update_frame( - stream_id=stream_id, increment=0 + stream_id=stream_id, increment=0, ) with pytest.raises(h2.exceptions.ProtocolError): @@ -487,7 +489,7 @@ def test_reject_bad_remote_increments(self, frame_factory, stream_id): ) assert c.data_to_send() == expected_frame.serialize() - def test_reject_increasing_connection_window_too_far(self, frame_factory): + def test_reject_increasing_connection_window_too_far(self, frame_factory) -> None: """ Attempts by the remote peer to increase the connection flow control window beyond 2**31 - 1 are rejected. @@ -499,7 +501,7 @@ def test_reject_increasing_connection_window_too_far(self, frame_factory): increment = 2**31 - c.outbound_flow_control_window f = frame_factory.build_window_update_frame( - stream_id=0, increment=increment + stream_id=0, increment=increment, ) with pytest.raises(h2.exceptions.FlowControlError): @@ -511,7 +513,7 @@ def test_reject_increasing_connection_window_too_far(self, frame_factory): ) assert c.data_to_send() == expected_frame.serialize() - def test_reject_increasing_stream_window_too_far(self, frame_factory): + def test_reject_increasing_stream_window_too_far(self, frame_factory) -> None: """ Attempts by the remote peer to increase the stream flow control window beyond 2**31 - 1 are rejected. @@ -524,7 +526,7 @@ def test_reject_increasing_stream_window_too_far(self, frame_factory): increment = 2**31 - c.outbound_flow_control_window f = frame_factory.build_window_update_frame( - stream_id=1, increment=increment + stream_id=1, increment=increment, ) events = c.receive_data(f.serialize()) @@ -542,7 +544,7 @@ def test_reject_increasing_stream_window_too_far(self, frame_factory): ) assert c.data_to_send() == expected_frame.serialize() - def test_reject_overlarge_conn_window_settings(self, frame_factory): + def test_reject_overlarge_conn_window_settings(self, frame_factory) -> None: """ SETTINGS frames cannot change the size of the connection flow control window. @@ -554,7 +556,7 @@ def test_reject_overlarge_conn_window_settings(self, frame_factory): increment = 2**31 - 1 - c.outbound_flow_control_window f = frame_factory.build_window_update_frame( - stream_id=0, increment=increment + stream_id=0, increment=increment, ) c.receive_data(f.serialize()) @@ -562,8 +564,8 @@ def test_reject_overlarge_conn_window_settings(self, frame_factory): f = frame_factory.build_settings_frame( settings={ h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: - self.DEFAULT_FLOW_WINDOW + 1 - } + self.DEFAULT_FLOW_WINDOW + 1, + }, ) c.clear_outbound_data_buffer() @@ -574,11 +576,11 @@ def test_reject_overlarge_conn_window_settings(self, frame_factory): expected_frame = frame_factory.build_settings_frame( settings={}, - ack=True + ack=True, ) assert c.data_to_send() == expected_frame.serialize() - def test_reject_overlarge_stream_window_settings(self, frame_factory): + def test_reject_overlarge_stream_window_settings(self, frame_factory) -> None: """ Remote attempts to create overlarge stream windows via SETTINGS frames are rejected. @@ -591,7 +593,7 @@ def test_reject_overlarge_stream_window_settings(self, frame_factory): increment = 2**31 - 1 - c.outbound_flow_control_window f = frame_factory.build_window_update_frame( - stream_id=1, increment=increment + stream_id=1, increment=increment, ) c.receive_data(f.serialize()) @@ -599,8 +601,8 @@ def test_reject_overlarge_stream_window_settings(self, frame_factory): f = frame_factory.build_settings_frame( settings={ h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: - self.DEFAULT_FLOW_WINDOW + 1 - } + self.DEFAULT_FLOW_WINDOW + 1, + }, ) c.clear_outbound_data_buffer() with pytest.raises(h2.exceptions.FlowControlError): @@ -612,7 +614,7 @@ def test_reject_overlarge_stream_window_settings(self, frame_factory): ) assert c.data_to_send() == expected_frame.serialize() - def test_reject_local_overlarge_increase_connection_window(self): + def test_reject_local_overlarge_increase_connection_window(self) -> None: """ Local attempts to increase the connection window too far are rejected. """ @@ -624,7 +626,7 @@ def test_reject_local_overlarge_increase_connection_window(self): with pytest.raises(h2.exceptions.FlowControlError): c.increment_flow_control_window(increment=increment) - def test_reject_local_overlarge_increase_stream_window(self): + def test_reject_local_overlarge_increase_stream_window(self) -> None: """ Local attempts to increase the connection window too far are rejected. """ @@ -637,7 +639,7 @@ def test_reject_local_overlarge_increase_stream_window(self): with pytest.raises(h2.exceptions.FlowControlError): c.increment_flow_control_window(increment=increment, stream_id=1) - def test_send_update_on_closed_streams(self, frame_factory): + def test_send_update_on_closed_streams(self, frame_factory) -> None: c = h2.connection.H2Connection() c.initiate_connection() c.send_headers(1, self.example_request_headers) @@ -647,7 +649,7 @@ def test_send_update_on_closed_streams(self, frame_factory): c.open_outbound_streams c.open_inbound_streams - f = frame_factory.build_data_frame(b'some data'*1500) + f = frame_factory.build_data_frame(b"some data"*1500) events = c.receive_data(f.serialize()*3) assert not events @@ -663,7 +665,7 @@ def test_send_update_on_closed_streams(self, frame_factory): ).serialize() assert c.data_to_send() == expected - f = frame_factory.build_data_frame(b'') + f = frame_factory.build_data_frame(b"") events = c.receive_data(f.serialize()) assert not events @@ -674,15 +676,16 @@ def test_send_update_on_closed_streams(self, frame_factory): assert c.data_to_send() == expected -class TestAutomaticFlowControl(object): +class TestAutomaticFlowControl: """ Tests for the automatic flow control logic. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] server_config = h2.config.H2Configuration(client_side=False) @@ -698,16 +701,16 @@ def _setup_connection_and_send_headers(self, frame_factory): c.receive_data(frame_factory.preamble()) c.update_settings( - {h2.settings.SettingCodes.MAX_FRAME_SIZE: self.DEFAULT_FLOW_WINDOW} + {h2.settings.SettingCodes.MAX_FRAME_SIZE: self.DEFAULT_FLOW_WINDOW}, ) settings_frame = frame_factory.build_settings_frame( - settings={}, ack=True + settings={}, ack=True, ) c.receive_data(settings_frame.serialize()) c.clear_outbound_data_buffer() headers_frame = frame_factory.build_headers_frame( - headers=self.example_request_headers + headers=self.example_request_headers, ) c.receive_data(headers_frame.serialize()) c.clear_outbound_data_buffer() @@ -715,7 +718,7 @@ def _setup_connection_and_send_headers(self, frame_factory): @given(stream_id=integers(max_value=0)) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) - def test_must_acknowledge_for_stream(self, frame_factory, stream_id): + def test_must_acknowledge_for_stream(self, frame_factory, stream_id) -> None: """ Flow control acknowledgements must be done on a stream ID that is greater than zero. @@ -730,18 +733,18 @@ def test_must_acknowledge_for_stream(self, frame_factory, stream_id): # data acknolwedgement. c = self._setup_connection_and_send_headers(frame_factory) data_frame = frame_factory.build_data_frame( - b'some data', flags=['END_STREAM'] + b"some data", flags=["END_STREAM"], ) c.receive_data(data_frame.serialize()) with pytest.raises(ValueError): c.acknowledge_received_data( - acknowledged_size=5, stream_id=stream_id + acknowledged_size=5, stream_id=stream_id, ) @given(size=integers(max_value=-1)) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) - def test_cannot_acknowledge_less_than_zero(self, frame_factory, size): + def test_cannot_acknowledge_less_than_zero(self, frame_factory, size) -> None: """ The user must acknowledge at least 0 bytes. """ @@ -755,14 +758,14 @@ def test_cannot_acknowledge_less_than_zero(self, frame_factory, size): # data acknolwedgement. c = self._setup_connection_and_send_headers(frame_factory) data_frame = frame_factory.build_data_frame( - b'some data', flags=['END_STREAM'] + b"some data", flags=["END_STREAM"], ) c.receive_data(data_frame.serialize()) with pytest.raises(ValueError): c.acknowledge_received_data(acknowledged_size=size, stream_id=1) - def test_acknowledging_small_chunks_does_nothing(self, frame_factory): + def test_acknowledging_small_chunks_does_nothing(self, frame_factory) -> None: """ When a small amount of data is received and acknowledged, no window update is emitted. @@ -770,17 +773,17 @@ def test_acknowledging_small_chunks_does_nothing(self, frame_factory): c = self._setup_connection_and_send_headers(frame_factory) data_frame = frame_factory.build_data_frame( - b'some data', flags=['END_STREAM'] + b"some data", flags=["END_STREAM"], ) data_event = c.receive_data(data_frame.serialize())[0] c.acknowledge_received_data( - data_event.flow_controlled_length, stream_id=1 + data_event.flow_controlled_length, stream_id=1, ) assert not c.data_to_send() - def test_acknowledging_no_data_does_nothing(self, frame_factory): + def test_acknowledging_no_data_does_nothing(self, frame_factory) -> None: """ If a user accidentally acknowledges no data, nothing happens. """ @@ -788,28 +791,28 @@ def test_acknowledging_no_data_does_nothing(self, frame_factory): # Send an empty data frame, just to give the user impetus to ack the # data. - data_frame = frame_factory.build_data_frame(b'') + data_frame = frame_factory.build_data_frame(b"") c.receive_data(data_frame.serialize()) c.acknowledge_received_data(0, stream_id=1) assert not c.data_to_send() - @pytest.mark.parametrize('force_cleanup', (True, False)) + @pytest.mark.parametrize("force_cleanup", [True, False]) def test_acknowledging_data_on_closed_stream(self, frame_factory, - force_cleanup): + force_cleanup) -> None: """ When acknowledging data on a stream that has just been closed, no acknowledgement is given for that stream, only for the connection. """ c = self._setup_connection_and_send_headers(frame_factory) - data_to_send = b'\x00' * self.DEFAULT_FLOW_WINDOW + data_to_send = b"\x00" * self.DEFAULT_FLOW_WINDOW data_frame = frame_factory.build_data_frame(data_to_send) c.receive_data(data_frame.serialize()) rst_frame = frame_factory.build_rst_stream_frame( - stream_id=1 + stream_id=1, ) c.receive_data(rst_frame.serialize()) c.clear_outbound_data_buffer() @@ -822,11 +825,11 @@ def test_acknowledging_data_on_closed_stream(self, c.acknowledge_received_data(2048, stream_id=1) expected = frame_factory.build_window_update_frame( - stream_id=0, increment=2048 + stream_id=0, increment=2048, ) assert c.data_to_send() == expected.serialize() - def test_acknowledging_streams_we_never_saw(self, frame_factory): + def test_acknowledging_streams_we_never_saw(self, frame_factory) -> None: """ If the user acknowledges a stream ID we've never seen, that raises a NoSuchStreamError. @@ -841,7 +844,7 @@ def test_acknowledging_streams_we_never_saw(self, frame_factory): @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) def test_acknowledging_1024_bytes_when_empty_increments(self, frame_factory, - increment): + increment) -> None: """ If the flow control window is empty and we acknowledge 1024 bytes or more, we will emit a WINDOW_UPDATE frame just to move the connection @@ -855,20 +858,20 @@ def test_acknowledging_1024_bytes_when_empty_increments(self, c = self._setup_connection_and_send_headers(frame_factory) - data_to_send = b'\x00' * self.DEFAULT_FLOW_WINDOW + data_to_send = b"\x00" * self.DEFAULT_FLOW_WINDOW data_frame = frame_factory.build_data_frame(data_to_send) c.receive_data(data_frame.serialize()) c.acknowledge_received_data(increment, stream_id=1) first_expected = frame_factory.build_window_update_frame( - stream_id=0, increment=increment + stream_id=0, increment=increment, ) second_expected = frame_factory.build_window_update_frame( - stream_id=1, increment=increment + stream_id=1, increment=increment, ) - expected_data = b''.join( - [first_expected.serialize(), second_expected.serialize()] + expected_data = b"".join( + [first_expected.serialize(), second_expected.serialize()], ) assert c.data_to_send() == expected_data @@ -876,7 +879,7 @@ def test_acknowledging_1024_bytes_when_empty_increments(self, # increment the stream window anyway. @given(integers(min_value=1025, max_value=(DEFAULT_FLOW_WINDOW // 4) - 1)) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) - def test_connection_only_empty(self, frame_factory, increment): + def test_connection_only_empty(self, frame_factory, increment) -> None: """ If the connection flow control window is empty, but the stream flow control windows aren't, and 1024 bytes or more are acknowledged by the @@ -893,20 +896,20 @@ def test_connection_only_empty(self, frame_factory, increment): for stream_id in [3, 5, 7]: f = frame_factory.build_headers_frame( - headers=self.example_request_headers, stream_id=stream_id + headers=self.example_request_headers, stream_id=stream_id, ) c.receive_data(f.serialize()) # Now we send 1/4 of the connection window per stream. Annoyingly, # that's an odd number, so we need to round the last frame up. - data_to_send = b'\x00' * (self.DEFAULT_FLOW_WINDOW // 4) + data_to_send = b"\x00" * (self.DEFAULT_FLOW_WINDOW // 4) for stream_id in [1, 3, 5]: f = frame_factory.build_data_frame( - data_to_send, stream_id=stream_id + data_to_send, stream_id=stream_id, ) c.receive_data(f.serialize()) - data_to_send = b'\x00' * c.remote_flow_control_window(7) + data_to_send = b"\x00" * c.remote_flow_control_window(7) data_frame = frame_factory.build_data_frame(data_to_send, stream_id=7) c.receive_data(data_frame.serialize()) @@ -914,13 +917,13 @@ def test_connection_only_empty(self, frame_factory, increment): c.acknowledge_received_data(increment, stream_id=1) expected_data = frame_factory.build_window_update_frame( - stream_id=0, increment=increment + stream_id=0, increment=increment, ).serialize() assert c.data_to_send() == expected_data @given(integers(min_value=1025, max_value=DEFAULT_FLOW_WINDOW)) @settings(suppress_health_check=[HealthCheck.function_scoped_fixture]) - def test_mixing_update_forms(self, frame_factory, increment): + def test_mixing_update_forms(self, frame_factory, increment) -> None: """ If the user mixes acknowledging data with manually incrementing windows, we still keep track of what's going on. @@ -933,14 +936,14 @@ def test_mixing_update_forms(self, frame_factory, increment): # Empty the flow control window. c = self._setup_connection_and_send_headers(frame_factory) - data_to_send = b'\x00' * self.DEFAULT_FLOW_WINDOW + data_to_send = b"\x00" * self.DEFAULT_FLOW_WINDOW data_frame = frame_factory.build_data_frame(data_to_send) c.receive_data(data_frame.serialize()) # Manually increment the connection flow control window back to fully # open, but leave the stream window closed. c.increment_flow_control_window( - stream_id=None, increment=self.DEFAULT_FLOW_WINDOW + stream_id=None, increment=self.DEFAULT_FLOW_WINDOW, ) c.clear_outbound_data_buffer() @@ -951,6 +954,6 @@ def test_mixing_update_forms(self, frame_factory, increment): # We expect to see one window update frame only, for the stream. expected_data = frame_factory.build_window_update_frame( - stream_id=1, increment=increment + stream_id=1, increment=increment, ).serialize() assert c.data_to_send() == expected_data diff --git a/tests/test_h2_upgrade.py b/tests/test_h2_upgrade.py index 860ab017d..fae46082e 100644 --- a/tests/test_h2_upgrade.py +++ b/tests/test_h2_upgrade.py @@ -1,14 +1,12 @@ """ -test_h2_upgrade.py -~~~~~~~~~~~~~~~~~~ - -This module contains tests that exercise the HTTP Upgrade functionality of +Tests that exercise the HTTP Upgrade functionality of hyper-h2, ensuring that clients and servers can upgrade their plaintext HTTP/1.1 connections to HTTP/2. """ +from __future__ import annotations + import base64 -from h2.utilities import utf8_encode_headers import pytest import h2.config @@ -16,32 +14,33 @@ import h2.errors import h2.events import h2.exceptions - +from h2.utilities import utf8_encode_headers EXAMPLE_REQUEST_HEADERS = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] EXAMPLE_REQUEST_HEADERS_BYTES = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] -class TestClientUpgrade(object): +class TestClientUpgrade: """ Tests of the client-side of the HTTP/2 upgrade dance. """ + example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), ] - def test_returns_http2_settings(self, frame_factory): + def test_returns_http2_settings(self, frame_factory) -> None: """ Calling initiate_upgrade_connection returns a base64url encoded Settings frame with the settings used by the connection. @@ -50,16 +49,16 @@ def test_returns_http2_settings(self, frame_factory): data = conn.initiate_upgrade_connection() # The base64 encoding must not be padded. - assert not data.endswith(b'=') + assert not data.endswith(b"=") # However, SETTINGS frames should never need to be padded. decoded_frame = base64.urlsafe_b64decode(data) expected_frame = frame_factory.build_settings_frame( - settings=conn.local_settings + settings=conn.local_settings, ) assert decoded_frame == expected_frame.serialize_body() - def test_emits_preamble(self, frame_factory): + def test_emits_preamble(self, frame_factory) -> None: """ Calling initiate_upgrade_connection emits the connection preamble. """ @@ -71,11 +70,11 @@ def test_emits_preamble(self, frame_factory): data = data[len(frame_factory.preamble()):] expected_frame = frame_factory.build_settings_frame( - settings=conn.local_settings + settings=conn.local_settings, ) assert data == expected_frame.serialize() - def test_can_receive_response(self, frame_factory): + def test_can_receive_response(self, frame_factory) -> None: """ After upgrading, we can safely receive a response. """ @@ -89,8 +88,8 @@ def test_can_receive_response(self, frame_factory): ) f2 = frame_factory.build_data_frame( stream_id=1, - data=b'some data', - flags=['END_STREAM'] + data=b"some data", + flags=["END_STREAM"], ) events = c.receive_data(f1.serialize() + f2.serialize()) assert len(events) == 3 @@ -100,13 +99,13 @@ def test_can_receive_response(self, frame_factory): assert isinstance(events[2], h2.events.StreamEnded) assert events[0].headers == self.example_response_headers - assert events[1].data == b'some data' + assert events[1].data == b"some data" assert all(e.stream_id == 1 for e in events) assert not c.data_to_send() @pytest.mark.parametrize("headers", [EXAMPLE_REQUEST_HEADERS, EXAMPLE_REQUEST_HEADERS_BYTES]) - def test_can_receive_pushed_stream(self, frame_factory, headers): + def test_can_receive_pushed_stream(self, frame_factory, headers) -> None: """ After upgrading, we can safely receive a pushed stream. """ @@ -117,7 +116,7 @@ def test_can_receive_pushed_stream(self, frame_factory, headers): f = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, - headers=headers + headers=headers, ) events = c.receive_data(f.serialize()) assert len(events) == 1 @@ -128,7 +127,7 @@ def test_can_receive_pushed_stream(self, frame_factory, headers): assert events[0].pushed_stream_id == 2 @pytest.mark.parametrize("headers", [EXAMPLE_REQUEST_HEADERS, EXAMPLE_REQUEST_HEADERS_BYTES]) - def test_cannot_send_headers_stream_1(self, frame_factory, headers): + def test_cannot_send_headers_stream_1(self, frame_factory, headers) -> None: """ After upgrading, we cannot send headers on stream 1. """ @@ -139,7 +138,7 @@ def test_cannot_send_headers_stream_1(self, frame_factory, headers): with pytest.raises(h2.exceptions.ProtocolError): c.send_headers(stream_id=1, headers=headers) - def test_cannot_send_data_stream_1(self, frame_factory): + def test_cannot_send_data_stream_1(self, frame_factory) -> None: """ After upgrading, we cannot send data on stream 1. """ @@ -148,20 +147,21 @@ def test_cannot_send_data_stream_1(self, frame_factory): c.clear_outbound_data_buffer() with pytest.raises(h2.exceptions.ProtocolError): - c.send_data(stream_id=1, data=b'some data') + c.send_data(stream_id=1, data=b"some data") -class TestServerUpgrade(object): +class TestServerUpgrade: """ Tests of the server-side of the HTTP/2 upgrade dance. """ + example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) - def test_returns_nothing(self, frame_factory): + def test_returns_nothing(self, frame_factory) -> None: """ Calling initiate_upgrade_connection returns nothing. """ @@ -170,7 +170,7 @@ def test_returns_nothing(self, frame_factory): data = conn.initiate_upgrade_connection(curl_header) assert data is None - def test_emits_preamble(self, frame_factory): + def test_emits_preamble(self, frame_factory) -> None: """ Calling initiate_upgrade_connection emits the connection preamble. """ @@ -179,11 +179,11 @@ def test_emits_preamble(self, frame_factory): data = conn.data_to_send() expected_frame = frame_factory.build_settings_frame( - settings=conn.local_settings + settings=conn.local_settings, ) assert data == expected_frame.serialize() - def test_can_send_response(self, frame_factory): + def test_can_send_response(self, frame_factory) -> None: """ After upgrading, we can safely send a response. """ @@ -192,7 +192,7 @@ def test_can_send_response(self, frame_factory): c.clear_outbound_data_buffer() c.send_headers(stream_id=1, headers=self.example_response_headers) - c.send_data(stream_id=1, data=b'some data', end_stream=True) + c.send_data(stream_id=1, data=b"some data", end_stream=True) f1 = frame_factory.build_headers_frame( stream_id=1, @@ -200,15 +200,15 @@ def test_can_send_response(self, frame_factory): ) f2 = frame_factory.build_data_frame( stream_id=1, - data=b'some data', - flags=['END_STREAM'] + data=b"some data", + flags=["END_STREAM"], ) expected_data = f1.serialize() + f2.serialize() assert c.data_to_send() == expected_data @pytest.mark.parametrize("headers", [EXAMPLE_REQUEST_HEADERS, EXAMPLE_REQUEST_HEADERS_BYTES]) - def test_can_push_stream(self, frame_factory, headers): + def test_can_push_stream(self, frame_factory, headers) -> None: """ After upgrading, we can safely push a stream. """ @@ -219,7 +219,7 @@ def test_can_push_stream(self, frame_factory, headers): c.push_stream( stream_id=1, promised_stream_id=2, - request_headers=headers + request_headers=headers, ) f = frame_factory.build_push_promise_frame( @@ -230,7 +230,7 @@ def test_can_push_stream(self, frame_factory, headers): assert c.data_to_send() == f.serialize() @pytest.mark.parametrize("headers", [EXAMPLE_REQUEST_HEADERS, EXAMPLE_REQUEST_HEADERS_BYTES]) - def test_cannot_receive_headers_stream_1(self, frame_factory, headers): + def test_cannot_receive_headers_stream_1(self, frame_factory, headers) -> None: """ After upgrading, we cannot receive headers on stream 1. """ @@ -251,7 +251,7 @@ def test_cannot_receive_headers_stream_1(self, frame_factory, headers): ) assert c.data_to_send() == expected_frame.serialize() - def test_cannot_receive_data_stream_1(self, frame_factory): + def test_cannot_receive_data_stream_1(self, frame_factory) -> None: """ After upgrading, we cannot receive data on stream 1. """ @@ -262,7 +262,7 @@ def test_cannot_receive_data_stream_1(self, frame_factory): f = frame_factory.build_data_frame( stream_id=1, - data=b'some data', + data=b"some data", ) c.receive_data(f.serialize()) @@ -272,7 +272,7 @@ def test_cannot_receive_data_stream_1(self, frame_factory): ).serialize() assert c.data_to_send() == expected - def test_client_settings_are_applied(self, frame_factory): + def test_client_settings_are_applied(self, frame_factory) -> None: """ The settings provided by the client are applied and immediately ACK'ed. @@ -299,7 +299,7 @@ def test_client_settings_are_applied(self, frame_factory): # and has not sent a SETTINGS ack, and also that the server has the # correct settings. expected_frame = frame_factory.build_settings_frame( - server.local_settings + server.local_settings, ) assert server.data_to_send() == expected_frame.serialize() diff --git a/tests/test_head_request.py b/tests/test_head_request.py index 75f4cd76e..e32d1525f 100644 --- a/tests/test_head_request.py +++ b/tests/test_head_request.py @@ -1,42 +1,40 @@ -""" -test_head_request -~~~~~~~~~~~~~~~~~ -""" -import h2.connection +from __future__ import annotations + import pytest +import h2.connection EXAMPLE_REQUEST_HEADERS_BYTES = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'HEAD'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"HEAD"), ] EXAMPLE_REQUEST_HEADERS = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'HEAD'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "HEAD"), ] -class TestHeadRequest(object): +class TestHeadRequest: example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0'), - (b'content_length', b'1'), + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), + (b"content_length", b"1"), ] - @pytest.mark.parametrize('headers', [EXAMPLE_REQUEST_HEADERS, EXAMPLE_REQUEST_HEADERS_BYTES]) - def test_non_zero_content_and_no_body(self, frame_factory, headers): + @pytest.mark.parametrize("headers", [EXAMPLE_REQUEST_HEADERS, EXAMPLE_REQUEST_HEADERS_BYTES]) + def test_non_zero_content_and_no_body(self, frame_factory, headers) -> None: c = h2.connection.H2Connection() c.initiate_connection() c.send_headers(1, headers, end_stream=True) f = frame_factory.build_headers_frame( self.example_response_headers, - flags=['END_STREAM'] + flags=["END_STREAM"], ) events = c.receive_data(f.serialize()) @@ -47,16 +45,16 @@ def test_non_zero_content_and_no_body(self, frame_factory, headers): assert event.stream_id == 1 assert event.headers == self.example_response_headers - @pytest.mark.parametrize('headers', [EXAMPLE_REQUEST_HEADERS, EXAMPLE_REQUEST_HEADERS_BYTES]) - def test_reject_non_zero_content_and_body(self, frame_factory, headers): + @pytest.mark.parametrize("headers", [EXAMPLE_REQUEST_HEADERS, EXAMPLE_REQUEST_HEADERS_BYTES]) + def test_reject_non_zero_content_and_body(self, frame_factory, headers) -> None: c = h2.connection.H2Connection() c.initiate_connection() c.send_headers(1, headers) headers = frame_factory.build_headers_frame( - self.example_response_headers + self.example_response_headers, ) - data = frame_factory.build_data_frame(data=b'\x01') + data = frame_factory.build_data_frame(data=b"\x01") c.receive_data(headers.serialize()) with pytest.raises(h2.exceptions.InvalidBodyLengthError): diff --git a/tests/test_header_indexing.py b/tests/test_header_indexing.py index 30240d95a..f50f23d8b 100644 --- a/tests/test_header_indexing.py +++ b/tests/test_header_indexing.py @@ -1,19 +1,17 @@ """ -test_header_indexing.py -~~~~~~~~~~~~~~~~~~~~~~~ - This module contains tests that use HPACK header tuples that provide additional metadata to the hpack module about how to encode the headers. """ -import pytest +from __future__ import annotations +import pytest from hpack import HeaderTuple, NeverIndexedHeaderTuple import h2.config import h2.connection -def assert_header_blocks_actually_equal(block_a, block_b): +def assert_header_blocks_actually_equal(block_a, block_b) -> None: """ Asserts that two header bocks are really, truly equal, down to the types of their tuples. Doesn't return anything. @@ -25,70 +23,71 @@ def assert_header_blocks_actually_equal(block_a, block_b): assert a.__class__ is b.__class__ -class TestHeaderIndexing(object): +class TestHeaderIndexing: """ Test that Hyper-h2 can correctly handle never indexed header fields using the appropriate hpack data structures. """ + example_request_headers = [ - HeaderTuple(':authority', 'example.com'), - HeaderTuple(':path', '/'), - HeaderTuple(':scheme', 'https'), - HeaderTuple(':method', 'GET'), + HeaderTuple(":authority", "example.com"), + HeaderTuple(":path", "/"), + HeaderTuple(":scheme", "https"), + HeaderTuple(":method", "GET"), ] bytes_example_request_headers = [ - HeaderTuple(b':authority', b'example.com'), - HeaderTuple(b':path', b'/'), - HeaderTuple(b':scheme', b'https'), - HeaderTuple(b':method', b'GET'), + HeaderTuple(b":authority", b"example.com"), + HeaderTuple(b":path", b"/"), + HeaderTuple(b":scheme", b"https"), + HeaderTuple(b":method", b"GET"), ] extended_request_headers = [ - HeaderTuple(':authority', 'example.com'), - HeaderTuple(':path', '/'), - HeaderTuple(':scheme', 'https'), - HeaderTuple(':method', 'GET'), - NeverIndexedHeaderTuple('authorization', 'realpassword'), + HeaderTuple(":authority", "example.com"), + HeaderTuple(":path", "/"), + HeaderTuple(":scheme", "https"), + HeaderTuple(":method", "GET"), + NeverIndexedHeaderTuple("authorization", "realpassword"), ] bytes_extended_request_headers = [ - HeaderTuple(b':authority', b'example.com'), - HeaderTuple(b':path', b'/'), - HeaderTuple(b':scheme', b'https'), - HeaderTuple(b':method', b'GET'), - NeverIndexedHeaderTuple(b'authorization', b'realpassword'), + HeaderTuple(b":authority", b"example.com"), + HeaderTuple(b":path", b"/"), + HeaderTuple(b":scheme", b"https"), + HeaderTuple(b":method", b"GET"), + NeverIndexedHeaderTuple(b"authorization", b"realpassword"), ] example_response_headers = [ - HeaderTuple(':status', '200'), - HeaderTuple('server', 'fake-serv/0.1.0') + HeaderTuple(":status", "200"), + HeaderTuple("server", "fake-serv/0.1.0"), ] bytes_example_response_headers = [ - HeaderTuple(b':status', b'200'), - HeaderTuple(b'server', b'fake-serv/0.1.0') + HeaderTuple(b":status", b"200"), + HeaderTuple(b"server", b"fake-serv/0.1.0"), ] extended_response_headers = [ - HeaderTuple(':status', '200'), - HeaderTuple('server', 'fake-serv/0.1.0'), - NeverIndexedHeaderTuple('secure', 'you-bet'), + HeaderTuple(":status", "200"), + HeaderTuple("server", "fake-serv/0.1.0"), + NeverIndexedHeaderTuple("secure", "you-bet"), ] bytes_extended_response_headers = [ - HeaderTuple(b':status', b'200'), - HeaderTuple(b'server', b'fake-serv/0.1.0'), - NeverIndexedHeaderTuple(b'secure', b'you-bet'), + HeaderTuple(b":status", b"200"), + HeaderTuple(b"server", b"fake-serv/0.1.0"), + NeverIndexedHeaderTuple(b"secure", b"you-bet"), ] server_config = h2.config.H2Configuration(client_side=False) @pytest.mark.parametrize( - 'headers', ( + "headers", [ example_request_headers, bytes_example_request_headers, extended_request_headers, bytes_extended_request_headers, - ) + ], ) - def test_sending_header_tuples(self, headers, frame_factory): + def test_sending_header_tuples(self, headers, frame_factory) -> None: """ Providing HeaderTuple and HeaderTuple subclasses preserves the metadata about indexing. @@ -104,14 +103,14 @@ def test_sending_header_tuples(self, headers, frame_factory): assert c.data_to_send() == f.serialize() @pytest.mark.parametrize( - 'headers', ( + "headers", [ example_request_headers, bytes_example_request_headers, extended_request_headers, bytes_extended_request_headers, - ) + ], ) - def test_header_tuples_in_pushes(self, headers, frame_factory): + def test_header_tuples_in_pushes(self, headers, frame_factory) -> None: """ Providing HeaderTuple and HeaderTuple subclasses to push promises preserves metadata about indexing. @@ -121,7 +120,7 @@ def test_header_tuples_in_pushes(self, headers, frame_factory): # We can use normal headers for the request. f = frame_factory.build_headers_frame( - self.example_request_headers + self.example_request_headers, ) c.receive_data(f.serialize()) @@ -130,36 +129,36 @@ def test_header_tuples_in_pushes(self, headers, frame_factory): stream_id=1, promised_stream_id=2, headers=headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) c.clear_outbound_data_buffer() c.push_stream( stream_id=1, promised_stream_id=2, - request_headers=headers + request_headers=headers, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize( - 'headers,encoding', ( - (example_request_headers, 'utf-8'), + ("headers", "encoding"), [ + (example_request_headers, "utf-8"), (bytes_example_request_headers, None), - (extended_request_headers, 'utf-8'), + (extended_request_headers, "utf-8"), (bytes_extended_request_headers, None), - ) + ], ) def test_header_tuples_are_decoded_request(self, headers, encoding, - frame_factory): + frame_factory) -> None: """ The indexing status of the header is preserved when emitting RequestReceived events. """ config = h2.config.H2Configuration( - client_side=False, header_encoding=encoding + client_side=False, header_encoding=encoding, ) c = h2.connection.H2Connection(config=config) c.receive_data(frame_factory.preamble()) @@ -175,23 +174,23 @@ def test_header_tuples_are_decoded_request(self, assert_header_blocks_actually_equal(headers, event.headers) @pytest.mark.parametrize( - 'headers,encoding', ( - (example_response_headers, 'utf-8'), + ("headers", "encoding"), [ + (example_response_headers, "utf-8"), (bytes_example_response_headers, None), - (extended_response_headers, 'utf-8'), + (extended_response_headers, "utf-8"), (bytes_extended_response_headers, None), - ) + ], ) def test_header_tuples_are_decoded_response(self, headers, encoding, - frame_factory): + frame_factory) -> None: """ The indexing status of the header is preserved when emitting ResponseReceived events. """ config = h2.config.H2Configuration( - header_encoding=encoding + header_encoding=encoding, ) c = h2.connection.H2Connection(config=config) c.initiate_connection() @@ -208,17 +207,17 @@ def test_header_tuples_are_decoded_response(self, assert_header_blocks_actually_equal(headers, event.headers) @pytest.mark.parametrize( - 'headers,encoding', ( - (example_response_headers, 'utf-8'), + ("headers", "encoding"), [ + (example_response_headers, "utf-8"), (bytes_example_response_headers, None), - (extended_response_headers, 'utf-8'), + (extended_response_headers, "utf-8"), (bytes_extended_response_headers, None), - ) + ], ) def test_header_tuples_are_decoded_info_response(self, headers, encoding, - frame_factory): + frame_factory) -> None: """ The indexing status of the header is preserved when emitting InformationalResponseReceived events. @@ -227,12 +226,12 @@ def test_header_tuples_are_decoded_info_response(self, # to avoid breaking the example headers. headers = headers[:] if encoding: - headers[0] = HeaderTuple(':status', '100') + headers[0] = HeaderTuple(":status", "100") else: - headers[0] = HeaderTuple(b':status', b'100') + headers[0] = HeaderTuple(b":status", b"100") config = h2.config.H2Configuration( - header_encoding=encoding + header_encoding=encoding, ) c = h2.connection.H2Connection(config=config) c.initiate_connection() @@ -249,17 +248,17 @@ def test_header_tuples_are_decoded_info_response(self, assert_header_blocks_actually_equal(headers, event.headers) @pytest.mark.parametrize( - 'headers,encoding', ( - (example_response_headers, 'utf-8'), + ("headers", "encoding"), [ + (example_response_headers, "utf-8"), (bytes_example_response_headers, None), - (extended_response_headers, 'utf-8'), + (extended_response_headers, "utf-8"), (bytes_extended_response_headers, None), - ) + ], ) def test_header_tuples_are_decoded_trailers(self, headers, encoding, - frame_factory): + frame_factory) -> None: """ The indexing status of the header is preserved when emitting TrailersReceived events. @@ -270,7 +269,7 @@ def test_header_tuples_are_decoded_trailers(self, headers = headers[1:] config = h2.config.H2Configuration( - header_encoding=encoding + header_encoding=encoding, ) c = h2.connection.H2Connection(config=config) c.initiate_connection() @@ -279,7 +278,7 @@ def test_header_tuples_are_decoded_trailers(self, data = f.serialize() c.receive_data(data) - f = frame_factory.build_headers_frame(headers, flags=['END_STREAM']) + f = frame_factory.build_headers_frame(headers, flags=["END_STREAM"]) data = f.serialize() events = c.receive_data(data) @@ -290,23 +289,23 @@ def test_header_tuples_are_decoded_trailers(self, assert_header_blocks_actually_equal(headers, event.headers) @pytest.mark.parametrize( - 'headers,encoding', ( - (example_request_headers, 'utf-8'), + ("headers", "encoding"), [ + (example_request_headers, "utf-8"), (bytes_example_request_headers, None), - (extended_request_headers, 'utf-8'), + (extended_request_headers, "utf-8"), (bytes_extended_request_headers, None), - ) + ], ) def test_header_tuples_are_decoded_push_promise(self, headers, encoding, - frame_factory): + frame_factory) -> None: """ The indexing status of the header is preserved when emitting PushedStreamReceived events. """ config = h2.config.H2Configuration( - header_encoding=encoding + header_encoding=encoding, ) c = h2.connection.H2Connection(config=config) c.initiate_connection() @@ -316,7 +315,7 @@ def test_header_tuples_are_decoded_push_promise(self, stream_id=1, promised_stream_id=2, headers=headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) data = f.serialize() events = c.receive_data(data) @@ -328,116 +327,115 @@ def test_header_tuples_are_decoded_push_promise(self, assert_header_blocks_actually_equal(headers, event.headers) -class TestSecureHeaders(object): +class TestSecureHeaders: """ Certain headers should always be transformed to their never-indexed form. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] bytes_example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] possible_auth_headers = [ - ('authorization', 'test'), - ('Authorization', 'test'), - ('authorization', 'really long test'), - HeaderTuple('authorization', 'test'), - HeaderTuple('Authorization', 'test'), - HeaderTuple('authorization', 'really long test'), - NeverIndexedHeaderTuple('authorization', 'test'), - NeverIndexedHeaderTuple('Authorization', 'test'), - NeverIndexedHeaderTuple('authorization', 'really long test'), - (b'authorization', b'test'), - (b'Authorization', b'test'), - (b'authorization', b'really long test'), - HeaderTuple(b'authorization', b'test'), - HeaderTuple(b'Authorization', b'test'), - HeaderTuple(b'authorization', b'really long test'), - NeverIndexedHeaderTuple(b'authorization', b'test'), - NeverIndexedHeaderTuple(b'Authorization', b'test'), - NeverIndexedHeaderTuple(b'authorization', b'really long test'), - ('proxy-authorization', 'test'), - ('Proxy-Authorization', 'test'), - ('proxy-authorization', 'really long test'), - HeaderTuple('proxy-authorization', 'test'), - HeaderTuple('Proxy-Authorization', 'test'), - HeaderTuple('proxy-authorization', 'really long test'), - NeverIndexedHeaderTuple('proxy-authorization', 'test'), - NeverIndexedHeaderTuple('Proxy-Authorization', 'test'), - NeverIndexedHeaderTuple('proxy-authorization', 'really long test'), - (b'proxy-authorization', b'test'), - (b'Proxy-Authorization', b'test'), - (b'proxy-authorization', b'really long test'), - HeaderTuple(b'proxy-authorization', b'test'), - HeaderTuple(b'Proxy-Authorization', b'test'), - HeaderTuple(b'proxy-authorization', b'really long test'), - NeverIndexedHeaderTuple(b'proxy-authorization', b'test'), - NeverIndexedHeaderTuple(b'Proxy-Authorization', b'test'), - NeverIndexedHeaderTuple(b'proxy-authorization', b'really long test'), + ("authorization", "test"), + ("Authorization", "test"), + ("authorization", "really long test"), + HeaderTuple("authorization", "test"), + HeaderTuple("Authorization", "test"), + HeaderTuple("authorization", "really long test"), + NeverIndexedHeaderTuple("authorization", "test"), + NeverIndexedHeaderTuple("Authorization", "test"), + NeverIndexedHeaderTuple("authorization", "really long test"), + (b"authorization", b"test"), + (b"Authorization", b"test"), + (b"authorization", b"really long test"), + HeaderTuple(b"authorization", b"test"), + HeaderTuple(b"Authorization", b"test"), + HeaderTuple(b"authorization", b"really long test"), + NeverIndexedHeaderTuple(b"authorization", b"test"), + NeverIndexedHeaderTuple(b"Authorization", b"test"), + NeverIndexedHeaderTuple(b"authorization", b"really long test"), + ("proxy-authorization", "test"), + ("Proxy-Authorization", "test"), + ("proxy-authorization", "really long test"), + HeaderTuple("proxy-authorization", "test"), + HeaderTuple("Proxy-Authorization", "test"), + HeaderTuple("proxy-authorization", "really long test"), + NeverIndexedHeaderTuple("proxy-authorization", "test"), + NeverIndexedHeaderTuple("Proxy-Authorization", "test"), + NeverIndexedHeaderTuple("proxy-authorization", "really long test"), + (b"proxy-authorization", b"test"), + (b"Proxy-Authorization", b"test"), + (b"proxy-authorization", b"really long test"), + HeaderTuple(b"proxy-authorization", b"test"), + HeaderTuple(b"Proxy-Authorization", b"test"), + HeaderTuple(b"proxy-authorization", b"really long test"), + NeverIndexedHeaderTuple(b"proxy-authorization", b"test"), + NeverIndexedHeaderTuple(b"Proxy-Authorization", b"test"), + NeverIndexedHeaderTuple(b"proxy-authorization", b"really long test"), ] secured_cookie_headers = [ - ('cookie', 'short'), - ('Cookie', 'short'), - ('cookie', 'nineteen byte cooki'), - HeaderTuple('cookie', 'short'), - HeaderTuple('Cookie', 'short'), - HeaderTuple('cookie', 'nineteen byte cooki'), - NeverIndexedHeaderTuple('cookie', 'short'), - NeverIndexedHeaderTuple('Cookie', 'short'), - NeverIndexedHeaderTuple('cookie', 'nineteen byte cooki'), - NeverIndexedHeaderTuple('cookie', 'longer manually secured cookie'), - (b'cookie', b'short'), - (b'Cookie', b'short'), - (b'cookie', b'nineteen byte cooki'), - HeaderTuple(b'cookie', b'short'), - HeaderTuple(b'Cookie', b'short'), - HeaderTuple(b'cookie', b'nineteen byte cooki'), - NeverIndexedHeaderTuple(b'cookie', b'short'), - NeverIndexedHeaderTuple(b'Cookie', b'short'), - NeverIndexedHeaderTuple(b'cookie', b'nineteen byte cooki'), - NeverIndexedHeaderTuple(b'cookie', b'longer manually secured cookie'), + ("cookie", "short"), + ("Cookie", "short"), + ("cookie", "nineteen byte cooki"), + HeaderTuple("cookie", "short"), + HeaderTuple("Cookie", "short"), + HeaderTuple("cookie", "nineteen byte cooki"), + NeverIndexedHeaderTuple("cookie", "short"), + NeverIndexedHeaderTuple("Cookie", "short"), + NeverIndexedHeaderTuple("cookie", "nineteen byte cooki"), + NeverIndexedHeaderTuple("cookie", "longer manually secured cookie"), + (b"cookie", b"short"), + (b"Cookie", b"short"), + (b"cookie", b"nineteen byte cooki"), + HeaderTuple(b"cookie", b"short"), + HeaderTuple(b"Cookie", b"short"), + HeaderTuple(b"cookie", b"nineteen byte cooki"), + NeverIndexedHeaderTuple(b"cookie", b"short"), + NeverIndexedHeaderTuple(b"Cookie", b"short"), + NeverIndexedHeaderTuple(b"cookie", b"nineteen byte cooki"), + NeverIndexedHeaderTuple(b"cookie", b"longer manually secured cookie"), ] unsecured_cookie_headers = [ - ('cookie', 'twenty byte cookie!!'), - ('Cookie', 'twenty byte cookie!!'), - ('cookie', 'substantially longer than 20 byte cookie'), - HeaderTuple('cookie', 'twenty byte cookie!!'), - HeaderTuple('cookie', 'twenty byte cookie!!'), - HeaderTuple('Cookie', 'twenty byte cookie!!'), - (b'cookie', b'twenty byte cookie!!'), - (b'Cookie', b'twenty byte cookie!!'), - (b'cookie', b'substantially longer than 20 byte cookie'), - HeaderTuple(b'cookie', b'twenty byte cookie!!'), - HeaderTuple(b'cookie', b'twenty byte cookie!!'), - HeaderTuple(b'Cookie', b'twenty byte cookie!!'), + ("cookie", "twenty byte cookie!!"), + ("Cookie", "twenty byte cookie!!"), + ("cookie", "substantially longer than 20 byte cookie"), + HeaderTuple("cookie", "twenty byte cookie!!"), + HeaderTuple("cookie", "twenty byte cookie!!"), + HeaderTuple("Cookie", "twenty byte cookie!!"), + (b"cookie", b"twenty byte cookie!!"), + (b"Cookie", b"twenty byte cookie!!"), + (b"cookie", b"substantially longer than 20 byte cookie"), + HeaderTuple(b"cookie", b"twenty byte cookie!!"), + HeaderTuple(b"cookie", b"twenty byte cookie!!"), + HeaderTuple(b"Cookie", b"twenty byte cookie!!"), ] server_config = h2.config.H2Configuration(client_side=False) @pytest.mark.parametrize( - 'headers', (example_request_headers, bytes_example_request_headers) + "headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('auth_header', possible_auth_headers) + @pytest.mark.parametrize("auth_header", possible_auth_headers) def test_authorization_headers_never_indexed(self, headers, auth_header, - frame_factory): + frame_factory) -> None: """ Authorization and Proxy-Authorization headers are always forced to be never-indexed, regardless of their form. """ # Regardless of what we send, we expect it to be never indexed. - send_headers = headers + [auth_header] - expected_headers = headers + [ - NeverIndexedHeaderTuple(auth_header[0].lower(), auth_header[1]) - ] + send_headers = [*headers, auth_header] + expected_headers = [*headers, NeverIndexedHeaderTuple(auth_header[0].lower(), auth_header[1])] c = h2.connection.H2Connection() c.initiate_connection() @@ -450,29 +448,27 @@ def test_authorization_headers_never_indexed(self, assert c.data_to_send() == f.serialize() @pytest.mark.parametrize( - 'headers', (example_request_headers, bytes_example_request_headers) + "headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('auth_header', possible_auth_headers) + @pytest.mark.parametrize("auth_header", possible_auth_headers) def test_authorization_headers_never_indexed_push(self, headers, auth_header, - frame_factory): + frame_factory) -> None: """ Authorization and Proxy-Authorization headers are always forced to be never-indexed, regardless of their form, when pushed by a server. """ # Regardless of what we send, we expect it to be never indexed. - send_headers = headers + [auth_header] - expected_headers = headers + [ - NeverIndexedHeaderTuple(auth_header[0].lower(), auth_header[1]) - ] + send_headers = [*headers, auth_header] + expected_headers = [*headers, NeverIndexedHeaderTuple(auth_header[0].lower(), auth_header[1])] c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) # We can use normal headers for the request. f = frame_factory.build_headers_frame( - self.example_request_headers + self.example_request_headers, ) c.receive_data(f.serialize()) @@ -481,35 +477,33 @@ def test_authorization_headers_never_indexed_push(self, stream_id=1, promised_stream_id=2, headers=expected_headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) c.clear_outbound_data_buffer() c.push_stream( stream_id=1, promised_stream_id=2, - request_headers=send_headers + request_headers=send_headers, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize( - 'headers', (example_request_headers, bytes_example_request_headers) + "headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('cookie_header', secured_cookie_headers) + @pytest.mark.parametrize("cookie_header", secured_cookie_headers) def test_short_cookie_headers_never_indexed(self, headers, cookie_header, - frame_factory): + frame_factory) -> None: """ Short cookie headers, and cookies provided as NeverIndexedHeaderTuple, are never indexed. """ # Regardless of what we send, we expect it to be never indexed. - send_headers = headers + [cookie_header] - expected_headers = headers + [ - NeverIndexedHeaderTuple(cookie_header[0].lower(), cookie_header[1]) - ] + send_headers = [*headers, cookie_header] + expected_headers = [*headers, NeverIndexedHeaderTuple(cookie_header[0].lower(), cookie_header[1])] c = h2.connection.H2Connection() c.initiate_connection() @@ -522,29 +516,27 @@ def test_short_cookie_headers_never_indexed(self, assert c.data_to_send() == f.serialize() @pytest.mark.parametrize( - 'headers', (example_request_headers, bytes_example_request_headers) + "headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('cookie_header', secured_cookie_headers) + @pytest.mark.parametrize("cookie_header", secured_cookie_headers) def test_short_cookie_headers_never_indexed_push(self, headers, cookie_header, - frame_factory): + frame_factory) -> None: """ Short cookie headers, and cookies provided as NeverIndexedHeaderTuple, are never indexed when pushed by servers. """ # Regardless of what we send, we expect it to be never indexed. - send_headers = headers + [cookie_header] - expected_headers = headers + [ - NeverIndexedHeaderTuple(cookie_header[0].lower(), cookie_header[1]) - ] + send_headers = [*headers, cookie_header] + expected_headers = [*headers, NeverIndexedHeaderTuple(cookie_header[0].lower(), cookie_header[1])] c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) # We can use normal headers for the request. f = frame_factory.build_headers_frame( - self.example_request_headers + self.example_request_headers, ) c.receive_data(f.serialize()) @@ -553,34 +545,32 @@ def test_short_cookie_headers_never_indexed_push(self, stream_id=1, promised_stream_id=2, headers=expected_headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) c.clear_outbound_data_buffer() c.push_stream( stream_id=1, promised_stream_id=2, - request_headers=send_headers + request_headers=send_headers, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize( - 'headers', (example_request_headers, bytes_example_request_headers) + "headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('cookie_header', unsecured_cookie_headers) + @pytest.mark.parametrize("cookie_header", unsecured_cookie_headers) def test_long_cookie_headers_can_be_indexed(self, headers, cookie_header, - frame_factory): + frame_factory) -> None: """ Longer cookie headers can be indexed. """ # Regardless of what we send, we expect it to be indexed. - send_headers = headers + [cookie_header] - expected_headers = headers + [ - HeaderTuple(cookie_header[0].lower(), cookie_header[1]) - ] + send_headers = [*headers, cookie_header] + expected_headers = [*headers, HeaderTuple(cookie_header[0].lower(), cookie_header[1])] c = h2.connection.H2Connection() c.initiate_connection() @@ -593,28 +583,26 @@ def test_long_cookie_headers_can_be_indexed(self, assert c.data_to_send() == f.serialize() @pytest.mark.parametrize( - 'headers', (example_request_headers, bytes_example_request_headers) + "headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('cookie_header', unsecured_cookie_headers) + @pytest.mark.parametrize("cookie_header", unsecured_cookie_headers) def test_long_cookie_headers_can_be_indexed_push(self, headers, cookie_header, - frame_factory): + frame_factory) -> None: """ Longer cookie headers can be indexed. """ # Regardless of what we send, we expect it to be never indexed. - send_headers = headers + [cookie_header] - expected_headers = headers + [ - HeaderTuple(cookie_header[0].lower(), cookie_header[1]) - ] + send_headers = [*headers, cookie_header] + expected_headers = [*headers, HeaderTuple(cookie_header[0].lower(), cookie_header[1])] c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) # We can use normal headers for the request. f = frame_factory.build_headers_frame( - self.example_request_headers + self.example_request_headers, ) c.receive_data(f.serialize()) @@ -623,14 +611,14 @@ def test_long_cookie_headers_can_be_indexed_push(self, stream_id=1, promised_stream_id=2, headers=expected_headers, - flags=['END_HEADERS'], + flags=["END_HEADERS"], ) c.clear_outbound_data_buffer() c.push_stream( stream_id=1, promised_stream_id=2, - request_headers=send_headers + request_headers=send_headers, ) assert c.data_to_send() == expected_frame.serialize() diff --git a/tests/test_informational_responses.py b/tests/test_informational_responses.py index 4193fb80f..bae9c74f0 100644 --- a/tests/test_informational_responses.py +++ b/tests/test_informational_responses.py @@ -1,10 +1,9 @@ """ -test_informational_responses -~~~~~~~~~~~~~~~~~~~~~~~~~~ - Tests that validate that hyper-h2 correctly handles informational (1XX) responses in its state machine. """ +from __future__ import annotations + import pytest import h2.config @@ -13,31 +12,32 @@ import h2.exceptions -class TestReceivingInformationalResponses(object): +class TestReceivingInformationalResponses: """ Tests for receiving informational responses. """ + example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), - (b'expect', b'100-continue'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), + (b"expect", b"100-continue"), ] example_informational_headers = [ - (b':status', b'100'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"100"), + (b"server", b"fake-serv/0.1.0"), ] example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), ] example_trailers = [ - (b'trailer', b'you-bet'), + (b"trailer", b"you-bet"), ] - @pytest.mark.parametrize('end_stream', (True, False)) - def test_single_informational_response(self, frame_factory, end_stream): + @pytest.mark.parametrize("end_stream", [True, False]) + def test_single_informational_response(self, frame_factory, end_stream) -> None: """ When receiving a informational response, the appropriate event is signaled. @@ -47,7 +47,7 @@ def test_single_informational_response(self, frame_factory, end_stream): c.send_headers( stream_id=1, headers=self.example_request_headers, - end_stream=end_stream + end_stream=end_stream, ) f = frame_factory.build_headers_frame( @@ -63,8 +63,8 @@ def test_single_informational_response(self, frame_factory, end_stream): assert event.headers == self.example_informational_headers assert event.stream_id == 1 - @pytest.mark.parametrize('end_stream', (True, False)) - def test_receiving_multiple_header_blocks(self, frame_factory, end_stream): + @pytest.mark.parametrize("end_stream", [True, False]) + def test_receiving_multiple_header_blocks(self, frame_factory, end_stream) -> None: """ At least three header blocks can be received: informational, headers, trailers. @@ -74,7 +74,7 @@ def test_receiving_multiple_header_blocks(self, frame_factory, end_stream): c.send_headers( stream_id=1, headers=self.example_request_headers, - end_stream=end_stream + end_stream=end_stream, ) f1 = frame_factory.build_headers_frame( @@ -88,10 +88,10 @@ def test_receiving_multiple_header_blocks(self, frame_factory, end_stream): f3 = frame_factory.build_headers_frame( headers=self.example_trailers, stream_id=1, - flags=['END_STREAM'], + flags=["END_STREAM"], ) events = c.receive_data( - f1.serialize() + f2.serialize() + f3.serialize() + f1.serialize() + f2.serialize() + f3.serialize(), ) assert len(events) == 4 @@ -108,10 +108,10 @@ def test_receiving_multiple_header_blocks(self, frame_factory, end_stream): assert events[2].headers == self.example_trailers assert events[2].stream_id == 1 - @pytest.mark.parametrize('end_stream', (True, False)) + @pytest.mark.parametrize("end_stream", [True, False]) def test_receiving_multiple_informational_responses(self, frame_factory, - end_stream): + end_stream) -> None: """ More than one informational response is allowed. """ @@ -120,7 +120,7 @@ def test_receiving_multiple_informational_responses(self, c.send_headers( stream_id=1, headers=self.example_request_headers, - end_stream=end_stream + end_stream=end_stream, ) f1 = frame_factory.build_headers_frame( @@ -128,7 +128,7 @@ def test_receiving_multiple_informational_responses(self, stream_id=1, ) f2 = frame_factory.build_headers_frame( - headers=[(':status', '101')], + headers=[(":status", "101")], stream_id=1, ) events = c.receive_data(f1.serialize() + f2.serialize()) @@ -140,13 +140,13 @@ def test_receiving_multiple_informational_responses(self, assert events[0].stream_id == 1 assert isinstance(events[1], h2.events.InformationalResponseReceived) - assert events[1].headers == [(b':status', b'101')] + assert events[1].headers == [(b":status", b"101")] assert events[1].stream_id == 1 - @pytest.mark.parametrize('end_stream', (True, False)) + @pytest.mark.parametrize("end_stream", [True, False]) def test_receive_provisional_response_with_end_stream(self, frame_factory, - end_stream): + end_stream) -> None: """ Receiving provisional responses with END_STREAM set causes ProtocolErrors. @@ -156,14 +156,14 @@ def test_receive_provisional_response_with_end_stream(self, c.send_headers( stream_id=1, headers=self.example_request_headers, - end_stream=end_stream + end_stream=end_stream, ) c.clear_outbound_data_buffer() f = frame_factory.build_headers_frame( headers=self.example_informational_headers, stream_id=1, - flags=['END_STREAM'] + flags=["END_STREAM"], ) with pytest.raises(h2.exceptions.ProtocolError): @@ -175,8 +175,8 @@ def test_receive_provisional_response_with_end_stream(self, ) assert c.data_to_send() == expected.serialize() - @pytest.mark.parametrize('end_stream', (True, False)) - def test_receiving_out_of_order_headers(self, frame_factory, end_stream): + @pytest.mark.parametrize("end_stream", [True, False]) + def test_receiving_out_of_order_headers(self, frame_factory, end_stream) -> None: """ When receiving a informational response after the actual response headers we consider it a ProtocolError and raise it. @@ -186,7 +186,7 @@ def test_receiving_out_of_order_headers(self, frame_factory, end_stream): c.send_headers( stream_id=1, headers=self.example_request_headers, - end_stream=end_stream + end_stream=end_stream, ) f1 = frame_factory.build_headers_frame( @@ -210,53 +210,54 @@ def test_receiving_out_of_order_headers(self, frame_factory, end_stream): assert c.data_to_send() == expected.serialize() -class TestSendingInformationalResponses(object): +class TestSendingInformationalResponses: """ Tests for sending informational responses. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), - ('expect', '100-continue'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ("expect", "100-continue"), ] bytes_example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), - (b'expect', b'100-continue'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), + (b"expect", b"100-continue"), ] informational_headers = [ - (':status', '100'), - ('server', 'fake-serv/0.1.0') + (":status", "100"), + ("server", "fake-serv/0.1.0"), ] bytes_informational_headers = [ - (b':status', b'100'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"100"), + (b"server", b"fake-serv/0.1.0"), ] example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0') + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), ] example_trailers = [ - (b'trailer', b'you-bet'), + (b"trailer", b"you-bet"), ] server_config = h2.config.H2Configuration(client_side=False) @pytest.mark.parametrize( - 'hdrs', (informational_headers, bytes_informational_headers), + "hdrs", [informational_headers, bytes_informational_headers], ) @pytest.mark.parametrize( - 'request_headers', (example_request_headers, bytes_example_request_headers), + "request_headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('end_stream', (True, False)) + @pytest.mark.parametrize("end_stream", [True, False]) def test_single_informational_response(self, frame_factory, hdrs, request_headers, - end_stream): + end_stream) -> None: """ When sending a informational response, the appropriate frames are emitted. @@ -264,7 +265,7 @@ def test_single_informational_response(self, c = h2.connection.H2Connection(config=self.server_config) c.initiate_connection() c.receive_data(frame_factory.preamble()) - flags = ['END_STREAM'] if end_stream else [] + flags = ["END_STREAM"] if end_stream else [] f = frame_factory.build_headers_frame( headers=request_headers, stream_id=1, @@ -276,7 +277,7 @@ def test_single_informational_response(self, c.send_headers( stream_id=1, - headers=hdrs + headers=hdrs, ) f = frame_factory.build_headers_frame( @@ -286,17 +287,17 @@ def test_single_informational_response(self, assert c.data_to_send() == f.serialize() @pytest.mark.parametrize( - 'hdrs', (informational_headers, bytes_informational_headers), + "hdrs", [informational_headers, bytes_informational_headers], ) @pytest.mark.parametrize( - 'request_headers', (example_request_headers, bytes_example_request_headers), + "request_headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('end_stream', (True, False)) + @pytest.mark.parametrize("end_stream", [True, False]) def test_sending_multiple_header_blocks(self, frame_factory, hdrs, request_headers, - end_stream): + end_stream) -> None: """ At least three header blocks can be sent: informational, headers, trailers. @@ -304,7 +305,7 @@ def test_sending_multiple_header_blocks(self, c = h2.connection.H2Connection(config=self.server_config) c.initiate_connection() c.receive_data(frame_factory.preamble()) - flags = ['END_STREAM'] if end_stream else [] + flags = ["END_STREAM"] if end_stream else [] f = frame_factory.build_headers_frame( headers=request_headers, stream_id=1, @@ -317,16 +318,16 @@ def test_sending_multiple_header_blocks(self, # Send the three header blocks. c.send_headers( stream_id=1, - headers=hdrs + headers=hdrs, ) c.send_headers( stream_id=1, - headers=self.example_response_headers + headers=self.example_response_headers, ) c.send_headers( stream_id=1, headers=self.example_trailers, - end_stream=True + end_stream=True, ) # Check that we sent them properly. @@ -341,7 +342,7 @@ def test_sending_multiple_header_blocks(self, f3 = frame_factory.build_headers_frame( headers=self.example_trailers, stream_id=1, - flags=['END_STREAM'] + flags=["END_STREAM"], ) assert ( c.data_to_send() == @@ -349,24 +350,24 @@ def test_sending_multiple_header_blocks(self, ) @pytest.mark.parametrize( - 'hdrs', (informational_headers, bytes_informational_headers), + "hdrs", [informational_headers, bytes_informational_headers], ) @pytest.mark.parametrize( - 'request_headers', (example_request_headers, bytes_example_request_headers), + "request_headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('end_stream', (True, False)) + @pytest.mark.parametrize("end_stream", [True, False]) def test_sending_multiple_informational_responses(self, frame_factory, hdrs, request_headers, - end_stream): + end_stream) -> None: """ More than one informational response is allowed. """ c = h2.connection.H2Connection(config=self.server_config) c.initiate_connection() c.receive_data(frame_factory.preamble()) - flags = ['END_STREAM'] if end_stream else [] + flags = ["END_STREAM"] if end_stream else [] f = frame_factory.build_headers_frame( headers=request_headers, stream_id=1, @@ -383,7 +384,7 @@ def test_sending_multiple_informational_responses(self, ) c.send_headers( stream_id=1, - headers=[(b':status', b'101')] + headers=[(b":status", b"101")], ) # Check we sent them both. @@ -392,23 +393,23 @@ def test_sending_multiple_informational_responses(self, stream_id=1, ) f2 = frame_factory.build_headers_frame( - headers=[(':status', '101')], + headers=[(":status", "101")], stream_id=1, ) assert c.data_to_send() == f1.serialize() + f2.serialize() @pytest.mark.parametrize( - 'hdrs', (informational_headers, bytes_informational_headers), + "hdrs", [informational_headers, bytes_informational_headers], ) @pytest.mark.parametrize( - 'request_headers', (example_request_headers, bytes_example_request_headers), + "request_headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('end_stream', (True, False)) + @pytest.mark.parametrize("end_stream", [True, False]) def test_send_provisional_response_with_end_stream(self, frame_factory, hdrs, request_headers, - end_stream): + end_stream) -> None: """ Sending provisional responses with END_STREAM set causes ProtocolErrors. @@ -416,7 +417,7 @@ def test_send_provisional_response_with_end_stream(self, c = h2.connection.H2Connection(config=self.server_config) c.initiate_connection() c.receive_data(frame_factory.preamble()) - flags = ['END_STREAM'] if end_stream else [] + flags = ["END_STREAM"] if end_stream else [] f = frame_factory.build_headers_frame( headers=request_headers, stream_id=1, @@ -432,17 +433,17 @@ def test_send_provisional_response_with_end_stream(self, ) @pytest.mark.parametrize( - 'hdrs', (informational_headers, bytes_informational_headers), + "hdrs", [informational_headers, bytes_informational_headers], ) @pytest.mark.parametrize( - 'request_headers', (example_request_headers, bytes_example_request_headers), + "request_headers", [example_request_headers, bytes_example_request_headers], ) - @pytest.mark.parametrize('end_stream', (True, False)) + @pytest.mark.parametrize("end_stream", [True, False]) def test_reject_sending_out_of_order_headers(self, frame_factory, hdrs, request_headers, - end_stream): + end_stream) -> None: """ When sending an informational response after the actual response headers we consider it a ProtocolError and raise it. @@ -450,7 +451,7 @@ def test_reject_sending_out_of_order_headers(self, c = h2.connection.H2Connection(config=self.server_config) c.initiate_connection() c.receive_data(frame_factory.preamble()) - flags = ['END_STREAM'] if end_stream else [] + flags = ["END_STREAM"] if end_stream else [] f = frame_factory.build_headers_frame( headers=request_headers, stream_id=1, @@ -460,11 +461,11 @@ def test_reject_sending_out_of_order_headers(self, c.send_headers( stream_id=1, - headers=self.example_response_headers + headers=self.example_response_headers, ) with pytest.raises(h2.exceptions.ProtocolError): c.send_headers( stream_id=1, - headers=hdrs + headers=hdrs, ) diff --git a/tests/test_interacting_stacks.py b/tests/test_interacting_stacks.py index 68c7e7651..c657986da 100644 --- a/tests/test_interacting_stacks.py +++ b/tests/test_interacting_stacks.py @@ -1,7 +1,4 @@ """ -test_interacting_stacks -~~~~~~~~~~~~~~~~~~~~~~~ - These tests run two entities, a client and a server, in parallel threads. These two entities talk to each other, running what amounts to a number of carefully controlled simulations of real flows. @@ -17,7 +14,7 @@ these tests, so that they can be written more easily, as they are remarkably useful. """ -from . import coroutine_tests +from __future__ import annotations import pytest @@ -26,37 +23,40 @@ import h2.events import h2.settings +from . import coroutine_tests + class TestCommunication(coroutine_tests.CoroutineTestCase): """ Test that two communicating state machines can work together. """ + server_config = h2.config.H2Configuration(client_side=False) request_headers = [ - (':method', 'GET'), - (':path', '/'), - (':authority', 'example.com'), - (':scheme', 'https'), - ('user-agent', 'test-client/0.1.0'), + (":method", "GET"), + (":path", "/"), + (":authority", "example.com"), + (":scheme", "https"), + ("user-agent", "test-client/0.1.0"), ] request_headers_bytes = [ - (b':method', b'GET'), - (b':path', b'/'), - (b':authority', b'example.com'), - (b':scheme', b'https'), - (b'user-agent', b'test-client/0.1.0'), + (b":method", b"GET"), + (b":path", b"/"), + (b":authority", b"example.com"), + (b":scheme", b"https"), + (b"user-agent", b"test-client/0.1.0"), ] response_headers = [ - (b':status', b'204'), - (b'server', b'test-server/0.1.0'), - (b'content-length', b'0'), + (b":status", b"204"), + (b"server", b"test-server/0.1.0"), + (b"content-length", b"0"), ] - @pytest.mark.parametrize('request_headers', [request_headers, request_headers_bytes]) - def test_basic_request_response(self, request_headers): + @pytest.mark.parametrize("request_headers", [request_headers, request_headers_bytes]) + def test_basic_request_response(self, request_headers) -> None: """ A request issued by hyper-h2 can be responded to by hyper-h2. """ diff --git a/tests/test_invalid_content_lengths.py b/tests/test_invalid_content_lengths.py index f2ceefc67..39401ea2b 100644 --- a/tests/test_invalid_content_lengths.py +++ b/tests/test_invalid_content_lengths.py @@ -1,10 +1,9 @@ """ -test_invalid_content_lengths.py -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - This module contains tests that use invalid content lengths, and validates that they fail appropriately. """ +from __future__ import annotations + import pytest import h2.config @@ -14,33 +13,34 @@ import h2.exceptions -class TestInvalidContentLengths(object): +class TestInvalidContentLengths: """ Hyper-h2 raises Protocol Errors when the content-length sent by a remote peer is not valid. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'POST'), - ('content-length', '15'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "POST"), + ("content-length", "15"), ] example_request_headers_bytes = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'POST'), - (b'content-length', b'15'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"POST"), + (b"content-length", b"15"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_too_much_data(self, frame_factory, request_headers): + def test_too_much_data(self, frame_factory, request_headers) -> None: """ Remote peers sending data in excess of content-length causes Protocol Errors. @@ -50,13 +50,13 @@ def test_too_much_data(self, frame_factory, request_headers): c.receive_data(frame_factory.preamble()) headers = frame_factory.build_headers_frame( - headers=request_headers + headers=request_headers, ) - first_data = frame_factory.build_data_frame(data=b'\x01'*15) + first_data = frame_factory.build_data_frame(data=b"\x01"*15) c.receive_data(headers.serialize() + first_data.serialize()) c.clear_outbound_data_buffer() - second_data = frame_factory.build_data_frame(data=b'\x01') + second_data = frame_factory.build_data_frame(data=b"\x01") with pytest.raises(h2.exceptions.InvalidBodyLengthError) as exp: c.receive_data(second_data.serialize()) @@ -73,7 +73,7 @@ def test_too_much_data(self, frame_factory, request_headers): assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_insufficient_data(self, frame_factory, request_headers): + def test_insufficient_data(self, frame_factory, request_headers) -> None: """ Remote peers sending less data than content-length causes Protocol Errors. @@ -83,15 +83,15 @@ def test_insufficient_data(self, frame_factory, request_headers): c.receive_data(frame_factory.preamble()) headers = frame_factory.build_headers_frame( - headers=request_headers + headers=request_headers, ) - first_data = frame_factory.build_data_frame(data=b'\x01'*13) + first_data = frame_factory.build_data_frame(data=b"\x01"*13) c.receive_data(headers.serialize() + first_data.serialize()) c.clear_outbound_data_buffer() second_data = frame_factory.build_data_frame( - data=b'\x01', - flags=['END_STREAM'], + data=b"\x01", + flags=["END_STREAM"], ) with pytest.raises(h2.exceptions.InvalidBodyLengthError) as exp: c.receive_data(second_data.serialize()) @@ -109,7 +109,7 @@ def test_insufficient_data(self, frame_factory, request_headers): assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_insufficient_data_empty_frame(self, frame_factory, request_headers): + def test_insufficient_data_empty_frame(self, frame_factory, request_headers) -> None: """ Remote peers sending less data than content-length where the last data frame is empty causes Protocol Errors. @@ -119,15 +119,15 @@ def test_insufficient_data_empty_frame(self, frame_factory, request_headers): c.receive_data(frame_factory.preamble()) headers = frame_factory.build_headers_frame( - headers=request_headers + headers=request_headers, ) - first_data = frame_factory.build_data_frame(data=b'\x01'*14) + first_data = frame_factory.build_data_frame(data=b"\x01"*14) c.receive_data(headers.serialize() + first_data.serialize()) c.clear_outbound_data_buffer() second_data = frame_factory.build_data_frame( - data=b'', - flags=['END_STREAM'], + data=b"", + flags=["END_STREAM"], ) with pytest.raises(h2.exceptions.InvalidBodyLengthError) as exp: c.receive_data(second_data.serialize()) diff --git a/tests/test_invalid_frame_sequences.py b/tests/test_invalid_frame_sequences.py index c18a1f555..f78e9f14c 100644 --- a/tests/test_invalid_frame_sequences.py +++ b/tests/test_invalid_frame_sequences.py @@ -1,10 +1,8 @@ """ -test_invalid_frame_sequences.py -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -This module contains tests that use invalid frame sequences, and validates that -they fail appropriately. +Tests invalid frame sequences, and validates that they fail appropriately. """ +from __future__ import annotations + import pytest import h2.config @@ -14,32 +12,33 @@ import h2.exceptions -class TestInvalidFrameSequences(object): +class TestInvalidFrameSequences: """ Invalid frame sequences, either sent or received, cause ProtocolErrors to be thrown. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_request_headers_bytes = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) client_config = h2.config.H2Configuration(client_side=True) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_cannot_send_on_closed_stream(self, request_headers): + def test_cannot_send_on_closed_stream(self, request_headers) -> None: """ When we've closed a stream locally, we cannot send further data. """ @@ -48,23 +47,23 @@ def test_cannot_send_on_closed_stream(self, request_headers): c.send_headers(1, request_headers, end_stream=True) with pytest.raises(h2.exceptions.ProtocolError): - c.send_data(1, b'some data') + c.send_data(1, b"some data") - def test_missing_preamble_errors(self): + def test_missing_preamble_errors(self) -> None: """ Server side connections require the preamble. """ c = h2.connection.H2Connection(config=self.server_config) encoded_headers_frame = ( - b'\x00\x00\r\x01\x04\x00\x00\x00\x01' - b'A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x87\x82' + b"\x00\x00\r\x01\x04\x00\x00\x00\x01" + b"A\x88/\x91\xd3]\x05\\\x87\xa7\x84\x87\x82" ) with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(encoded_headers_frame) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_server_connections_reject_even_streams(self, frame_factory, request_headers): + def test_server_connections_reject_even_streams(self, frame_factory, request_headers) -> None: """ Servers do not allow clients to initiate even-numbered streams. """ @@ -73,14 +72,14 @@ def test_server_connections_reject_even_streams(self, frame_factory, request_hea c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - request_headers, stream_id=2 + request_headers, stream_id=2, ) with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(f.serialize()) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_clients_reject_odd_stream_pushes(self, frame_factory, request_headers): + def test_clients_reject_odd_stream_pushes(self, frame_factory, request_headers) -> None: """ Clients do not allow servers to push odd numbered streams. """ @@ -91,14 +90,14 @@ def test_clients_reject_odd_stream_pushes(self, frame_factory, request_headers): f = frame_factory.build_push_promise_frame( stream_id=1, headers=request_headers, - promised_stream_id=3 + promised_stream_id=3, ) with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(f.serialize()) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_can_handle_frames_with_invalid_padding(self, frame_factory, request_headers): + def test_can_handle_frames_with_invalid_padding(self, frame_factory, request_headers) -> None: """ Frames with invalid padding cause connection teardown. """ @@ -111,18 +110,18 @@ def test_can_handle_frames_with_invalid_padding(self, frame_factory, request_hea c.clear_outbound_data_buffer() invalid_data_frame = ( - b'\x00\x00\x05\x00\x0b\x00\x00\x00\x01\x06\x54\x65\x73\x74' + b"\x00\x00\x05\x00\x0b\x00\x00\x00\x01\x06\x54\x65\x73\x74" ) with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(invalid_data_frame) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=1, error_code=1 + last_stream_id=1, error_code=1, ) assert c.data_to_send() == expected_frame.serialize() - def test_receiving_frames_with_insufficent_size(self, frame_factory): + def test_receiving_frames_with_insufficent_size(self, frame_factory) -> None: """ Frames with not enough data cause connection teardown. """ @@ -132,19 +131,19 @@ def test_receiving_frames_with_insufficent_size(self, frame_factory): c.clear_outbound_data_buffer() invalid_window_update_frame = ( - b'\x00\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x02' + b"\x00\x00\x03\x08\x00\x00\x00\x00\x00\x00\x00\x02" ) with pytest.raises(h2.exceptions.FrameDataMissingError): c.receive_data(invalid_window_update_frame) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=0, error_code=h2.errors.ErrorCodes.FRAME_SIZE_ERROR + last_stream_id=0, error_code=h2.errors.ErrorCodes.FRAME_SIZE_ERROR, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_reject_data_on_closed_streams(self, frame_factory, request_headers): + def test_reject_data_on_closed_streams(self, frame_factory, request_headers) -> None: """ When a stream is not open to the remote peer, we reject receiving data frames from them. @@ -155,13 +154,13 @@ def test_reject_data_on_closed_streams(self, frame_factory, request_headers): f = frame_factory.build_headers_frame( request_headers, - flags=['END_STREAM'] + flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() bad_frame = frame_factory.build_data_frame( - data=b'some data' + data=b"some data", ) c.receive_data(bad_frame.serialize()) @@ -172,7 +171,7 @@ def test_reject_data_on_closed_streams(self, frame_factory, request_headers): assert c.data_to_send() == expected @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_unexpected_continuation_on_closed_stream(self, frame_factory, request_headers): + def test_unexpected_continuation_on_closed_stream(self, frame_factory, request_headers) -> None: """ CONTINUATION frames received on closed streams cause connection errors of type PROTOCOL_ERROR. @@ -183,13 +182,13 @@ def test_unexpected_continuation_on_closed_stream(self, frame_factory, request_h f = frame_factory.build_headers_frame( request_headers, - flags=['END_STREAM'] + flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() bad_frame = frame_factory.build_continuation_frame( - header_block=b'hello' + header_block=b"hello", ) with pytest.raises(h2.exceptions.ProtocolError): @@ -197,12 +196,12 @@ def test_unexpected_continuation_on_closed_stream(self, frame_factory, request_h expected_frame = frame_factory.build_goaway_frame( error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, - last_stream_id=1 + last_stream_id=1, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_prevent_continuation_dos(self, frame_factory, request_headers): + def test_prevent_continuation_dos(self, frame_factory, request_headers) -> None: """ Receiving too many CONTINUATION frames in one block causes a protocol error. @@ -214,20 +213,20 @@ def test_prevent_continuation_dos(self, frame_factory, request_headers): f = frame_factory.build_headers_frame( request_headers, ) - f.flags = {'END_STREAM'} + f.flags = {"END_STREAM"} c.receive_data(f.serialize()) c.clear_outbound_data_buffer() # Send 63 additional frames. - for _ in range(0, 63): + for _ in range(63): extra_frame = frame_factory.build_continuation_frame( - header_block=b'hello' + header_block=b"hello", ) c.receive_data(extra_frame.serialize()) # The final continuation frame should cause a protocol error. extra_frame = frame_factory.build_continuation_frame( - header_block=b'hello' + header_block=b"hello", ) with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(extra_frame.serialize()) @@ -246,9 +245,9 @@ def test_prevent_continuation_dos(self, frame_factory, request_headers): {0x4: 2**31}, {0x5: 5}, {0x5: 2**24}, - ] + ], ) - def test_reject_invalid_settings_values(self, frame_factory, settings): + def test_reject_invalid_settings_values(self, frame_factory, settings) -> None: """ When a SETTINGS frame is received with invalid settings values it causes connection teardown with the appropriate error code. @@ -268,7 +267,7 @@ def test_reject_invalid_settings_values(self, frame_factory, settings): ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_invalid_frame_headers_are_protocol_errors(self, frame_factory, request_headers): + def test_invalid_frame_headers_are_protocol_errors(self, frame_factory, request_headers) -> None: """ When invalid frame headers are received they cause ProtocolErrors to be raised. @@ -278,7 +277,7 @@ def test_invalid_frame_headers_are_protocol_errors(self, frame_factory, request_ c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - headers=request_headers + headers=request_headers, ) # Do some annoying bit twiddling here: the stream ID is currently set @@ -286,7 +285,7 @@ def test_invalid_frame_headers_are_protocol_errors(self, frame_factory, request_ # replace any instances of the byte '\x01', and then graft it onto the # remaining bytes. frame_data = f.serialize() - frame_data = frame_data[:9].replace(b'\x01', b'\x00') + frame_data[9:] + frame_data = frame_data[:9].replace(b"\x01", b"\x00") + frame_data[9:] with pytest.raises(h2.exceptions.ProtocolError) as e: c.receive_data(frame_data) @@ -294,7 +293,7 @@ def test_invalid_frame_headers_are_protocol_errors(self, frame_factory, request_ assert "Received frame with invalid header" in str(e.value) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_data_before_headers(self, frame_factory, request_headers): + def test_data_before_headers(self, frame_factory, request_headers) -> None: """ When data frames are received before headers they cause ProtocolErrors to be raised. @@ -312,7 +311,7 @@ def test_data_before_headers(self, frame_factory, request_headers): assert "cannot receive data before headers" in str(e.value) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_get_stream_reset_event_on_auto_reset(self, frame_factory, request_headers): + def test_get_stream_reset_event_on_auto_reset(self, frame_factory, request_headers) -> None: """ When hyper-h2 resets a stream automatically, a StreamReset event fires. """ @@ -322,13 +321,13 @@ def test_get_stream_reset_event_on_auto_reset(self, frame_factory, request_heade f = frame_factory.build_headers_frame( request_headers, - flags=['END_STREAM'] + flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() bad_frame = frame_factory.build_data_frame( - data=b'some data' + data=b"some data", ) events = c.receive_data(bad_frame.serialize()) @@ -346,7 +345,7 @@ def test_get_stream_reset_event_on_auto_reset(self, frame_factory, request_heade assert not event.remote_reset @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_one_one_stream_reset(self, frame_factory, request_headers): + def test_one_one_stream_reset(self, frame_factory, request_headers) -> None: """ When hyper-h2 resets a stream automatically, a StreamReset event fires, but only for the first reset: the others are silent. @@ -357,13 +356,13 @@ def test_one_one_stream_reset(self, frame_factory, request_headers): f = frame_factory.build_headers_frame( request_headers, - flags=['END_STREAM'] + flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() bad_frame = frame_factory.build_data_frame( - data=b'some data' + data=b"some data", ) # Receive 5 frames. events = c.receive_data(bad_frame.serialize() * 5) @@ -382,8 +381,8 @@ def test_one_one_stream_reset(self, frame_factory, request_headers): assert not event.remote_reset @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - @pytest.mark.parametrize('value', ['', 'twelve']) - def test_error_on_invalid_content_length(self, frame_factory, value, request_headers): + @pytest.mark.parametrize("value", ["", "twelve"]) + def test_error_on_invalid_content_length(self, frame_factory, value, request_headers) -> None: """ When an invalid content-length is received, a ProtocolError is thrown. """ @@ -394,19 +393,19 @@ def test_error_on_invalid_content_length(self, frame_factory, value, request_hea f = frame_factory.build_headers_frame( stream_id=1, - headers=request_headers + [('content-length', value)] + headers=[*request_headers, ("content-length", value)], ) with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(f.serialize()) expected_frame = frame_factory.build_goaway_frame( last_stream_id=1, - error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_invalid_header_data_protocol_error(self, frame_factory, request_headers): + def test_invalid_header_data_protocol_error(self, frame_factory, request_headers) -> None: """ If an invalid header block is received, we raise a ProtocolError. """ @@ -417,21 +416,21 @@ def test_invalid_header_data_protocol_error(self, frame_factory, request_headers f = frame_factory.build_headers_frame( stream_id=1, - headers=request_headers + headers=request_headers, ) - f.data = b'\x00\x00\x00\x00' + f.data = b"\x00\x00\x00\x00" with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(f.serialize()) expected_frame = frame_factory.build_goaway_frame( last_stream_id=0, - error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_invalid_push_promise_data_protocol_error(self, frame_factory, request_headers): + def test_invalid_push_promise_data_protocol_error(self, frame_factory, request_headers) -> None: """ If an invalid header block is received on a PUSH_PROMISE, we raise a ProtocolError. @@ -444,21 +443,21 @@ def test_invalid_push_promise_data_protocol_error(self, frame_factory, request_h f = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, - headers=request_headers + headers=request_headers, ) - f.data = b'\x00\x00\x00\x00' + f.data = b"\x00\x00\x00\x00" with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(f.serialize()) expected_frame = frame_factory.build_goaway_frame( last_stream_id=0, - error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_cannot_receive_push_on_pushed_stream(self, frame_factory, request_headers): + def test_cannot_receive_push_on_pushed_stream(self, frame_factory, request_headers) -> None: """ If a PUSH_PROMISE frame is received with the parent stream ID being a pushed stream, this is rejected with a PROTOCOL_ERROR. @@ -468,7 +467,7 @@ def test_cannot_receive_push_on_pushed_stream(self, frame_factory, request_heade c.send_headers( stream_id=1, headers=request_headers, - end_stream=True + end_stream=True, ) f1 = frame_factory.build_push_promise_frame( @@ -494,12 +493,12 @@ def test_cannot_receive_push_on_pushed_stream(self, frame_factory, request_heade expected_frame = frame_factory.build_goaway_frame( last_stream_id=2, - error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_cannot_send_push_on_pushed_stream(self, frame_factory, request_headers): + def test_cannot_send_push_on_pushed_stream(self, frame_factory, request_headers) -> None: """ If a user tries to send a PUSH_PROMISE frame with the parent stream ID being a pushed stream, this is rejected with a PROTOCOL_ERROR. @@ -508,14 +507,14 @@ def test_cannot_send_push_on_pushed_stream(self, frame_factory, request_headers) c.initiate_connection() c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - stream_id=1, headers=request_headers + stream_id=1, headers=request_headers, ) c.receive_data(f.serialize()) c.push_stream( stream_id=1, promised_stream_id=2, - request_headers=request_headers + request_headers=request_headers, ) c.send_headers(stream_id=2, headers=self.example_response_headers) @@ -523,5 +522,5 @@ def test_cannot_send_push_on_pushed_stream(self, frame_factory, request_headers) c.push_stream( stream_id=2, promised_stream_id=4, - request_headers=request_headers + request_headers=request_headers, ) diff --git a/tests/test_invalid_headers.py b/tests/test_invalid_headers.py index e585fae86..192ba10d4 100644 --- a/tests/test_invalid_headers.py +++ b/tests/test_invalid_headers.py @@ -1,13 +1,14 @@ """ -test_invalid_headers.py -~~~~~~~~~~~~~~~~~~~~~~~ - -This module contains tests that use invalid header blocks, and validates that -they fail appropriately. +Tests invalid header blocks, and validates that they fail appropriately. """ +from __future__ import annotations + import itertools +import hyperframe.frame import pytest +from hypothesis import given +from hypothesis.strategies import binary, lists, tuples import h2.config import h2.connection @@ -17,53 +18,49 @@ import h2.settings import h2.utilities -import hyperframe.frame - -from hypothesis import given -from hypothesis.strategies import binary, lists, tuples - HEADERS_STRATEGY = lists(tuples(binary(min_size=1), binary())) -class TestInvalidFrameSequences(object): +class TestInvalidFrameSequences: """ Invalid header sequences cause ProtocolErrors to be thrown when received. """ + base_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), - ('user-agent', 'someua/0.0.1'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ("user-agent", "someua/0.0.1"), ] base_invalid_header_blocks = [ - base_request_headers + [('Uppercase', 'name')], - base_request_headers + [(':late', 'pseudo-header')], - [(':path', 'duplicate-pseudo-header')] + base_request_headers, - base_request_headers + [('connection', 'close')], - base_request_headers + [('proxy-connection', 'close')], - base_request_headers + [('keep-alive', 'close')], - base_request_headers + [('transfer-encoding', 'gzip')], - base_request_headers + [('upgrade', 'super-protocol/1.1')], - base_request_headers + [('te', 'chunked')], - base_request_headers + [('host', 'notexample.com')], - base_request_headers + [(' name', 'name with leading space')], - base_request_headers + [('name ', 'name with trailing space')], - base_request_headers + [('name', ' value with leading space')], - base_request_headers + [('name', 'value with trailing space ')], + [*base_request_headers, ("Uppercase", "name")], + [*base_request_headers, (":late", "pseudo-header")], + [(":path", "duplicate-pseudo-header"), *base_request_headers], + [*base_request_headers, ("connection", "close")], + [*base_request_headers, ("proxy-connection", "close")], + [*base_request_headers, ("keep-alive", "close")], + [*base_request_headers, ("transfer-encoding", "gzip")], + [*base_request_headers, ("upgrade", "super-protocol/1.1")], + [*base_request_headers, ("te", "chunked")], + [*base_request_headers, ("host", "notexample.com")], + [*base_request_headers, (" name", "name with leading space")], + [*base_request_headers, ("name ", "name with trailing space")], + [*base_request_headers, ("name", " value with leading space")], + [*base_request_headers, ("name", "value with trailing space ")], [header for header in base_request_headers - if header[0] != ':authority'], - [(':protocol', 'websocket')] + base_request_headers, + if header[0] != ":authority"], + [(":protocol", "websocket"), *base_request_headers], ] invalid_header_blocks = base_invalid_header_blocks + [ h2.utilities.utf8_encode_headers(headers) for headers in base_invalid_header_blocks ] server_config = h2.config.H2Configuration( - client_side=False, header_encoding='utf-8' + client_side=False, header_encoding="utf-8", ) - @pytest.mark.parametrize('headers', invalid_header_blocks) - def test_headers_event(self, frame_factory, headers): + @pytest.mark.parametrize("headers", invalid_header_blocks) + def test_headers_event(self, frame_factory, headers) -> None: """ Test invalid headers are rejected with PROTOCOL_ERROR. """ @@ -78,12 +75,12 @@ def test_headers_event(self, frame_factory, headers): c.receive_data(data) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() - @pytest.mark.parametrize('headers', invalid_header_blocks) - def test_push_promise_event(self, frame_factory, headers): + @pytest.mark.parametrize("headers", invalid_header_blocks) + def test_push_promise_event(self, frame_factory, headers) -> None: """ If a PUSH_PROMISE header frame is received with an invalid header block it is rejected with a PROTOCOL_ERROR. @@ -91,14 +88,14 @@ def test_push_promise_event(self, frame_factory, headers): c = h2.connection.H2Connection() c.initiate_connection() c.send_headers( - stream_id=1, headers=self.base_request_headers, end_stream=True + stream_id=1, headers=self.base_request_headers, end_stream=True, ) c.clear_outbound_data_buffer() f = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, - headers=headers + headers=headers, ) data = f.serialize() @@ -106,12 +103,12 @@ def test_push_promise_event(self, frame_factory, headers): c.receive_data(data) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=0, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + last_stream_id=0, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() - @pytest.mark.parametrize('headers', invalid_header_blocks) - def test_push_promise_skipping_validation(self, frame_factory, headers): + @pytest.mark.parametrize("headers", invalid_header_blocks) + def test_push_promise_skipping_validation(self, frame_factory, headers) -> None: """ If we have ``validate_inbound_headers`` disabled, then invalid header blocks in push promise frames are allowed to pass. @@ -124,14 +121,14 @@ def test_push_promise_skipping_validation(self, frame_factory, headers): c = h2.connection.H2Connection(config=config) c.initiate_connection() c.send_headers( - stream_id=1, headers=self.base_request_headers, end_stream=True + stream_id=1, headers=self.base_request_headers, end_stream=True, ) c.clear_outbound_data_buffer() f = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, - headers=headers + headers=headers, ) data = f.serialize() @@ -140,8 +137,8 @@ def test_push_promise_skipping_validation(self, frame_factory, headers): pp_event = events[0] assert pp_event.headers == h2.utilities.utf8_encode_headers(headers) - @pytest.mark.parametrize('headers', invalid_header_blocks) - def test_headers_event_skipping_validation(self, frame_factory, headers): + @pytest.mark.parametrize("headers", invalid_header_blocks) + def test_headers_event_skipping_validation(self, frame_factory, headers) -> None: """ If we have ``validate_inbound_headers`` disabled, then all of these invalid header blocks are allowed to pass. @@ -162,12 +159,12 @@ def test_headers_event_skipping_validation(self, frame_factory, headers): request_event = events[0] assert request_event.headers == h2.utilities.utf8_encode_headers(headers) - def test_te_trailers_is_valid(self, frame_factory): + def test_te_trailers_is_valid(self, frame_factory) -> None: """ `te: trailers` is allowed by the filter. """ headers = ( - self.base_request_headers + [('te', 'trailers')] + [*self.base_request_headers, ("te", "trailers")] ) c = h2.connection.H2Connection(config=self.server_config) @@ -181,21 +178,21 @@ def test_te_trailers_is_valid(self, frame_factory): request_event = events[0] assert request_event.headers == headers - def test_pseudo_headers_rejected_in_trailer(self, frame_factory): + def test_pseudo_headers_rejected_in_trailer(self, frame_factory) -> None: """ Ensure we reject pseudo headers included in trailers """ - trailers = [(':path', '/'), ('extra', 'value')] + trailers = [(":path", "/"), ("extra", "value")] c = h2.connection.H2Connection(config=self.server_config) c.receive_data(frame_factory.preamble()) c.clear_outbound_data_buffer() header_frame = frame_factory.build_headers_frame( - self.base_request_headers + self.base_request_headers, ) trailer_frame = frame_factory.build_headers_frame( - trailers, flags=["END_STREAM"] + trailers, flags=["END_STREAM"], ) head = header_frame.serialize() trailer = trailer_frame.serialize() @@ -208,44 +205,45 @@ def test_pseudo_headers_rejected_in_trailer(self, frame_factory): # Test appropriate response frame is generated expected_frame = frame_factory.build_goaway_frame( - last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() -class TestSendingInvalidFrameSequences(object): +class TestSendingInvalidFrameSequences: """ Trying to send invalid header sequences cause ProtocolErrors to be thrown. """ + base_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), - ('user-agent', 'someua/0.0.1'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), + ("user-agent", "someua/0.0.1"), ] invalid_header_blocks = [ - base_request_headers + [(':late', 'pseudo-header')], - [(':path', 'duplicate-pseudo-header')] + base_request_headers, - base_request_headers + [('te', 'chunked')], - base_request_headers + [('host', 'notexample.com')], + [*base_request_headers, (":late", "pseudo-header")], + [(":path", "duplicate-pseudo-header"), *base_request_headers], + [*base_request_headers, ("te", "chunked")], + [*base_request_headers, ("host", "notexample.com")], [header for header in base_request_headers - if header[0] != ':authority'], + if header[0] != ":authority"], ] strippable_header_blocks = [ - base_request_headers + [('connection', 'close')], - base_request_headers + [('proxy-connection', 'close')], - base_request_headers + [('keep-alive', 'close')], - base_request_headers + [('transfer-encoding', 'gzip')], - base_request_headers + [('upgrade', 'super-protocol/1.1')] + [*base_request_headers, ("connection", "close")], + [*base_request_headers, ("proxy-connection", "close")], + [*base_request_headers, ("keep-alive", "close")], + [*base_request_headers, ("transfer-encoding", "gzip")], + [*base_request_headers, ("upgrade", "super-protocol/1.1")], ] all_header_blocks = invalid_header_blocks + strippable_header_blocks server_config = h2.config.H2Configuration(client_side=False) - @pytest.mark.parametrize('headers', invalid_header_blocks) - def test_headers_event(self, frame_factory, headers): + @pytest.mark.parametrize("headers", invalid_header_blocks) + def test_headers_event(self, frame_factory, headers) -> None: """ Test sending invalid headers raise a ProtocolError. """ @@ -257,8 +255,8 @@ def test_headers_event(self, frame_factory, headers): with pytest.raises(h2.exceptions.ProtocolError): c.send_headers(1, headers) - @pytest.mark.parametrize('headers', invalid_header_blocks) - def test_send_push_promise(self, frame_factory, headers): + @pytest.mark.parametrize("headers", invalid_header_blocks) + def test_send_push_promise(self, frame_factory, headers) -> None: """ Sending invalid headers in a push promise raises a ProtocolError. """ @@ -267,7 +265,7 @@ def test_send_push_promise(self, frame_factory, headers): c.receive_data(frame_factory.preamble()) header_frame = frame_factory.build_headers_frame( - self.base_request_headers + self.base_request_headers, ) c.receive_data(header_frame.serialize()) @@ -275,17 +273,17 @@ def test_send_push_promise(self, frame_factory, headers): c.clear_outbound_data_buffer() with pytest.raises(h2.exceptions.ProtocolError): c.push_stream( - stream_id=1, promised_stream_id=2, request_headers=headers + stream_id=1, promised_stream_id=2, request_headers=headers, ) - @pytest.mark.parametrize('headers', all_header_blocks) - def test_headers_event_skipping_validation(self, frame_factory, headers): + @pytest.mark.parametrize("headers", all_header_blocks) + def test_headers_event_skipping_validation(self, frame_factory, headers) -> None: """ If we have ``validate_outbound_headers`` disabled, then all of these invalid header blocks are allowed to pass. """ config = h2.config.H2Configuration( - validate_outbound_headers=False + validate_outbound_headers=False, ) c = h2.connection.H2Connection(config=config) @@ -298,13 +296,13 @@ def test_headers_event_skipping_validation(self, frame_factory, headers): # Ensure headers are still normalized. headers = h2.utilities.utf8_encode_headers(headers) norm_headers = h2.utilities.normalize_outbound_headers( - headers, None, False + headers, None, False, ) f = frame_factory.build_headers_frame(norm_headers) assert c.data_to_send() == f.serialize() - @pytest.mark.parametrize('headers', all_header_blocks) - def test_push_promise_skipping_validation(self, frame_factory, headers): + @pytest.mark.parametrize("headers", all_header_blocks) + def test_push_promise_skipping_validation(self, frame_factory, headers) -> None: """ If we have ``validate_outbound_headers`` disabled, then all of these invalid header blocks are allowed to pass. @@ -319,7 +317,7 @@ def test_push_promise_skipping_validation(self, frame_factory, headers): c.receive_data(frame_factory.preamble()) header_frame = frame_factory.build_headers_frame( - self.base_request_headers + self.base_request_headers, ) c.receive_data(header_frame.serialize()) @@ -328,28 +326,28 @@ def test_push_promise_skipping_validation(self, frame_factory, headers): # Create push promise frame with normalized headers. headers = h2.utilities.utf8_encode_headers(headers) norm_headers = h2.utilities.normalize_outbound_headers( - headers, None, False + headers, None, False, ) pp_frame = frame_factory.build_push_promise_frame( - stream_id=1, promised_stream_id=2, headers=norm_headers + stream_id=1, promised_stream_id=2, headers=norm_headers, ) # Clear the data, then send a push promise. c.clear_outbound_data_buffer() c.push_stream( - stream_id=1, promised_stream_id=2, request_headers=headers + stream_id=1, promised_stream_id=2, request_headers=headers, ) assert c.data_to_send() == pp_frame.serialize() - @pytest.mark.parametrize('headers', all_header_blocks) - def test_headers_event_skip_normalization(self, frame_factory, headers): + @pytest.mark.parametrize("headers", all_header_blocks) + def test_headers_event_skip_normalization(self, frame_factory, headers) -> None: """ If we have ``normalize_outbound_headers`` disabled, then all of these invalid header blocks are sent through unmodified. """ config = h2.config.H2Configuration( validate_outbound_headers=False, - normalize_outbound_headers=False + normalize_outbound_headers=False, ) c = h2.connection.H2Connection(config=config) @@ -365,8 +363,8 @@ def test_headers_event_skip_normalization(self, frame_factory, headers): c.send_headers(1, headers) assert c.data_to_send() == f.serialize() - @pytest.mark.parametrize('headers', all_header_blocks) - def test_push_promise_skip_normalization(self, frame_factory, headers): + @pytest.mark.parametrize("headers", all_header_blocks) + def test_push_promise_skip_normalization(self, frame_factory, headers) -> None: """ If we have ``normalize_outbound_headers`` disabled, then all of these invalid header blocks are allowed to pass unmodified. @@ -382,24 +380,24 @@ def test_push_promise_skip_normalization(self, frame_factory, headers): c.receive_data(frame_factory.preamble()) header_frame = frame_factory.build_headers_frame( - self.base_request_headers + self.base_request_headers, ) c.receive_data(header_frame.serialize()) frame_factory.refresh_encoder() pp_frame = frame_factory.build_push_promise_frame( - stream_id=1, promised_stream_id=2, headers=headers + stream_id=1, promised_stream_id=2, headers=headers, ) # Clear the data, then send a push promise. c.clear_outbound_data_buffer() c.push_stream( - stream_id=1, promised_stream_id=2, request_headers=headers + stream_id=1, promised_stream_id=2, request_headers=headers, ) assert c.data_to_send() == pp_frame.serialize() - @pytest.mark.parametrize('headers', strippable_header_blocks) - def test_strippable_headers(self, frame_factory, headers): + @pytest.mark.parametrize("headers", strippable_header_blocks) + def test_strippable_headers(self, frame_factory, headers) -> None: """ Test connection related headers are removed before sending. """ @@ -414,7 +412,7 @@ def test_strippable_headers(self, frame_factory, headers): assert c.data_to_send() == f.serialize() -class TestFilter(object): +class TestFilter: """ Test the filter function directly. @@ -423,14 +421,15 @@ class TestFilter(object): HTTP/2 and so may never hit the function, but it's worth validating that it behaves as expected anyway. """ + validation_functions = [ h2.utilities.validate_headers, - h2.utilities.validate_outbound_headers + h2.utilities.validate_outbound_headers, ] hdr_validation_combos = [ h2.utilities.HeaderValidationFlags( - is_client, is_trailer, is_response_header, is_push_promise + is_client, is_trailer, is_response_header, is_push_promise, ) for is_client, is_trailer, is_response_header, is_push_promise in ( itertools.product([True, False], repeat=4) @@ -450,42 +449,42 @@ class TestFilter(object): invalid_request_header_blocks_bytes = ( # First, missing :method ( - (b':authority', b'google.com'), - (b':path', b'/'), - (b':scheme', b'https'), + (b":authority", b"google.com"), + (b":path", b"/"), + (b":scheme", b"https"), ), # Next, missing :path ( - (b':authority', b'google.com'), - (b':method', b'GET'), - (b':scheme', b'https'), + (b":authority", b"google.com"), + (b":method", b"GET"), + (b":scheme", b"https"), ), # Next, missing :scheme ( - (b':authority', b'google.com'), - (b':method', b'GET'), - (b':path', b'/'), + (b":authority", b"google.com"), + (b":method", b"GET"), + (b":path", b"/"), ), # Finally, path present but empty. ( - (b':authority', b'google.com'), - (b':method', b'GET'), - (b':scheme', b'https'), - (b':path', b''), + (b":authority", b"google.com"), + (b":method", b"GET"), + (b":scheme", b"https"), + (b":path", b""), ), ) # All headers that are forbidden from either request or response blocks. - forbidden_request_headers_bytes = (b':status',) - forbidden_response_headers_bytes = (b':path', b':scheme', b':authority', b':method') + forbidden_request_headers_bytes = (b":status",) + forbidden_response_headers_bytes = (b":path", b":scheme", b":authority", b":method") - @pytest.mark.parametrize('validation_function', validation_functions) - @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos) + @pytest.mark.parametrize("validation_function", validation_functions) + @pytest.mark.parametrize("hdr_validation_flags", hdr_validation_combos) @given(headers=HEADERS_STRATEGY) def test_range_of_acceptable_outputs(self, headers, validation_function, - hdr_validation_flags): + hdr_validation_flags) -> None: """ The header validation functions either return the data unchanged or throw a ProtocolError. @@ -496,175 +495,175 @@ def test_range_of_acceptable_outputs(self, except h2.exceptions.ProtocolError: assert True - @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos) - def test_invalid_pseudo_headers(self, hdr_validation_flags): - headers = [(b':custom', b'value')] + @pytest.mark.parametrize("hdr_validation_flags", hdr_validation_combos) + def test_invalid_pseudo_headers(self, hdr_validation_flags) -> None: + headers = [(b":custom", b"value")] with pytest.raises(h2.exceptions.ProtocolError): list(h2.utilities.validate_headers(headers, hdr_validation_flags)) - @pytest.mark.parametrize('validation_function', validation_functions) + @pytest.mark.parametrize("validation_function", validation_functions) @pytest.mark.parametrize( - 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + "hdr_validation_flags", hdr_validation_request_headers_no_trailer, ) def test_matching_authority_host_headers(self, validation_function, - hdr_validation_flags): + hdr_validation_flags) -> None: """ If a header block has :authority and Host headers and they match, the headers should pass through unchanged. """ headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), - (b'host', b'example.com'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), + (b"host", b"example.com"), ] assert headers == list(h2.utilities.validate_headers( - headers, hdr_validation_flags + headers, hdr_validation_flags, )) @pytest.mark.parametrize( - 'hdr_validation_flags', hdr_validation_response_headers + "hdr_validation_flags", hdr_validation_response_headers, ) - def test_response_header_without_status(self, hdr_validation_flags): - headers = [(b'content-length', b'42')] + def test_response_header_without_status(self, hdr_validation_flags) -> None: + headers = [(b"content-length", b"42")] with pytest.raises(h2.exceptions.ProtocolError): list(h2.utilities.validate_headers(headers, hdr_validation_flags)) @pytest.mark.parametrize( - 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + "hdr_validation_flags", hdr_validation_request_headers_no_trailer, ) @pytest.mark.parametrize( - 'header_block', + "header_block", (invalid_request_header_blocks_bytes), ) def test_outbound_req_header_missing_pseudo_headers(self, hdr_validation_flags, - header_block): + header_block) -> None: with pytest.raises(h2.exceptions.ProtocolError): list( h2.utilities.validate_outbound_headers( - header_block, hdr_validation_flags - ) + header_block, hdr_validation_flags, + ), ) @pytest.mark.parametrize( - 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + "hdr_validation_flags", hdr_validation_request_headers_no_trailer, ) @pytest.mark.parametrize( - 'header_block', invalid_request_header_blocks_bytes + "header_block", invalid_request_header_blocks_bytes, ) def test_inbound_req_header_missing_pseudo_headers(self, hdr_validation_flags, - header_block): + header_block) -> None: with pytest.raises(h2.exceptions.ProtocolError): list( h2.utilities.validate_headers( - header_block, hdr_validation_flags - ) + header_block, hdr_validation_flags, + ), ) @pytest.mark.parametrize( - 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + "hdr_validation_flags", hdr_validation_request_headers_no_trailer, ) @pytest.mark.parametrize( - 'invalid_header', + "invalid_header", forbidden_request_headers_bytes, ) def test_outbound_req_header_extra_pseudo_headers(self, hdr_validation_flags, - invalid_header): + invalid_header) -> None: """ Outbound request header blocks containing the forbidden request headers fail validation. """ headers = [ - (b':path', b'/'), - (b':scheme', b'https'), - (b':authority', b'google.com'), - (b':method', b'GET'), + (b":path", b"/"), + (b":scheme", b"https"), + (b":authority", b"google.com"), + (b":method", b"GET"), ] - headers.append((invalid_header, b'some value')) + headers.append((invalid_header, b"some value")) with pytest.raises(h2.exceptions.ProtocolError): list( h2.utilities.validate_outbound_headers( - headers, hdr_validation_flags - ) + headers, hdr_validation_flags, + ), ) @pytest.mark.parametrize( - 'hdr_validation_flags', hdr_validation_request_headers_no_trailer + "hdr_validation_flags", hdr_validation_request_headers_no_trailer, ) @pytest.mark.parametrize( - 'invalid_header', - forbidden_request_headers_bytes + "invalid_header", + forbidden_request_headers_bytes, ) def test_inbound_req_header_extra_pseudo_headers(self, hdr_validation_flags, - invalid_header): + invalid_header) -> None: """ Inbound request header blocks containing the forbidden request headers fail validation. """ headers = [ - (b':path', b'/'), - (b':scheme', b'https'), - (b':authority', b'google.com'), - (b':method', b'GET'), + (b":path", b"/"), + (b":scheme", b"https"), + (b":authority", b"google.com"), + (b":method", b"GET"), ] - headers.append((invalid_header, b'some value')) + headers.append((invalid_header, b"some value")) with pytest.raises(h2.exceptions.ProtocolError): list(h2.utilities.validate_headers(headers, hdr_validation_flags)) @pytest.mark.parametrize( - 'hdr_validation_flags', hdr_validation_response_headers + "hdr_validation_flags", hdr_validation_response_headers, ) @pytest.mark.parametrize( - 'invalid_header', + "invalid_header", forbidden_response_headers_bytes, ) def test_outbound_resp_header_extra_pseudo_headers(self, hdr_validation_flags, - invalid_header): + invalid_header) -> None: """ Outbound response header blocks containing the forbidden response headers fail validation. """ - headers = [(b':status', b'200')] - headers.append((invalid_header, b'some value')) + headers = [(b":status", b"200")] + headers.append((invalid_header, b"some value")) with pytest.raises(h2.exceptions.ProtocolError): list( h2.utilities.validate_outbound_headers( - headers, hdr_validation_flags - ) + headers, hdr_validation_flags, + ), ) @pytest.mark.parametrize( - 'hdr_validation_flags', hdr_validation_response_headers + "hdr_validation_flags", hdr_validation_response_headers, ) @pytest.mark.parametrize( - 'invalid_header', - forbidden_response_headers_bytes + "invalid_header", + forbidden_response_headers_bytes, ) def test_inbound_resp_header_extra_pseudo_headers(self, hdr_validation_flags, - invalid_header): + invalid_header) -> None: """ Inbound response header blocks containing the forbidden response headers fail validation. """ - headers = [(b':status', b'200')] - headers.append((invalid_header, b'some value')) + headers = [(b":status", b"200")] + headers.append((invalid_header, b"some value")) with pytest.raises(h2.exceptions.ProtocolError): list(h2.utilities.validate_headers(headers, hdr_validation_flags)) - @pytest.mark.parametrize('hdr_validation_flags', hdr_validation_combos) - def test_inbound_header_name_length(self, hdr_validation_flags): + @pytest.mark.parametrize("hdr_validation_flags", hdr_validation_combos) + def test_inbound_header_name_length(self, hdr_validation_flags) -> None: with pytest.raises(h2.exceptions.ProtocolError): - list(h2.utilities.validate_headers([(b'', b'foobar')], hdr_validation_flags)) + list(h2.utilities.validate_headers([(b"", b"foobar")], hdr_validation_flags)) - def test_inbound_header_name_length_full_frame_decode(self, frame_factory): + def test_inbound_header_name_length_full_frame_decode(self, frame_factory) -> None: f = frame_factory.build_headers_frame([]) f.data = b"\x00\x00\x05\x00\x00\x00\x00\x04" data = f.serialize() @@ -678,20 +677,21 @@ def test_inbound_header_name_length_full_frame_decode(self, frame_factory): c.receive_data(data) -class TestOversizedHeaders(object): +class TestOversizedHeaders: """ Tests that oversized header blocks are correctly rejected. This replicates the "HPACK Bomb" attack, and confirms that we're resistant against it. """ + request_header_block = [ - (b':method', b'GET'), - (b':authority', b'example.com'), - (b':scheme', b'https'), - (b':path', b'/'), + (b":method", b"GET"), + (b":authority", b"example.com"), + (b":scheme", b"https"), + (b":path", b"/"), ] response_header_block = [ - (b':status', b'200'), + (b":status", b"200"), ] # The first header block contains a single header that fills the header @@ -700,18 +700,18 @@ class TestOversizedHeaders(object): # table. It must come last, so that it evicts all other headers. # This block must be appended to either a request or response block. first_header_block = [ - (b'a', b'a' * 4063), + (b"a", b"a" * 4063), ] # The second header "block" is actually a custom HEADERS frame body that # simply repeatedly refers to the first entry for 16kB. Each byte has the # high bit set (0x80), and then uses the remaining 7 bits to encode the # number 62 (0x3e), leading to a repeat of the byte 0xbe. - second_header_block = b'\xbe' * 2**14 + second_header_block = b"\xbe" * 2**14 server_config = h2.config.H2Configuration(client_side=False) - def test_hpack_bomb_request(self, frame_factory): + def test_hpack_bomb_request(self, frame_factory) -> None: """ A HPACK bomb request causes the connection to be torn down with the error code ENHANCE_YOUR_CALM. @@ -721,7 +721,7 @@ def test_hpack_bomb_request(self, frame_factory): c.clear_outbound_data_buffer() f = frame_factory.build_headers_frame( - self.request_header_block + self.first_header_block + self.request_header_block + self.first_header_block, ) data = f.serialize() c.receive_data(data) @@ -729,18 +729,18 @@ def test_hpack_bomb_request(self, frame_factory): # Build the attack payload. attack_frame = hyperframe.frame.HeadersFrame(stream_id=3) attack_frame.data = self.second_header_block - attack_frame.flags.add('END_HEADERS') + attack_frame.flags.add("END_HEADERS") data = attack_frame.serialize() with pytest.raises(h2.exceptions.DenialOfServiceError): c.receive_data(data) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=1, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + last_stream_id=1, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM, ) assert c.data_to_send() == expected_frame.serialize() - def test_hpack_bomb_response(self, frame_factory): + def test_hpack_bomb_response(self, frame_factory) -> None: """ A HPACK bomb response causes the connection to be torn down with the error code ENHANCE_YOUR_CALM. @@ -748,15 +748,15 @@ def test_hpack_bomb_response(self, frame_factory): c = h2.connection.H2Connection() c.initiate_connection() c.send_headers( - stream_id=1, headers=self.request_header_block + stream_id=1, headers=self.request_header_block, ) c.send_headers( - stream_id=3, headers=self.request_header_block + stream_id=3, headers=self.request_header_block, ) c.clear_outbound_data_buffer() f = frame_factory.build_headers_frame( - self.response_header_block + self.first_header_block + self.response_header_block + self.first_header_block, ) data = f.serialize() c.receive_data(data) @@ -764,18 +764,18 @@ def test_hpack_bomb_response(self, frame_factory): # Build the attack payload. attack_frame = hyperframe.frame.HeadersFrame(stream_id=3) attack_frame.data = self.second_header_block - attack_frame.flags.add('END_HEADERS') + attack_frame.flags.add("END_HEADERS") data = attack_frame.serialize() with pytest.raises(h2.exceptions.DenialOfServiceError): c.receive_data(data) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM, ) assert c.data_to_send() == expected_frame.serialize() - def test_hpack_bomb_push(self, frame_factory): + def test_hpack_bomb_push(self, frame_factory) -> None: """ A HPACK bomb push causes the connection to be torn down with the error code ENHANCE_YOUR_CALM. @@ -783,12 +783,12 @@ def test_hpack_bomb_push(self, frame_factory): c = h2.connection.H2Connection() c.initiate_connection() c.send_headers( - stream_id=1, headers=self.request_header_block + stream_id=1, headers=self.request_header_block, ) c.clear_outbound_data_buffer() f = frame_factory.build_headers_frame( - self.response_header_block + self.first_header_block + self.response_header_block + self.first_header_block, ) data = f.serialize() c.receive_data(data) @@ -798,18 +798,18 @@ def test_hpack_bomb_push(self, frame_factory): attack_frame = hyperframe.frame.PushPromiseFrame(stream_id=3) attack_frame.promised_stream_id = 2 attack_frame.data = self.second_header_block[:-4] - attack_frame.flags.add('END_HEADERS') + attack_frame.flags.add("END_HEADERS") data = attack_frame.serialize() with pytest.raises(h2.exceptions.DenialOfServiceError): c.receive_data(data) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + last_stream_id=0, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM, ) assert c.data_to_send() == expected_frame.serialize() - def test_reject_headers_when_list_size_shrunk(self, frame_factory): + def test_reject_headers_when_list_size_shrunk(self, frame_factory) -> None: """ When we've shrunk the header list size, we reject new header blocks that violate the new size. @@ -821,7 +821,7 @@ def test_reject_headers_when_list_size_shrunk(self, frame_factory): # Receive the first request, which causes no problem. f = frame_factory.build_headers_frame( stream_id=1, - headers=self.request_header_block + headers=self.request_header_block, ) data = f.serialize() c.receive_data(data) @@ -832,7 +832,7 @@ def test_reject_headers_when_list_size_shrunk(self, frame_factory): c.clear_outbound_data_buffer() f = frame_factory.build_headers_frame( stream_id=3, - headers=self.request_header_block + headers=self.request_header_block, ) data = f.serialize() c.receive_data(data) @@ -845,7 +845,7 @@ def test_reject_headers_when_list_size_shrunk(self, frame_factory): # Now a third request comes in. This explodes. f = frame_factory.build_headers_frame( stream_id=5, - headers=self.request_header_block + headers=self.request_header_block, ) data = f.serialize() @@ -853,11 +853,11 @@ def test_reject_headers_when_list_size_shrunk(self, frame_factory): c.receive_data(data) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=3, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM + last_stream_id=3, error_code=h2.errors.ErrorCodes.ENHANCE_YOUR_CALM, ) assert c.data_to_send() == expected_frame.serialize() - def test_reject_headers_when_table_size_shrunk(self, frame_factory): + def test_reject_headers_when_table_size_shrunk(self, frame_factory) -> None: """ When we've shrunk the header table size, we reject header blocks that do not respect the change. @@ -869,7 +869,7 @@ def test_reject_headers_when_table_size_shrunk(self, frame_factory): # Receive the first request, which causes no problem. f = frame_factory.build_headers_frame( stream_id=1, - headers=self.request_header_block + headers=self.request_header_block, ) data = f.serialize() c.receive_data(data) @@ -880,7 +880,7 @@ def test_reject_headers_when_table_size_shrunk(self, frame_factory): c.clear_outbound_data_buffer() f = frame_factory.build_headers_frame( stream_id=3, - headers=self.request_header_block + headers=self.request_header_block, ) data = f.serialize() c.receive_data(data) @@ -894,7 +894,7 @@ def test_reject_headers_when_table_size_shrunk(self, frame_factory): # a dynamic table size update. f = frame_factory.build_headers_frame( stream_id=5, - headers=self.request_header_block + headers=self.request_header_block, ) data = f.serialize() @@ -902,11 +902,11 @@ def test_reject_headers_when_table_size_shrunk(self, frame_factory): c.receive_data(data) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=3, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + last_stream_id=3, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() - def test_reject_headers_exceeding_table_size(self, frame_factory): + def test_reject_headers_exceeding_table_size(self, frame_factory) -> None: """ When the remote peer sends a dynamic table size update that exceeds our setting, we reject it. @@ -918,7 +918,7 @@ def test_reject_headers_exceeding_table_size(self, frame_factory): # Receive the first request, which causes no problem. f = frame_factory.build_headers_frame( stream_id=1, - headers=self.request_header_block + headers=self.request_header_block, ) data = f.serialize() c.receive_data(data) @@ -928,7 +928,7 @@ def test_reject_headers_exceeding_table_size(self, frame_factory): frame_factory.change_table_size(c.local_settings.header_table_size + 1) f = frame_factory.build_headers_frame( stream_id=5, - headers=self.request_header_block + headers=self.request_header_block, ) data = f.serialize() @@ -936,6 +936,6 @@ def test_reject_headers_exceeding_table_size(self, frame_factory): c.receive_data(data) expected_frame = frame_factory.build_goaway_frame( - last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR + last_stream_id=1, error_code=h2.errors.ErrorCodes.PROTOCOL_ERROR, ) assert c.data_to_send() == expected_frame.serialize() diff --git a/tests/test_priority.py b/tests/test_priority.py index 56c5165d3..761df4ab0 100644 --- a/tests/test_priority.py +++ b/tests/test_priority.py @@ -1,9 +1,5 @@ -""" -test_priority -~~~~~~~~~~~~~ +from __future__ import annotations -Test the priority logic of Hyper-h2. -""" import pytest import h2.config @@ -14,29 +10,30 @@ import h2.stream -class TestPriority(object): +class TestPriority: """ Basic priority tests. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_request_headers_bytes = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'pytest-h2'), + (":status", "200"), + ("server", "pytest-h2"), ] server_config = h2.config.H2Configuration(client_side=False) - def test_receiving_priority_emits_priority_update(self, frame_factory): + def test_receiving_priority_emits_priority_update(self, frame_factory) -> None: """ Receiving a priority frame emits a PriorityUpdated event. """ @@ -62,7 +59,7 @@ def test_receiving_priority_emits_priority_update(self, frame_factory): assert event.exclusive is False @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_headers_with_priority_info(self, frame_factory, request_headers): + def test_headers_with_priority_info(self, frame_factory, request_headers) -> None: """ Receiving a HEADERS frame with priority information on it emits a PriorityUpdated event. @@ -75,7 +72,7 @@ def test_headers_with_priority_info(self, frame_factory, request_headers): f = frame_factory.build_headers_frame( headers=request_headers, stream_id=3, - flags=['PRIORITY'], + flags=["PRIORITY"], stream_weight=15, depends_on=1, exclusive=True, @@ -93,7 +90,7 @@ def test_headers_with_priority_info(self, frame_factory, request_headers): assert event.exclusive is True @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_streams_may_not_depend_on_themselves(self, frame_factory, request_headers): + def test_streams_may_not_depend_on_themselves(self, frame_factory, request_headers) -> None: """ A stream adjusted to depend on itself causes a Protocol Error. """ @@ -105,7 +102,7 @@ def test_streams_may_not_depend_on_themselves(self, frame_factory, request_heade f = frame_factory.build_headers_frame( headers=request_headers, stream_id=3, - flags=['PRIORITY'], + flags=["PRIORITY"], stream_weight=15, depends_on=1, exclusive=True, @@ -116,7 +113,7 @@ def test_streams_may_not_depend_on_themselves(self, frame_factory, request_heade f = frame_factory.build_priority_frame( stream_id=3, depends_on=3, - weight=15 + weight=15, ) with pytest.raises(h2.exceptions.ProtocolError): c.receive_data(f.serialize()) @@ -129,15 +126,15 @@ def test_streams_may_not_depend_on_themselves(self, frame_factory, request_heade @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) @pytest.mark.parametrize( - 'depends_on,weight,exclusive', + ("depends_on", "weight", "exclusive"), [ (0, 256, False), (3, 128, False), (3, 128, True), - ] + ], ) def test_can_prioritize_stream(self, depends_on, weight, exclusive, - frame_factory, request_headers): + frame_factory, request_headers) -> None: """ hyper-h2 can emit priority frames. """ @@ -152,7 +149,7 @@ def test_can_prioritize_stream(self, depends_on, weight, exclusive, stream_id=1, depends_on=depends_on, weight=weight, - exclusive=exclusive + exclusive=exclusive, ) f = frame_factory.build_priority_frame( @@ -165,15 +162,15 @@ def test_can_prioritize_stream(self, depends_on, weight, exclusive, @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) @pytest.mark.parametrize( - 'depends_on,weight,exclusive', + ("depends_on", "weight", "exclusive"), [ (0, 256, False), (1, 128, False), (1, 128, True), - ] + ], ) def test_emit_headers_with_priority_info(self, depends_on, weight, - exclusive, frame_factory, request_headers): + exclusive, frame_factory, request_headers) -> None: """ It is possible to send a headers frame with priority information on it. @@ -193,7 +190,7 @@ def test_emit_headers_with_priority_info(self, depends_on, weight, f = frame_factory.build_headers_frame( headers=request_headers, stream_id=3, - flags=['PRIORITY'], + flags=["PRIORITY"], stream_weight=weight - 1, depends_on=depends_on, exclusive=exclusive, @@ -201,7 +198,7 @@ def test_emit_headers_with_priority_info(self, depends_on, weight, assert c.data_to_send() == f.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_may_not_prioritize_stream_to_depend_on_self(self, frame_factory, request_headers): + def test_may_not_prioritize_stream_to_depend_on_self(self, frame_factory, request_headers) -> None: """ A stream adjusted to depend on itself causes a Protocol Error. """ @@ -226,7 +223,7 @@ def test_may_not_prioritize_stream_to_depend_on_self(self, frame_factory, reques assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_may_not_initially_set_stream_depend_on_self(self, frame_factory, request_headers): + def test_may_not_initially_set_stream_depend_on_self(self, frame_factory, request_headers) -> None: """ A stream that starts by depending on itself causes a Protocol Error. """ @@ -244,8 +241,8 @@ def test_may_not_initially_set_stream_depend_on_self(self, frame_factory, reques assert not c.data_to_send() - @pytest.mark.parametrize('weight', [0, -15, 257]) - def test_prioritize_requires_valid_weight(self, weight): + @pytest.mark.parametrize("weight", [0, -15, 257]) + def test_prioritize_requires_valid_weight(self, weight) -> None: """ A call to prioritize with an invalid weight causes a ProtocolError. """ @@ -259,8 +256,8 @@ def test_prioritize_requires_valid_weight(self, weight): assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - @pytest.mark.parametrize('weight', [0, -15, 257]) - def test_send_headers_requires_valid_weight(self, weight, request_headers): + @pytest.mark.parametrize("weight", [0, -15, 257]) + def test_send_headers_requires_valid_weight(self, weight, request_headers) -> None: """ A call to send_headers with an invalid weight causes a ProtocolError. """ @@ -272,12 +269,12 @@ def test_send_headers_requires_valid_weight(self, weight, request_headers): c.send_headers( stream_id=1, headers=request_headers, - priority_weight=weight + priority_weight=weight, ) assert not c.data_to_send() - def test_prioritize_defaults(self, frame_factory): + def test_prioritize_defaults(self, frame_factory) -> None: """ When prioritize() is called with no explicit arguments, it emits a weight of 16, depending on stream zero non-exclusively. @@ -298,14 +295,14 @@ def test_prioritize_defaults(self, frame_factory): @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) @pytest.mark.parametrize( - 'priority_kwargs', + "priority_kwargs", [ - {'priority_weight': 16}, - {'priority_depends_on': 0}, - {'priority_exclusive': False}, - ] + {"priority_weight": 16}, + {"priority_depends_on": 0}, + {"priority_exclusive": False}, + ], ) - def test_send_headers_defaults(self, priority_kwargs, frame_factory, request_headers): + def test_send_headers_defaults(self, priority_kwargs, frame_factory, request_headers) -> None: """ When send_headers() is called with only one explicit argument, it emits default values for everything else. @@ -317,13 +314,13 @@ def test_send_headers_defaults(self, priority_kwargs, frame_factory, request_hea c.send_headers( stream_id=1, headers=request_headers, - **priority_kwargs + **priority_kwargs, ) f = frame_factory.build_headers_frame( headers=request_headers, stream_id=1, - flags=['PRIORITY'], + flags=["PRIORITY"], stream_weight=15, depends_on=0, exclusive=False, @@ -331,7 +328,7 @@ def test_send_headers_defaults(self, priority_kwargs, frame_factory, request_hea assert c.data_to_send() == f.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_servers_cannot_prioritize(self, frame_factory, request_headers): + def test_servers_cannot_prioritize(self, frame_factory, request_headers) -> None: """ Server stacks are not allowed to call ``prioritize()``. """ @@ -350,7 +347,7 @@ def test_servers_cannot_prioritize(self, frame_factory, request_headers): c.prioritize(stream_id=1) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_servers_cannot_prioritize_with_headers(self, frame_factory, request_headers): + def test_servers_cannot_prioritize_with_headers(self, frame_factory, request_headers) -> None: """ Server stacks are not allowed to prioritize on headers either. """ diff --git a/tests/test_related_events.py b/tests/test_related_events.py index 9477daae7..aed7c1874 100644 --- a/tests/test_related_events.py +++ b/tests/test_related_events.py @@ -1,10 +1,9 @@ """ -test_related_events.py -~~~~~~~~~~~~~~~~~~~~~~ - Specific tests to validate the "related events" logic used by certain events inside hyper-h2. """ +from __future__ import annotations + import pytest import h2.config @@ -12,42 +11,43 @@ import h2.events -class TestRelatedEvents(object): +class TestRelatedEvents: """ Related events correlate all those events that happen on a single frame. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_request_headers_bytes = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] informational_response_headers = [ - (':status', '100'), - ('server', 'fake-serv/0.1.0') + (":status", "100"), + ("server", "fake-serv/0.1.0"), ] example_trailers = [ - ('another', 'field'), + ("another", "field"), ] server_config = h2.config.H2Configuration(client_side=False) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_request_received_related_all(self, frame_factory, request_headers): + def test_request_received_related_all(self, frame_factory, request_headers) -> None: """ RequestReceived has two possible related events: PriorityUpdated and StreamEnded, all fired when a single HEADERS frame is received. @@ -58,7 +58,7 @@ def test_request_received_related_all(self, frame_factory, request_headers): input_frame = frame_factory.build_headers_frame( headers=request_headers, - flags=['END_STREAM', 'PRIORITY'], + flags=["END_STREAM", "PRIORITY"], stream_weight=15, depends_on=0, exclusive=False, @@ -73,11 +73,11 @@ def test_request_received_related_all(self, frame_factory, request_headers): assert isinstance(base_event.stream_ended, h2.events.StreamEnded) assert base_event.priority_updated in other_events assert isinstance( - base_event.priority_updated, h2.events.PriorityUpdated + base_event.priority_updated, h2.events.PriorityUpdated, ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_request_received_related_priority(self, frame_factory, request_headers): + def test_request_received_related_priority(self, frame_factory, request_headers) -> None: """ RequestReceived can be related to PriorityUpdated. """ @@ -87,7 +87,7 @@ def test_request_received_related_priority(self, frame_factory, request_headers) input_frame = frame_factory.build_headers_frame( headers=request_headers, - flags=['PRIORITY'], + flags=["PRIORITY"], stream_weight=15, depends_on=0, exclusive=False, @@ -101,11 +101,11 @@ def test_request_received_related_priority(self, frame_factory, request_headers) assert base_event.priority_updated is priority_updated_event assert base_event.stream_ended is None assert isinstance( - base_event.priority_updated, h2.events.PriorityUpdated + base_event.priority_updated, h2.events.PriorityUpdated, ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_request_received_related_stream_ended(self, frame_factory, request_headers): + def test_request_received_related_stream_ended(self, frame_factory, request_headers) -> None: """ RequestReceived can be related to StreamEnded. """ @@ -115,7 +115,7 @@ def test_request_received_related_stream_ended(self, frame_factory, request_head input_frame = frame_factory.build_headers_frame( headers=request_headers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) events = c.receive_data(input_frame.serialize()) @@ -128,7 +128,7 @@ def test_request_received_related_stream_ended(self, frame_factory, request_head assert isinstance(base_event.stream_ended, h2.events.StreamEnded) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_response_received_related_nothing(self, frame_factory, request_headers): + def test_response_received_related_nothing(self, frame_factory, request_headers) -> None: """ ResponseReceived is ordinarily related to no events. """ @@ -148,7 +148,7 @@ def test_response_received_related_nothing(self, frame_factory, request_headers) assert base_event.priority_updated is None @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_response_received_related_all(self, frame_factory, request_headers): + def test_response_received_related_all(self, frame_factory, request_headers) -> None: """ ResponseReceived has two possible related events: PriorityUpdated and StreamEnded, all fired when a single HEADERS frame is received. @@ -159,7 +159,7 @@ def test_response_received_related_all(self, frame_factory, request_headers): input_frame = frame_factory.build_headers_frame( headers=self.example_response_headers, - flags=['END_STREAM', 'PRIORITY'], + flags=["END_STREAM", "PRIORITY"], stream_weight=15, depends_on=0, exclusive=False, @@ -174,11 +174,11 @@ def test_response_received_related_all(self, frame_factory, request_headers): assert isinstance(base_event.stream_ended, h2.events.StreamEnded) assert base_event.priority_updated in other_events assert isinstance( - base_event.priority_updated, h2.events.PriorityUpdated + base_event.priority_updated, h2.events.PriorityUpdated, ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_response_received_related_priority(self, frame_factory, request_headers): + def test_response_received_related_priority(self, frame_factory, request_headers) -> None: """ ResponseReceived can be related to PriorityUpdated. """ @@ -188,7 +188,7 @@ def test_response_received_related_priority(self, frame_factory, request_headers input_frame = frame_factory.build_headers_frame( headers=self.example_response_headers, - flags=['PRIORITY'], + flags=["PRIORITY"], stream_weight=15, depends_on=0, exclusive=False, @@ -202,11 +202,11 @@ def test_response_received_related_priority(self, frame_factory, request_headers assert base_event.priority_updated is priority_updated_event assert base_event.stream_ended is None assert isinstance( - base_event.priority_updated, h2.events.PriorityUpdated + base_event.priority_updated, h2.events.PriorityUpdated, ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_response_received_related_stream_ended(self, frame_factory, request_headers): + def test_response_received_related_stream_ended(self, frame_factory, request_headers) -> None: """ ResponseReceived can be related to StreamEnded. """ @@ -216,7 +216,7 @@ def test_response_received_related_stream_ended(self, frame_factory, request_hea input_frame = frame_factory.build_headers_frame( headers=self.example_response_headers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) events = c.receive_data(input_frame.serialize()) @@ -229,7 +229,7 @@ def test_response_received_related_stream_ended(self, frame_factory, request_hea assert isinstance(base_event.stream_ended, h2.events.StreamEnded) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_trailers_received_related_all(self, frame_factory, request_headers): + def test_trailers_received_related_all(self, frame_factory, request_headers) -> None: """ TrailersReceived has two possible related events: PriorityUpdated and StreamEnded, all fired when a single HEADERS frame is received. @@ -245,7 +245,7 @@ def test_trailers_received_related_all(self, frame_factory, request_headers): input_frame = frame_factory.build_headers_frame( headers=self.example_trailers, - flags=['END_STREAM', 'PRIORITY'], + flags=["END_STREAM", "PRIORITY"], stream_weight=15, depends_on=0, exclusive=False, @@ -260,11 +260,11 @@ def test_trailers_received_related_all(self, frame_factory, request_headers): assert isinstance(base_event.stream_ended, h2.events.StreamEnded) assert base_event.priority_updated in other_events assert isinstance( - base_event.priority_updated, h2.events.PriorityUpdated + base_event.priority_updated, h2.events.PriorityUpdated, ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_trailers_received_related_stream_ended(self, frame_factory, request_headers): + def test_trailers_received_related_stream_ended(self, frame_factory, request_headers) -> None: """ TrailersReceived can be related to StreamEnded by itself. """ @@ -279,7 +279,7 @@ def test_trailers_received_related_stream_ended(self, frame_factory, request_hea input_frame = frame_factory.build_headers_frame( headers=self.example_trailers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) events = c.receive_data(input_frame.serialize()) @@ -292,7 +292,7 @@ def test_trailers_received_related_stream_ended(self, frame_factory, request_hea assert isinstance(base_event.stream_ended, h2.events.StreamEnded) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_informational_response_related_nothing(self, frame_factory, request_headers): + def test_informational_response_related_nothing(self, frame_factory, request_headers) -> None: """ InformationalResponseReceived in the standard case is related to nothing. @@ -312,7 +312,7 @@ def test_informational_response_related_nothing(self, frame_factory, request_hea assert base_event.priority_updated is None @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_informational_response_received_related_all(self, frame_factory, request_headers): + def test_informational_response_received_related_all(self, frame_factory, request_headers) -> None: """ InformationalResponseReceived has one possible related event: PriorityUpdated, fired when a single HEADERS frame is received. @@ -323,7 +323,7 @@ def test_informational_response_received_related_all(self, frame_factory, reques input_frame = frame_factory.build_headers_frame( headers=self.informational_response_headers, - flags=['PRIORITY'], + flags=["PRIORITY"], stream_weight=15, depends_on=0, exclusive=False, @@ -336,11 +336,11 @@ def test_informational_response_received_related_all(self, frame_factory, reques assert base_event.priority_updated is priority_updated_event assert isinstance( - base_event.priority_updated, h2.events.PriorityUpdated + base_event.priority_updated, h2.events.PriorityUpdated, ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_data_received_normally_relates_to_nothing(self, frame_factory, request_headers): + def test_data_received_normally_relates_to_nothing(self, frame_factory, request_headers) -> None: """ A plain DATA frame leads to DataReceieved with no related events. """ @@ -354,7 +354,7 @@ def test_data_received_normally_relates_to_nothing(self, frame_factory, request_ c.receive_data(f.serialize()) input_frame = frame_factory.build_data_frame( - data=b'some data', + data=b"some data", ) events = c.receive_data(input_frame.serialize()) @@ -364,7 +364,7 @@ def test_data_received_normally_relates_to_nothing(self, frame_factory, request_ assert base_event.stream_ended is None @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_data_received_related_stream_ended(self, frame_factory, request_headers): + def test_data_received_related_stream_ended(self, frame_factory, request_headers) -> None: """ DataReceived can be related to StreamEnded by itself. """ @@ -378,8 +378,8 @@ def test_data_received_related_stream_ended(self, frame_factory, request_headers c.receive_data(f.serialize()) input_frame = frame_factory.build_data_frame( - data=b'some data', - flags=['END_STREAM'], + data=b"some data", + flags=["END_STREAM"], ) events = c.receive_data(input_frame.serialize()) diff --git a/tests/test_rfc7838.py b/tests/test_rfc7838.py index 2b696052b..63a4d2ccd 100644 --- a/tests/test_rfc7838.py +++ b/tests/test_rfc7838.py @@ -1,9 +1,8 @@ """ -test_rfc7838 -~~~~~~~~~~~~ - Test the RFC 7838 ALTSVC support. """ +from __future__ import annotations + import pytest import h2.config @@ -12,28 +11,29 @@ import h2.exceptions -class TestRFC7838Client(object): +class TestRFC7838Client: """ Tests that the client supports receiving the RFC 7838 AltSvc frame. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_request_headers_bytes = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] - def test_receiving_altsvc_stream_zero(self, frame_factory): + def test_receiving_altsvc_stream_zero(self, frame_factory) -> None: """ An ALTSVC frame received on stream zero correctly transposes all the fields from the frames. @@ -43,7 +43,7 @@ def test_receiving_altsvc_stream_zero(self, frame_factory): c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' + stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) @@ -57,7 +57,7 @@ def test_receiving_altsvc_stream_zero(self, frame_factory): # No data gets sent. assert not c.data_to_send() - def test_receiving_altsvc_stream_zero_no_origin(self, frame_factory): + def test_receiving_altsvc_stream_zero_no_origin(self, frame_factory) -> None: """ An ALTSVC frame received on stream zero without an origin field is ignored. @@ -67,7 +67,7 @@ def test_receiving_altsvc_stream_zero_no_origin(self, frame_factory): c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=0, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=0, origin=b"", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) @@ -75,7 +75,7 @@ def test_receiving_altsvc_stream_zero_no_origin(self, frame_factory): assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_receiving_altsvc_on_stream(self, frame_factory, request_headers): + def test_receiving_altsvc_on_stream(self, frame_factory, request_headers) -> None: """ An ALTSVC frame received on a stream correctly transposes all the fields from the frame and attaches the expected origin. @@ -86,7 +86,7 @@ def test_receiving_altsvc_on_stream(self, frame_factory, request_headers): c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) @@ -101,7 +101,7 @@ def test_receiving_altsvc_on_stream(self, frame_factory, request_headers): assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_receiving_altsvc_on_stream_with_origin(self, frame_factory, request_headers): + def test_receiving_altsvc_on_stream_with_origin(self, frame_factory, request_headers) -> None: """ An ALTSVC frame received on a stream with an origin field present gets ignored. @@ -112,14 +112,14 @@ def test_receiving_altsvc_on_stream_with_origin(self, frame_factory, request_hea c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=1, origin=b"example.com", field=b'h2=":8000"; ma=60' + stream_id=1, origin=b"example.com", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) assert len(events) == 0 assert not c.data_to_send() - def test_receiving_altsvc_on_stream_not_yet_opened(self, frame_factory): + def test_receiving_altsvc_on_stream_not_yet_opened(self, frame_factory) -> None: """ When an ALTSVC frame is received on a stream the client hasn't yet opened, the frame is ignored. @@ -131,17 +131,17 @@ def test_receiving_altsvc_on_stream_not_yet_opened(self, frame_factory): # We'll test this twice, once on a client-initiated stream ID and once # on a server initiated one. f1 = frame_factory.build_alt_svc_frame( - stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60', ) f2 = frame_factory.build_alt_svc_frame( - stream_id=2, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=2, origin=b"", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f1.serialize() + f2.serialize()) assert len(events) == 0 assert not c.data_to_send() - def test_receiving_altsvc_before_sending_headers(self, frame_factory): + def test_receiving_altsvc_before_sending_headers(self, frame_factory) -> None: """ When an ALTSVC frame is received but the client hasn't sent headers yet it gets ignored. @@ -154,12 +154,12 @@ def test_receiving_altsvc_before_sending_headers(self, frame_factory): # don't currently have a mechanism by which this could occur), it could # happen in the future and we defend against it. c._begin_new_stream( - stream_id=1, allowed_ids=h2.connection.AllowedStreamIDs.ODD + stream_id=1, allowed_ids=h2.connection.AllowedStreamIDs.ODD, ) c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) @@ -167,7 +167,7 @@ def test_receiving_altsvc_before_sending_headers(self, frame_factory): assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_receiving_altsvc_after_receiving_headers(self, frame_factory, request_headers): + def test_receiving_altsvc_after_receiving_headers(self, frame_factory, request_headers) -> None: """ When an ALTSVC frame is received but the server has already sent headers it gets ignored. @@ -177,13 +177,13 @@ def test_receiving_altsvc_after_receiving_headers(self, frame_factory, request_h c.send_headers(stream_id=1, headers=request_headers) f = frame_factory.build_headers_frame( - headers=self.example_response_headers + headers=self.example_response_headers, ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) @@ -191,25 +191,25 @@ def test_receiving_altsvc_after_receiving_headers(self, frame_factory, request_h assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_receiving_altsvc_on_closed_stream(self, frame_factory, request_headers): + def test_receiving_altsvc_on_closed_stream(self, frame_factory, request_headers) -> None: """ When an ALTSVC frame is received on a closed stream, we ignore it. """ c = h2.connection.H2Connection() c.initiate_connection() c.send_headers( - stream_id=1, headers=request_headers, end_stream=True + stream_id=1, headers=request_headers, end_stream=True, ) f = frame_factory.build_headers_frame( headers=self.example_response_headers, - flags=['END_STREAM'], + flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) @@ -217,7 +217,7 @@ def test_receiving_altsvc_on_closed_stream(self, frame_factory, request_headers) assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_receiving_altsvc_on_pushed_stream(self, frame_factory, request_headers): + def test_receiving_altsvc_on_pushed_stream(self, frame_factory, request_headers) -> None: """ When an ALTSVC frame is received on a stream that the server pushed, the frame is accepted. @@ -229,13 +229,13 @@ def test_receiving_altsvc_on_pushed_stream(self, frame_factory, request_headers) f = frame_factory.build_push_promise_frame( stream_id=1, promised_stream_id=2, - headers=request_headers + headers=request_headers, ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=2, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=2, origin=b"", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) @@ -250,7 +250,7 @@ def test_receiving_altsvc_on_pushed_stream(self, frame_factory, request_headers) assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_cannot_send_explicit_alternative_service(self, frame_factory, request_headers): + def test_cannot_send_explicit_alternative_service(self, frame_factory, request_headers) -> None: """ A client cannot send an explicit alternative service. """ @@ -266,7 +266,7 @@ def test_cannot_send_explicit_alternative_service(self, frame_factory, request_h ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_cannot_send_implicit_alternative_service(self, frame_factory, request_headers): + def test_cannot_send_implicit_alternative_service(self, frame_factory, request_headers) -> None: """ A client cannot send an implicit alternative service. """ @@ -282,30 +282,31 @@ def test_cannot_send_implicit_alternative_service(self, frame_factory, request_h ) -class TestRFC7838Server(object): +class TestRFC7838Server: """ Tests that the server supports sending the RFC 7838 AltSvc frame. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_request_headers_bytes = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (u':status', u'200'), - (u'server', u'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) - def test_receiving_altsvc_as_server_stream_zero(self, frame_factory): + def test_receiving_altsvc_as_server_stream_zero(self, frame_factory) -> None: """ When an ALTSVC frame is received on stream zero and we are a server, we ignore it. @@ -316,7 +317,7 @@ def test_receiving_altsvc_as_server_stream_zero(self, frame_factory): c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' + stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) @@ -324,7 +325,7 @@ def test_receiving_altsvc_as_server_stream_zero(self, frame_factory): assert not c.data_to_send() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_receiving_altsvc_as_server_on_stream(self, frame_factory, request_headers): + def test_receiving_altsvc_as_server_on_stream(self, frame_factory, request_headers) -> None: """ When an ALTSVC frame is received on a stream and we are a server, we ignore it. @@ -334,20 +335,20 @@ def test_receiving_altsvc_as_server_on_stream(self, frame_factory, request_heade c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - headers=request_headers + headers=request_headers, ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() f = frame_factory.build_alt_svc_frame( - stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60', ) events = c.receive_data(f.serialize()) assert len(events) == 0 assert not c.data_to_send() - def test_sending_explicit_alternative_service(self, frame_factory): + def test_sending_explicit_alternative_service(self, frame_factory) -> None: """ A server can send an explicit alternative service. """ @@ -362,12 +363,12 @@ def test_sending_explicit_alternative_service(self, frame_factory): ) f = frame_factory.build_alt_svc_frame( - stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60' + stream_id=0, origin=b"example.com", field=b'h2=":8000"; ma=60', ) assert c.data_to_send() == f.serialize() @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_sending_implicit_alternative_service(self, frame_factory, request_headers): + def test_sending_implicit_alternative_service(self, frame_factory, request_headers) -> None: """ A server can send an implicit alternative service. """ @@ -376,7 +377,7 @@ def test_sending_implicit_alternative_service(self, frame_factory, request_heade c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - headers=request_headers + headers=request_headers, ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() @@ -387,12 +388,12 @@ def test_sending_implicit_alternative_service(self, frame_factory, request_heade ) f = frame_factory.build_alt_svc_frame( - stream_id=1, origin=b"", field=b'h2=":8000"; ma=60' + stream_id=1, origin=b"", field=b'h2=":8000"; ma=60', ) assert c.data_to_send() == f.serialize() def test_no_implicit_alternative_service_before_headers(self, - frame_factory): + frame_factory) -> None: """ If headers haven't been received yet, the server forbids sending an implicit alternative service. @@ -411,7 +412,7 @@ def test_no_implicit_alternative_service_before_headers(self, @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) def test_no_implicit_alternative_service_after_response(self, frame_factory, - request_headers): + request_headers) -> None: """ If the server has sent response headers, hyper-h2 forbids sending an implicit alternative service. @@ -421,7 +422,7 @@ def test_no_implicit_alternative_service_after_response(self, c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - headers=request_headers + headers=request_headers, ) c.receive_data(f.serialize()) c.send_headers(stream_id=1, headers=self.example_response_headers) @@ -434,7 +435,7 @@ def test_no_implicit_alternative_service_after_response(self, ) @pytest.mark.parametrize("request_headers", [example_request_headers, example_request_headers_bytes]) - def test_cannot_provide_origin_and_stream_id(self, frame_factory, request_headers): + def test_cannot_provide_origin_and_stream_id(self, frame_factory, request_headers) -> None: """ The user cannot provide both the origin and stream_id arguments when advertising alternative services. @@ -443,7 +444,7 @@ def test_cannot_provide_origin_and_stream_id(self, frame_factory, request_header c.initiate_connection() c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - headers=request_headers + headers=request_headers, ) c.receive_data(f.serialize()) @@ -454,7 +455,7 @@ def test_cannot_provide_origin_and_stream_id(self, frame_factory, request_header stream_id=1, ) - def test_cannot_provide_unicode_altsvc_field(self, frame_factory): + def test_cannot_provide_unicode_altsvc_field(self, frame_factory) -> None: """ The user cannot provide the field value for alternative services as a unicode string. @@ -465,6 +466,6 @@ def test_cannot_provide_unicode_altsvc_field(self, frame_factory): with pytest.raises(ValueError): c.advertise_alternative_service( - field_value=u'h2=":8000"; ma=60', + field_value='h2=":8000"; ma=60', origin=b"example.com", ) diff --git a/tests/test_rfc8441.py b/tests/test_rfc8441.py index cc8ae00dc..0f6092c2f 100644 --- a/tests/test_rfc8441.py +++ b/tests/test_rfc8441.py @@ -1,49 +1,48 @@ """ -test_rfc8441 -~~~~~~~~~~~~ - Test the RFC 8441 extended connect request support. """ -from h2.utilities import utf8_encode_headers +from __future__ import annotations + import pytest import h2.config import h2.connection import h2.events +from h2.utilities import utf8_encode_headers -class TestRFC8441(object): +class TestRFC8441: """ Tests that the client supports sending an extended connect request and the server supports receiving it. """ headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'CONNECT'), - (':protocol', 'websocket'), - ('user-agent', 'someua/0.0.1'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "CONNECT"), + (":protocol", "websocket"), + ("user-agent", "someua/0.0.1"), ] headers_bytes = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'CONNECT'), - (b':protocol', b'websocket'), - (b'user-agent', b'someua/0.0.1'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"CONNECT"), + (b":protocol", b"websocket"), + (b"user-agent", b"someua/0.0.1"), ] @pytest.mark.parametrize("headers", [headers, headers_bytes]) - def test_can_send_headers(self, frame_factory, headers): + def test_can_send_headers(self, frame_factory, headers) -> None: client = h2.connection.H2Connection() client.initiate_connection() client.send_headers(stream_id=1, headers=headers) server = h2.connection.H2Connection( - config=h2.config.H2Configuration(client_side=False) + config=h2.config.H2Configuration(client_side=False), ) events = server.receive_data(client.data_to_send()) event = events[1] diff --git a/tests/test_settings.py b/tests/test_settings.py index cc34d0801..89acb90e0 100644 --- a/tests/test_settings.py +++ b/tests/test_settings.py @@ -1,26 +1,23 @@ """ -test_settings -~~~~~~~~~~~~~ - Test the Settings object. """ +from __future__ import annotations + import pytest +from hypothesis import assume, given +from hypothesis.strategies import booleans, builds, fixed_dictionaries, integers import h2.errors import h2.exceptions import h2.settings -from hypothesis import given, assume -from hypothesis.strategies import ( - integers, booleans, fixed_dictionaries, builds -) - -class TestSettings(object): +class TestSettings: """ Test the Settings object behaves as expected. """ - def test_settings_defaults_client(self): + + def test_settings_defaults_client(self) -> None: """ The Settings object begins with the appropriate defaults for clients. """ @@ -32,7 +29,7 @@ def test_settings_defaults_client(self): assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384 assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 - def test_settings_defaults_server(self): + def test_settings_defaults_server(self) -> None: """ The Settings object begins with the appropriate defaults for servers. """ @@ -44,8 +41,8 @@ def test_settings_defaults_server(self): assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384 assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 - @pytest.mark.parametrize('client', [True, False]) - def test_can_set_initial_values(self, client): + @pytest.mark.parametrize("client", [True, False]) + def test_can_set_initial_values(self, client) -> None: """ The Settings object can be provided initial values that override the defaults. @@ -68,7 +65,7 @@ def test_can_set_initial_values(self, client): assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 1 @pytest.mark.parametrize( - 'setting,value', + ("setting", "value"), [ (h2.settings.SettingCodes.ENABLE_PUSH, 2), (h2.settings.SettingCodes.ENABLE_PUSH, -1), @@ -78,9 +75,9 @@ def test_can_set_initial_values(self, client): (h2.settings.SettingCodes.MAX_FRAME_SIZE, 2**30), (h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE, -1), (h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL, -1), - ] + ], ) - def test_cannot_set_invalid_initial_values(self, setting, value): + def test_cannot_set_invalid_initial_values(self, setting, value) -> None: """ The Settings object can be provided initial values that override the defaults. @@ -90,7 +87,7 @@ def test_cannot_set_invalid_initial_values(self, setting, value): with pytest.raises(h2.exceptions.InvalidSettingsValueError): h2.settings.Settings(initial_values=overrides) - def test_applying_value_doesnt_take_effect_immediately(self): + def test_applying_value_doesnt_take_effect_immediately(self) -> None: """ When a value is applied to the settings object, it doesn't immediately take effect. @@ -100,7 +97,7 @@ def test_applying_value_doesnt_take_effect_immediately(self): assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096 - def test_acknowledging_values(self): + def test_acknowledging_values(self) -> None: """ When we acknowledge settings, the values change. """ @@ -120,7 +117,7 @@ def test_acknowledging_values(self): s.acknowledge() assert dict(s) == new_settings - def test_acknowledging_returns_the_changed_settings(self): + def test_acknowledging_returns_the_changed_settings(self) -> None: """ Acknowledging settings returns the changes. """ @@ -146,7 +143,7 @@ def test_acknowledging_returns_the_changed_settings(self): assert push_change.original_value == 1 assert push_change.new_value == 0 - def test_acknowledging_only_returns_changed_settings(self): + def test_acknowledging_only_returns_changed_settings(self) -> None: """ Acknowledging settings does not return unchanged settings. """ @@ -156,10 +153,10 @@ def test_acknowledging_only_returns_changed_settings(self): changes = s.acknowledge() assert len(changes) == 1 assert list(changes.keys()) == [ - h2.settings.SettingCodes.INITIAL_WINDOW_SIZE + h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, ] - def test_deleting_values_deletes_all_of_them(self): + def test_deleting_values_deletes_all_of_them(self) -> None: """ When we delete a key we lose all state about it. """ @@ -171,7 +168,7 @@ def test_deleting_values_deletes_all_of_them(self): with pytest.raises(KeyError): s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] - def test_length_correctly_reported(self): + def test_length_correctly_reported(self) -> None: """ Length is related only to the number of keys. """ @@ -187,7 +184,7 @@ def test_length_correctly_reported(self): del s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] assert len(s) == 4 - def test_new_values_work(self): + def test_new_values_work(self) -> None: """ New values initially don't appear """ @@ -197,7 +194,7 @@ def test_new_values_work(self): with pytest.raises(KeyError): s[80] - def test_new_values_follow_basic_acknowledgement_rules(self): + def test_new_values_follow_basic_acknowledgement_rules(self) -> None: """ A new value properly appears when acknowledged. """ @@ -213,7 +210,7 @@ def test_new_values_follow_basic_acknowledgement_rules(self): assert changed.original_value is None assert changed.new_value == 81 - def test_single_values_arent_affected_by_acknowledgement(self): + def test_single_values_arent_affected_by_acknowledgement(self) -> None: """ When acknowledged, unchanged settings remain unchanged. """ @@ -223,7 +220,7 @@ def test_single_values_arent_affected_by_acknowledgement(self): s.acknowledge() assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096 - def test_settings_getters(self): + def test_settings_getters(self) -> None: """ Getters exist for well-known settings. """ @@ -243,7 +240,7 @@ def test_settings_getters(self): h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL ] - def test_settings_setters(self): + def test_settings_setters(self) -> None: """ Setters exist for well-known settings. """ @@ -267,7 +264,7 @@ def test_settings_setters(self): assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 1 @given(integers()) - def test_cannot_set_invalid_values_for_enable_push(self, val): + def test_cannot_set_invalid_values_for_enable_push(self, val) -> None: """ SETTINGS_ENABLE_PUSH only allows two values: 0, 1. """ @@ -289,7 +286,7 @@ def test_cannot_set_invalid_values_for_enable_push(self, val): assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 1 @given(integers()) - def test_cannot_set_invalid_vals_for_initial_window_size(self, val): + def test_cannot_set_invalid_vals_for_initial_window_size(self, val) -> None: """ SETTINGS_INITIAL_WINDOW_SIZE only allows values between 0 and 2**32 - 1 inclusive. @@ -320,7 +317,7 @@ def test_cannot_set_invalid_vals_for_initial_window_size(self, val): assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535 @given(integers()) - def test_cannot_set_invalid_values_for_max_frame_size(self, val): + def test_cannot_set_invalid_values_for_max_frame_size(self, val) -> None: """ SETTINGS_MAX_FRAME_SIZE only allows values between 2**14 and 2**24 - 1. """ @@ -346,7 +343,7 @@ def test_cannot_set_invalid_values_for_max_frame_size(self, val): assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384 @given(integers()) - def test_cannot_set_invalid_values_for_max_header_list_size(self, val): + def test_cannot_set_invalid_values_for_max_header_list_size(self, val) -> None: """ SETTINGS_MAX_HEADER_LIST_SIZE only allows non-negative values. """ @@ -374,7 +371,7 @@ def test_cannot_set_invalid_values_for_max_header_list_size(self, val): s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] @given(integers()) - def test_cannot_set_invalid_values_for_enable_connect_protocol(self, val): + def test_cannot_set_invalid_values_for_enable_connect_protocol(self, val) -> None: """ SETTINGS_ENABLE_CONNECT_PROTOCOL only allows two values: 0, 1. """ @@ -396,7 +393,7 @@ def test_cannot_set_invalid_values_for_enable_connect_protocol(self, val): assert s[h2.settings.SettingCodes.ENABLE_CONNECT_PROTOCOL] == 0 -class TestSettingsEquality(object): +class TestSettingsEquality: """ A class defining tests for the standard implementation of == and != . """ @@ -416,48 +413,48 @@ class TestSettingsEquality(object): integers(0, 2**32 - 1), h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: integers(0, 2**32 - 1), - }) + }), ) @given(settings=SettingsStrategy) - def test_equality_reflexive(self, settings): + def test_equality_reflexive(self, settings) -> None: """ An object compares equal to itself using the == operator and the != operator. """ assert (settings == settings) - assert not (settings != settings) + assert settings == settings @given(settings=SettingsStrategy, o_settings=SettingsStrategy) - def test_equality_multiple(self, settings, o_settings): + def test_equality_multiple(self, settings, o_settings) -> None: """ Two objects compare themselves using the == operator and the != operator. """ if settings == o_settings: assert settings == o_settings - assert not (settings != o_settings) + assert settings == o_settings else: assert settings != o_settings - assert not (settings == o_settings) + assert settings != o_settings @given(settings=SettingsStrategy) - def test_another_type_equality(self, settings): + def test_another_type_equality(self, settings) -> None: """ The object does not compare equal to an object of an unrelated type (which does not implement the comparison) using the == operator. """ obj = object() assert (settings != obj) - assert not (settings == obj) + assert settings != obj @given(settings=SettingsStrategy) - def test_delegated_eq(self, settings): + def test_delegated_eq(self, settings) -> None: """ The result of comparison is delegated to the right-hand operand if it is of an unrelated type. """ - class Delegate(object): + class Delegate: def __eq__(self, other): return [self] diff --git a/tests/test_state_machines.py b/tests/test_state_machines.py index c9c4ca052..b92ef5976 100644 --- a/tests/test_state_machines.py +++ b/tests/test_state_machines.py @@ -1,28 +1,27 @@ """ -test_state_machines -~~~~~~~~~~~~~~~~~~~ - These tests validate the state machines directly. Writing meaningful tests for this case can be tricky, so the majority of these tests use Hypothesis to try to talk about general behaviours rather than specific cases. """ +from __future__ import annotations + import pytest +from hypothesis import given +from hypothesis.strategies import sampled_from import h2.connection import h2.exceptions import h2.stream -from hypothesis import given -from hypothesis.strategies import sampled_from - -class TestConnectionStateMachine(object): +class TestConnectionStateMachine: """ Tests of the connection state machine. """ + @given(state=sampled_from(h2.connection.ConnectionState), input_=sampled_from(h2.connection.ConnectionInputs)) - def test_state_transitions(self, state, input_): + def test_state_transitions(self, state, input_) -> None: c = h2.connection.H2ConnectionStateMachine() c.state = state @@ -33,7 +32,7 @@ def test_state_transitions(self, state, input_): else: assert c.state in h2.connection.ConnectionState - def test_state_machine_only_allows_connection_states(self): + def test_state_machine_only_allows_connection_states(self) -> None: """ The Connection state machine only allows ConnectionState inputs. """ @@ -53,10 +52,10 @@ def test_state_machine_only_allows_connection_states(self): "input_", [ h2.connection.ConnectionInputs.RECV_PRIORITY, - h2.connection.ConnectionInputs.SEND_PRIORITY - ] + h2.connection.ConnectionInputs.SEND_PRIORITY, + ], ) - def test_priority_frames_allowed_in_all_states(self, state, input_): + def test_priority_frames_allowed_in_all_states(self, state, input_) -> None: """ Priority frames can be sent/received in all connection states except closed. @@ -67,13 +66,14 @@ def test_priority_frames_allowed_in_all_states(self, state, input_): c.process_input(input_) -class TestStreamStateMachine(object): +class TestStreamStateMachine: """ Tests of the stream state machine. """ + @given(state=sampled_from(h2.stream.StreamState), input_=sampled_from(h2.stream.StreamInputs)) - def test_state_transitions(self, state, input_): + def test_state_transitions(self, state, input_) -> None: s = h2.stream.H2StreamStateMachine(stream_id=1) s.state = state @@ -104,7 +104,7 @@ def test_state_transitions(self, state, input_): else: assert s.state in h2.stream.StreamState - def test_state_machine_only_allows_stream_states(self): + def test_state_machine_only_allows_stream_states(self) -> None: """ The Stream state machine only allows StreamState inputs. """ @@ -113,7 +113,7 @@ def test_state_machine_only_allows_stream_states(self): with pytest.raises(ValueError): s.process_input(1) - def test_stream_state_machine_forbids_pushes_on_server_streams(self): + def test_stream_state_machine_forbids_pushes_on_server_streams(self) -> None: """ Streams where this peer is a server do not allow receiving pushed frames. @@ -124,7 +124,7 @@ def test_stream_state_machine_forbids_pushes_on_server_streams(self): with pytest.raises(h2.exceptions.ProtocolError): s.process_input(h2.stream.StreamInputs.RECV_PUSH_PROMISE) - def test_stream_state_machine_forbids_sending_pushes_from_clients(self): + def test_stream_state_machine_forbids_sending_pushes_from_clients(self) -> None: """ Streams where this peer is a client do not allow sending pushed frames. """ @@ -143,9 +143,9 @@ def test_stream_state_machine_forbids_sending_pushes_from_clients(self): h2.stream.StreamInputs.SEND_DATA, h2.stream.StreamInputs.SEND_WINDOW_UPDATE, h2.stream.StreamInputs.SEND_END_STREAM, - ] + ], ) - def test_cannot_send_on_closed_streams(self, input_): + def test_cannot_send_on_closed_streams(self, input_) -> None: """ Sending anything but a PRIORITY frame is forbidden on closed streams. """ diff --git a/tests/test_stream_reset.py b/tests/test_stream_reset.py index 05306c2ab..df0b4f2bd 100644 --- a/tests/test_stream_reset.py +++ b/tests/test_stream_reset.py @@ -1,13 +1,12 @@ """ -test_stream_reset -~~~~~~~~~~~~~~~~~ - More complex tests that exercise stream resetting functionality to validate that connection state is appropriately maintained. Specifically, these tests validate that streams that have been reset accurately keep track of connection-level state. """ +from __future__ import annotations + import pytest import h2.connection @@ -15,23 +14,24 @@ import h2.events -class TestStreamReset(object): +class TestStreamReset: """ Tests for resetting streams. """ + example_request_headers = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] example_response_headers = [ - (b':status', b'200'), - (b'server', b'fake-serv/0.1.0'), - (b'content-length', b'0') + (b":status", b"200"), + (b"server", b"fake-serv/0.1.0"), + (b"content-length", b"0"), ] - def test_reset_stream_keeps_header_state_correct(self, frame_factory): + def test_reset_stream_keeps_header_state_correct(self, frame_factory) -> None: """ A stream that has been reset still affects the header decoder. """ @@ -43,10 +43,10 @@ def test_reset_stream_keeps_header_state_correct(self, frame_factory): c.clear_outbound_data_buffer() f = frame_factory.build_headers_frame( - headers=self.example_response_headers, stream_id=1 + headers=self.example_response_headers, stream_id=1, ) rst_frame = frame_factory.build_rst_stream_frame( - 1, h2.errors.ErrorCodes.STREAM_CLOSED + 1, h2.errors.ErrorCodes.STREAM_CLOSED, ) events = c.receive_data(f.serialize()) assert not events @@ -55,7 +55,7 @@ def test_reset_stream_keeps_header_state_correct(self, frame_factory): # This works because the header state should be intact from the headers # frame that was send on stream 1, so they should decode cleanly. f = frame_factory.build_headers_frame( - headers=self.example_response_headers, stream_id=3 + headers=self.example_response_headers, stream_id=3, ) event = c.receive_data(f.serialize())[0] @@ -63,11 +63,11 @@ def test_reset_stream_keeps_header_state_correct(self, frame_factory): assert event.stream_id == 3 assert event.headers == self.example_response_headers - @pytest.mark.parametrize('close_id,other_id', [(1, 3), (3, 1)]) + @pytest.mark.parametrize(("close_id", "other_id"), [(1, 3), (3, 1)]) def test_reset_stream_keeps_flow_control_correct(self, close_id, other_id, - frame_factory): + frame_factory) -> None: """ A stream that has been reset does not affect the connection flow control window. @@ -81,15 +81,15 @@ def test_reset_stream_keeps_flow_control_correct(self, initial_window = c.remote_flow_control_window(stream_id=other_id) f = frame_factory.build_headers_frame( - headers=self.example_response_headers, stream_id=close_id + headers=self.example_response_headers, stream_id=close_id, ) c.receive_data(f.serialize()) c.reset_stream(stream_id=close_id) c.clear_outbound_data_buffer() f = frame_factory.build_data_frame( - data=b'some data', - stream_id=close_id + data=b"some data", + stream_id=close_id, ) c.receive_data(f.serialize()) @@ -100,12 +100,12 @@ def test_reset_stream_keeps_flow_control_correct(self, assert c.data_to_send() == expected new_window = c.remote_flow_control_window(stream_id=other_id) - assert initial_window - len(b'some data') == new_window + assert initial_window - len(b"some data") == new_window - @pytest.mark.parametrize('clear_streams', [True, False]) + @pytest.mark.parametrize("clear_streams", [True, False]) def test_reset_stream_automatically_resets_pushed_streams(self, frame_factory, - clear_streams): + clear_streams) -> None: """ Resetting a stream causes RST_STREAM frames to be automatically emitted to close any streams pushed after the reset. diff --git a/tests/test_utility_functions.py b/tests/test_utility_functions.py index 2bbe89fe3..31634f40e 100644 --- a/tests/test_utility_functions.py +++ b/tests/test_utility_functions.py @@ -1,9 +1,8 @@ """ -test_utility_functions -~~~~~~~~~~~~~~~~~~~~~~ - Tests for the various utility functions provided by hyper-h2. """ +from __future__ import annotations + import pytest import h2.config @@ -14,23 +13,24 @@ from h2.utilities import SizeLimitDict, extract_method_header -class TestGetNextAvailableStreamID(object): +class TestGetNextAvailableStreamID: """ Tests for the ``H2Connection.get_next_available_stream_id`` method. """ + example_request_headers = [ - (':authority', 'example.com'), - (':path', '/'), - (':scheme', 'https'), - (':method', 'GET'), + (":authority", "example.com"), + (":path", "/"), + (":scheme", "https"), + (":method", "GET"), ] example_response_headers = [ - (':status', '200'), - ('server', 'fake-serv/0.1.0') + (":status", "200"), + ("server", "fake-serv/0.1.0"), ] server_config = h2.config.H2Configuration(client_side=False) - def test_returns_correct_sequence_for_clients(self, frame_factory): + def test_returns_correct_sequence_for_clients(self, frame_factory) -> None: """ For a client connection, the correct sequence of stream IDs is returned. @@ -54,12 +54,12 @@ def test_returns_correct_sequence_for_clients(self, frame_factory): c.send_headers( stream_id=stream_id, headers=self.example_request_headers, - end_stream=True + end_stream=True, ) f = frame_factory.build_headers_frame( headers=self.example_response_headers, stream_id=stream_id, - flags=['END_STREAM'], + flags=["END_STREAM"], ) c.receive_data(f.serialize()) c.clear_outbound_data_buffer() @@ -70,13 +70,13 @@ def test_returns_correct_sequence_for_clients(self, frame_factory): c.send_headers( stream_id=last_client_id, headers=self.example_request_headers, - end_stream=True + end_stream=True, ) with pytest.raises(h2.exceptions.NoAvailableStreamIDError): c.get_next_available_stream_id() - def test_returns_correct_sequence_for_servers(self, frame_factory): + def test_returns_correct_sequence_for_servers(self, frame_factory) -> None: """ For a server connection, the correct sequence of stream IDs is returned. @@ -93,7 +93,7 @@ def test_returns_correct_sequence_for_servers(self, frame_factory): c.initiate_connection() c.receive_data(frame_factory.preamble()) f = frame_factory.build_headers_frame( - headers=self.example_request_headers + headers=self.example_request_headers, ) c.receive_data(f.serialize()) @@ -106,12 +106,12 @@ def test_returns_correct_sequence_for_servers(self, frame_factory): c.push_stream( stream_id=1, promised_stream_id=stream_id, - request_headers=self.example_request_headers + request_headers=self.example_request_headers, ) c.send_headers( stream_id=stream_id, headers=self.example_response_headers, - end_stream=True + end_stream=True, ) c.clear_outbound_data_buffer() @@ -127,7 +127,7 @@ def test_returns_correct_sequence_for_servers(self, frame_factory): with pytest.raises(h2.exceptions.NoAvailableStreamIDError): c.get_next_available_stream_id() - def test_does_not_increment_without_stream_send(self): + def test_does_not_increment_without_stream_send(self) -> None: """ If a new stream isn't actually created, the next stream ID doesn't change. @@ -142,29 +142,29 @@ def test_does_not_increment_without_stream_send(self): c.send_headers( stream_id=first_stream_id, - headers=self.example_request_headers + headers=self.example_request_headers, ) third_stream_id = c.get_next_available_stream_id() assert third_stream_id == (first_stream_id + 2) -class TestExtractHeader(object): +class TestExtractHeader: example_headers_with_bytes = [ - (b':authority', b'example.com'), - (b':path', b'/'), - (b':scheme', b'https'), - (b':method', b'GET'), + (b":authority", b"example.com"), + (b":path", b"/"), + (b":scheme", b"https"), + (b":method", b"GET"), ] - def test_extract_header_method(self): + def test_extract_header_method(self) -> None: assert extract_method_header( - self.example_headers_with_bytes - ) == b'GET' + self.example_headers_with_bytes, + ) == b"GET" -def test_size_limit_dict_limit(): +def test_size_limit_dict_limit() -> None: dct = SizeLimitDict(size_limit=2) dct[1] = 1 @@ -182,7 +182,7 @@ def test_size_limit_dict_limit(): assert 1 not in dct -def test_size_limit_dict_limit_init(): +def test_size_limit_dict_limit_init() -> None: initial_dct = { 1: 1, 2: 2, @@ -194,7 +194,7 @@ def test_size_limit_dict_limit_init(): assert len(dct) == 2 -def test_size_limit_dict_no_limit(): +def test_size_limit_dict_no_limit() -> None: dct = SizeLimitDict(size_limit=None) dct[1] = 1