summaryrefslogtreecommitdiff
path: root/test/test_stream_reset.py
blob: 778445515fa244631f0e5dfe3bdb9b9c6885e164 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
# -*- coding: utf-8 -*-
"""
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.
"""
import pytest

import h2.connection
import h2.errors
import h2.events


class TestStreamReset(object):
    """
    Tests for resetting streams.
    """
    example_request_headers = [
        (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')
    ]

    def test_reset_stream_keeps_header_state_correct(self, frame_factory):
        """
        A stream that has been reset still affects the header decoder.
        """
        c = h2.connection.H2Connection()
        c.initiate_connection()
        c.send_headers(stream_id=1, headers=self.example_request_headers)
        c.reset_stream(stream_id=1)
        c.send_headers(stream_id=3, headers=self.example_request_headers)
        c.clear_outbound_data_buffer()

        f = frame_factory.build_headers_frame(
            headers=self.example_response_headers, stream_id=1
        )
        rst_frame = frame_factory.build_rst_stream_frame(
            1, h2.errors.ErrorCodes.STREAM_CLOSED
        )
        events = c.receive_data(f.serialize())
        assert not events
        assert c.data_to_send() == rst_frame.serialize()

        # 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
        )
        event = c.receive_data(f.serialize())[0]

        assert isinstance(event, h2.events.ResponseReceived)
        assert event.stream_id == 3
        assert event.headers == self.example_response_headers

    @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):
        """
        A stream that has been reset does not affect the connection flow
        control window.
        """
        c = h2.connection.H2Connection()
        c.initiate_connection()
        c.send_headers(stream_id=1, headers=self.example_request_headers)
        c.send_headers(stream_id=3, headers=self.example_request_headers)

        # Record the initial window size.
        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
        )
        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
        )
        c.receive_data(f.serialize())

        expected = frame_factory.build_rst_stream_frame(
            stream_id=close_id,
            error_code=h2.errors.ErrorCodes.STREAM_CLOSED,
        ).serialize()
        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

    @pytest.mark.parametrize('clear_streams', [True, False])
    def test_reset_stream_automatically_resets_pushed_streams(self,
                                                              frame_factory,
                                                              clear_streams):
        """
        Resetting a stream causes RST_STREAM frames to be automatically emitted
        to close any streams pushed after the reset.
        """
        c = h2.connection.H2Connection()
        c.initiate_connection()
        c.send_headers(stream_id=1, headers=self.example_request_headers)
        c.reset_stream(stream_id=1)
        c.clear_outbound_data_buffer()

        if clear_streams:
            # Call open_outbound_streams to force the connection to clean
            # closed streams.
            c.open_outbound_streams

        f = frame_factory.build_push_promise_frame(
            stream_id=1,
            promised_stream_id=2,
            headers=self.example_request_headers,
        )
        events = c.receive_data(f.serialize())
        assert not events

        f = frame_factory.build_rst_stream_frame(
            stream_id=2,
            error_code=h2.errors.ErrorCodes.REFUSED_STREAM,
        )
        assert c.data_to_send() == f.serialize()