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