1# -*- coding: utf-8 -*-
2"""
3test_settings
4~~~~~~~~~~~~~
5
6Test the Settings object.
7"""
8import pytest
9
10import h2.errors
11import h2.exceptions
12import h2.settings
13
14from hypothesis import given, assume
15from hypothesis.strategies import integers
16
17
18class TestSettings(object):
19    """
20    Test the Settings object behaves as expected.
21    """
22    def test_settings_defaults_client(self):
23        """
24        The Settings object begins with the appropriate defaults for clients.
25        """
26        s = h2.settings.Settings(client=True)
27
28        assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096
29        assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 1
30        assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535
31        assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384
32
33    def test_settings_defaults_server(self):
34        """
35        The Settings object begins with the appropriate defaults for servers.
36        """
37        s = h2.settings.Settings(client=False)
38
39        assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096
40        assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 0
41        assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535
42        assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384
43
44    @pytest.mark.parametrize('client', [True, False])
45    def test_can_set_initial_values(self, client):
46        """
47        The Settings object can be provided initial values that override the
48        defaults.
49        """
50        overrides = {
51            h2.settings.SettingCodes.HEADER_TABLE_SIZE: 8080,
52            h2.settings.SettingCodes.MAX_FRAME_SIZE: 16388,
53            h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
54            h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 2**16,
55        }
56        s = h2.settings.Settings(client=client, initial_values=overrides)
57
58        assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8080
59        assert s[h2.settings.SettingCodes.ENABLE_PUSH] == bool(client)
60        assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535
61        assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16388
62        assert s[h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS] == 100
63        assert s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] == 2**16
64
65    @pytest.mark.parametrize(
66        'setting,value',
67        [
68            (h2.settings.SettingCodes.ENABLE_PUSH, 2),
69            (h2.settings.SettingCodes.ENABLE_PUSH, -1),
70            (h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, -1),
71            (h2.settings.SettingCodes.INITIAL_WINDOW_SIZE, 2**34),
72            (h2.settings.SettingCodes.MAX_FRAME_SIZE, 1),
73            (h2.settings.SettingCodes.MAX_FRAME_SIZE, 2**30),
74            (h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE, -1),
75        ]
76    )
77    def test_cannot_set_invalid_initial_values(self, setting, value):
78        """
79        The Settings object can be provided initial values that override the
80        defaults.
81        """
82        overrides = {setting: value}
83
84        with pytest.raises(h2.exceptions.InvalidSettingsValueError):
85            h2.settings.Settings(initial_values=overrides)
86
87    def test_applying_value_doesnt_take_effect_immediately(self):
88        """
89        When a value is applied to the settings object, it doesn't immediately
90        take effect.
91        """
92        s = h2.settings.Settings(client=True)
93        s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8000
94
95        assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096
96
97    def test_acknowledging_values(self):
98        """
99        When we acknowledge settings, the values change.
100        """
101        s = h2.settings.Settings(client=True)
102        old_settings = dict(s)
103
104        new_settings = {
105            h2.settings.SettingCodes.HEADER_TABLE_SIZE: 4000,
106            h2.settings.SettingCodes.ENABLE_PUSH: 0,
107            h2.settings.SettingCodes.INITIAL_WINDOW_SIZE: 60,
108            h2.settings.SettingCodes.MAX_FRAME_SIZE: 16385,
109        }
110        s.update(new_settings)
111
112        assert dict(s) == old_settings
113        s.acknowledge()
114        assert dict(s) == new_settings
115
116    def test_acknowledging_returns_the_changed_settings(self):
117        """
118        Acknowledging settings returns the changes.
119        """
120        s = h2.settings.Settings(client=True)
121        s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] = 8000
122        s[h2.settings.SettingCodes.ENABLE_PUSH] = 0
123
124        changes = s.acknowledge()
125        assert len(changes) == 2
126
127        table_size_change = (
128            changes[h2.settings.SettingCodes.HEADER_TABLE_SIZE]
129        )
130        push_change = changes[h2.settings.SettingCodes.ENABLE_PUSH]
131
132        assert table_size_change.setting == (
133            h2.settings.SettingCodes.HEADER_TABLE_SIZE
134        )
135        assert table_size_change.original_value == 4096
136        assert table_size_change.new_value == 8000
137
138        assert push_change.setting == h2.settings.SettingCodes.ENABLE_PUSH
139        assert push_change.original_value == 1
140        assert push_change.new_value == 0
141
142    def test_acknowledging_only_returns_changed_settings(self):
143        """
144        Acknowledging settings does not return unchanged settings.
145        """
146        s = h2.settings.Settings(client=True)
147        s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] = 70
148
149        changes = s.acknowledge()
150        assert len(changes) == 1
151        assert list(changes.keys()) == [
152            h2.settings.SettingCodes.INITIAL_WINDOW_SIZE
153        ]
154
155    def test_deleting_values_deletes_all_of_them(self):
156        """
157        When we delete a key we lose all state about it.
158        """
159        s = h2.settings.Settings(client=True)
160        s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8000
161
162        del s[h2.settings.SettingCodes.HEADER_TABLE_SIZE]
163
164        with pytest.raises(KeyError):
165            s[h2.settings.SettingCodes.HEADER_TABLE_SIZE]
166
167    def test_length_correctly_reported(self):
168        """
169        Length is related only to the number of keys.
170        """
171        s = h2.settings.Settings(client=True)
172        assert len(s) == 4
173
174        s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 8000
175        assert len(s) == 4
176
177        s.acknowledge()
178        assert len(s) == 4
179
180        del s[h2.settings.SettingCodes.HEADER_TABLE_SIZE]
181        assert len(s) == 3
182
183    def test_new_values_work(self):
184        """
185        New values initially don't appear
186        """
187        s = h2.settings.Settings(client=True)
188        s[80] = 81
189
190        with pytest.raises(KeyError):
191            s[80]
192
193    def test_new_values_follow_basic_acknowledgement_rules(self):
194        """
195        A new value properly appears when acknowledged.
196        """
197        s = h2.settings.Settings(client=True)
198        s[80] = 81
199        changed_settings = s.acknowledge()
200
201        assert s[80] == 81
202        assert len(changed_settings) == 1
203
204        changed = changed_settings[80]
205        assert changed.setting == 80
206        assert changed.original_value is None
207        assert changed.new_value == 81
208
209    def test_single_values_arent_affected_by_acknowledgement(self):
210        """
211        When acknowledged, unchanged settings remain unchanged.
212        """
213        s = h2.settings.Settings(client=True)
214        assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096
215
216        s.acknowledge()
217        assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 4096
218
219    def test_settings_getters(self):
220        """
221        Getters exist for well-known settings.
222        """
223        s = h2.settings.Settings(client=True)
224
225        assert s.header_table_size == (
226            s[h2.settings.SettingCodes.HEADER_TABLE_SIZE]
227        )
228        assert s.enable_push == s[h2.settings.SettingCodes.ENABLE_PUSH]
229        assert s.initial_window_size == (
230            s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE]
231        )
232        assert s.max_frame_size == s[h2.settings.SettingCodes.MAX_FRAME_SIZE]
233        assert s.max_concurrent_streams == 2**32 + 1  # A sensible default.
234        assert s.max_header_list_size is None
235
236    def test_settings_setters(self):
237        """
238        Setters exist for well-known settings.
239        """
240        s = h2.settings.Settings(client=True)
241
242        s.header_table_size = 0
243        s.enable_push = 1
244        s.initial_window_size = 2
245        s.max_frame_size = 16385
246        s.max_concurrent_streams = 4
247        s.max_header_list_size = 2**16
248
249        s.acknowledge()
250        assert s[h2.settings.SettingCodes.HEADER_TABLE_SIZE] == 0
251        assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 1
252        assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 2
253        assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16385
254        assert s[h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS] == 4
255        assert s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] == 2**16
256
257    @given(integers())
258    def test_cannot_set_invalid_values_for_enable_push(self, val):
259        """
260        SETTINGS_ENABLE_PUSH only allows two values: 0, 1.
261        """
262        assume(val not in (0, 1))
263        s = h2.settings.Settings()
264
265        with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
266            s.enable_push = val
267
268        s.acknowledge()
269        assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR
270        assert s.enable_push == 1
271
272        with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
273            s[h2.settings.SettingCodes.ENABLE_PUSH] = val
274
275        s.acknowledge()
276        assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR
277        assert s[h2.settings.SettingCodes.ENABLE_PUSH] == 1
278
279    @given(integers())
280    def test_cannot_set_invalid_vals_for_initial_window_size(self, val):
281        """
282        SETTINGS_INITIAL_WINDOW_SIZE only allows values between 0 and 2**32 - 1
283        inclusive.
284        """
285        s = h2.settings.Settings()
286
287        if 0 <= val <= 2**31 - 1:
288            s.initial_window_size = val
289            s.acknowledge()
290            assert s.initial_window_size == val
291        else:
292            with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
293                s.initial_window_size = val
294
295            s.acknowledge()
296            assert (
297                e.value.error_code == h2.errors.ErrorCodes.FLOW_CONTROL_ERROR
298            )
299            assert s.initial_window_size == 65535
300
301            with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
302                s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] = val
303
304            s.acknowledge()
305            assert (
306                e.value.error_code == h2.errors.ErrorCodes.FLOW_CONTROL_ERROR
307            )
308            assert s[h2.settings.SettingCodes.INITIAL_WINDOW_SIZE] == 65535
309
310    @given(integers())
311    def test_cannot_set_invalid_values_for_max_frame_size(self, val):
312        """
313        SETTINGS_MAX_FRAME_SIZE only allows values between 2**14 and 2**24 - 1.
314        """
315        s = h2.settings.Settings()
316
317        if 2**14 <= val <= 2**24 - 1:
318            s.max_frame_size = val
319            s.acknowledge()
320            assert s.max_frame_size == val
321        else:
322            with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
323                s.max_frame_size = val
324
325            s.acknowledge()
326            assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR
327            assert s.max_frame_size == 16384
328
329            with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
330                s[h2.settings.SettingCodes.MAX_FRAME_SIZE] = val
331
332            s.acknowledge()
333            assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR
334            assert s[h2.settings.SettingCodes.MAX_FRAME_SIZE] == 16384
335
336    @given(integers())
337    def test_cannot_set_invalid_values_for_max_header_list_size(self, val):
338        """
339        SETTINGS_MAX_HEADER_LIST_SIZE only allows non-negative values.
340        """
341        s = h2.settings.Settings()
342
343        if val >= 0:
344            s.max_header_list_size = val
345            s.acknowledge()
346            assert s.max_header_list_size == val
347        else:
348            with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
349                s.max_header_list_size = val
350
351            s.acknowledge()
352            assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR
353            assert s.max_header_list_size is None
354
355            with pytest.raises(h2.exceptions.InvalidSettingsValueError) as e:
356                s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE] = val
357
358            s.acknowledge()
359            assert e.value.error_code == h2.errors.ErrorCodes.PROTOCOL_ERROR
360
361            with pytest.raises(KeyError):
362                s[h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE]
363
364
365class TestSettingsEquality(object):
366    """
367    A class defining tests for the standard implementation of == and != .
368    """
369
370    def an_instance(self):
371        """
372        Return an instance of the class under test.  Each call to this method
373        must return a different object.  All objects returned must be equal to
374        each other.
375        """
376        overrides = {
377            h2.settings.SettingCodes.HEADER_TABLE_SIZE: 0,
378            h2.settings.SettingCodes.MAX_FRAME_SIZE: 16384,
379            h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 4,
380            h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 2**16,
381        }
382        return h2.settings.Settings(client=True, initial_values=overrides)
383
384    def another_instance(self):
385        """
386        Return an instance of the class under test.  Each call to this method
387        must return a different object.  The objects must not be equal to the
388        objects returned by an_instance.  They may or may not be equal to
389        each other (they will not be compared against each other).
390        """
391        overrides = {
392            h2.settings.SettingCodes.HEADER_TABLE_SIZE: 8080,
393            h2.settings.SettingCodes.MAX_FRAME_SIZE: 16388,
394            h2.settings.SettingCodes.MAX_CONCURRENT_STREAMS: 100,
395            h2.settings.SettingCodes.MAX_HEADER_LIST_SIZE: 2**16,
396        }
397        return h2.settings.Settings(client=False, initial_values=overrides)
398
399    def test_identical_eq(self):
400        """
401        An object compares equal to itself using the == operator.
402        """
403        o = self.an_instance()
404        assert (o == o)
405
406    def test_identical_ne(self):
407        """
408        An object doesn't compare not equal to itself using the != operator.
409        """
410        o = self.an_instance()
411        assert not (o != o)
412
413    def test_same_eq(self):
414        """
415        Two objects that are equal to each other compare equal to each other
416        using the == operator.
417        """
418        a = self.an_instance()
419        b = self.an_instance()
420        assert (a == b)
421
422    def test_same_ne(self):
423        """
424        Two objects that are equal to each other do not compare not equal to
425        each other using the != operator.
426        """
427        a = self.an_instance()
428        b = self.an_instance()
429        assert not (a != b)
430
431    def test_different_eq(self):
432        """
433        Two objects that are not equal to each other do not compare equal to
434        each other using the == operator.
435        """
436        a = self.an_instance()
437        b = self.another_instance()
438        assert not (a == b)
439
440    def test_different_ne(self):
441        """
442        Two objects that are not equal to each other compare not equal to each
443        other using the != operator.
444        """
445        a = self.an_instance()
446        b = self.another_instance()
447        assert (a != b)
448
449    def test_another_type_eq(self):
450        """
451        The object does not compare equal to an object of an unrelated type
452        (which does not implement the comparison) using the == operator.
453        """
454        a = self.an_instance()
455        b = object()
456        assert not (a == b)
457
458    def test_another_type_ne(self):
459        """
460        The object compares not equal to an object of an unrelated type (which
461        does not implement the comparison) using the != operator.
462        """
463        a = self.an_instance()
464        b = object()
465        assert (a != b)
466
467    def test_delegated_eq(self):
468        """
469        The result of comparison using == is delegated to the right-hand
470        operand if it is of an unrelated type.
471        """
472        class Delegate(object):
473            def __eq__(self, other):
474                return [self]
475
476        a = self.an_instance()
477        b = Delegate()
478        assert (a == b) == [b]
479
480    def test_delegate_ne(self):
481        """
482        The result of comparison using != is delegated to the right-hand
483        operand if it is of an unrelated type.
484        """
485        class Delegate(object):
486            def __ne__(self, other):
487                return [self]
488
489        a = self.an_instance()
490        b = Delegate()
491        assert (a != b) == [b]
492