QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 1 | // Copyright (c) 2018 The Chromium Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "net/third_party/quiche/src/quic/core/qpack/qpack_header_table.h" |
| 6 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 7 | #include "net/third_party/quiche/src/quic/core/qpack/qpack_static_table.h" |
| 8 | #include "net/third_party/quiche/src/quic/platform/api/quic_arraysize.h" |
| 9 | #include "net/third_party/quiche/src/quic/platform/api/quic_test.h" |
| 10 | #include "net/third_party/quiche/src/spdy/core/hpack/hpack_entry.h" |
| 11 | |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 12 | using ::testing::Mock; |
| 13 | using ::testing::StrictMock; |
| 14 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 15 | namespace quic { |
| 16 | namespace test { |
| 17 | namespace { |
| 18 | |
| 19 | const uint64_t kMaximumDynamicTableCapacityForTesting = 1024 * 1024; |
| 20 | |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 21 | class MockObserver : public QpackHeaderTable::Observer { |
| 22 | public: |
| 23 | ~MockObserver() override = default; |
| 24 | |
| 25 | MOCK_METHOD0(OnInsertCountReachedThreshold, void()); |
| 26 | }; |
| 27 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 28 | class QpackHeaderTableTest : public QuicTest { |
| 29 | protected: |
| 30 | QpackHeaderTableTest() { |
| 31 | table_.SetMaximumDynamicTableCapacity( |
| 32 | kMaximumDynamicTableCapacityForTesting); |
| 33 | } |
| 34 | ~QpackHeaderTableTest() override = default; |
| 35 | |
| 36 | void ExpectEntryAtIndex(bool is_static, |
| 37 | uint64_t index, |
| 38 | QuicStringPiece expected_name, |
| 39 | QuicStringPiece expected_value) const { |
| 40 | const auto* entry = table_.LookupEntry(is_static, index); |
| 41 | ASSERT_TRUE(entry); |
| 42 | EXPECT_EQ(expected_name, entry->name()); |
| 43 | EXPECT_EQ(expected_value, entry->value()); |
| 44 | } |
| 45 | |
| 46 | void ExpectNoEntryAtIndex(bool is_static, uint64_t index) const { |
| 47 | EXPECT_FALSE(table_.LookupEntry(is_static, index)); |
| 48 | } |
| 49 | |
| 50 | void ExpectMatch(QuicStringPiece name, |
| 51 | QuicStringPiece value, |
| 52 | QpackHeaderTable::MatchType expected_match_type, |
| 53 | bool expected_is_static, |
| 54 | uint64_t expected_index) const { |
| 55 | // Initialize outparams to a value different from the expected to ensure |
| 56 | // that FindHeaderField() sets them. |
| 57 | bool is_static = !expected_is_static; |
| 58 | uint64_t index = expected_index + 1; |
| 59 | |
| 60 | QpackHeaderTable::MatchType matchtype = |
| 61 | table_.FindHeaderField(name, value, &is_static, &index); |
| 62 | |
| 63 | EXPECT_EQ(expected_match_type, matchtype) << name << ": " << value; |
| 64 | EXPECT_EQ(expected_is_static, is_static) << name << ": " << value; |
| 65 | EXPECT_EQ(expected_index, index) << name << ": " << value; |
| 66 | } |
| 67 | |
| 68 | void ExpectNoMatch(QuicStringPiece name, QuicStringPiece value) const { |
| 69 | bool is_static = false; |
| 70 | uint64_t index = 0; |
| 71 | |
| 72 | QpackHeaderTable::MatchType matchtype = |
| 73 | table_.FindHeaderField(name, value, &is_static, &index); |
| 74 | |
| 75 | EXPECT_EQ(QpackHeaderTable::MatchType::kNoMatch, matchtype) |
| 76 | << name << ": " << value; |
| 77 | } |
| 78 | |
| 79 | void InsertEntry(QuicStringPiece name, QuicStringPiece value) { |
| 80 | EXPECT_TRUE(table_.InsertEntry(name, value)); |
| 81 | } |
| 82 | |
| 83 | void ExpectToFailInsertingEntry(QuicStringPiece name, QuicStringPiece value) { |
| 84 | EXPECT_FALSE(table_.InsertEntry(name, value)); |
| 85 | } |
| 86 | |
| 87 | bool SetDynamicTableCapacity(uint64_t capacity) { |
| 88 | return table_.SetDynamicTableCapacity(capacity); |
| 89 | } |
| 90 | |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 91 | void RegisterObserver(QpackHeaderTable::Observer* observer, |
| 92 | uint64_t required_insert_count) { |
| 93 | table_.RegisterObserver(observer, required_insert_count); |
| 94 | } |
| 95 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 96 | uint64_t max_entries() const { return table_.max_entries(); } |
| 97 | uint64_t inserted_entry_count() const { |
| 98 | return table_.inserted_entry_count(); |
| 99 | } |
| 100 | uint64_t dropped_entry_count() const { return table_.dropped_entry_count(); } |
| 101 | |
| 102 | private: |
| 103 | QpackHeaderTable table_; |
| 104 | }; |
| 105 | |
| 106 | TEST_F(QpackHeaderTableTest, LookupStaticEntry) { |
| 107 | ExpectEntryAtIndex(/* is_static = */ true, 0, ":authority", ""); |
| 108 | |
| 109 | ExpectEntryAtIndex(/* is_static = */ true, 1, ":path", "/"); |
| 110 | |
| 111 | // 98 is the last entry. |
| 112 | ExpectEntryAtIndex(/* is_static = */ true, 98, "x-frame-options", |
| 113 | "sameorigin"); |
| 114 | |
| 115 | ASSERT_EQ(99u, QpackStaticTableVector().size()); |
| 116 | ExpectNoEntryAtIndex(/* is_static = */ true, 99); |
| 117 | } |
| 118 | |
| 119 | TEST_F(QpackHeaderTableTest, InsertAndLookupDynamicEntry) { |
| 120 | // Dynamic table is initially entry. |
| 121 | ExpectNoEntryAtIndex(/* is_static = */ false, 0); |
| 122 | ExpectNoEntryAtIndex(/* is_static = */ false, 1); |
| 123 | ExpectNoEntryAtIndex(/* is_static = */ false, 2); |
| 124 | ExpectNoEntryAtIndex(/* is_static = */ false, 3); |
| 125 | |
| 126 | // Insert one entry. |
| 127 | InsertEntry("foo", "bar"); |
| 128 | |
| 129 | ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar"); |
| 130 | |
| 131 | ExpectNoEntryAtIndex(/* is_static = */ false, 1); |
| 132 | ExpectNoEntryAtIndex(/* is_static = */ false, 2); |
| 133 | ExpectNoEntryAtIndex(/* is_static = */ false, 3); |
| 134 | |
| 135 | // Insert a different entry. |
| 136 | InsertEntry("baz", "bing"); |
| 137 | |
| 138 | ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar"); |
| 139 | |
| 140 | ExpectEntryAtIndex(/* is_static = */ false, 1, "baz", "bing"); |
| 141 | |
| 142 | ExpectNoEntryAtIndex(/* is_static = */ false, 2); |
| 143 | ExpectNoEntryAtIndex(/* is_static = */ false, 3); |
| 144 | |
| 145 | // Insert an entry identical to the most recently inserted one. |
| 146 | InsertEntry("baz", "bing"); |
| 147 | |
| 148 | ExpectEntryAtIndex(/* is_static = */ false, 0, "foo", "bar"); |
| 149 | |
| 150 | ExpectEntryAtIndex(/* is_static = */ false, 1, "baz", "bing"); |
| 151 | |
| 152 | ExpectEntryAtIndex(/* is_static = */ false, 2, "baz", "bing"); |
| 153 | |
| 154 | ExpectNoEntryAtIndex(/* is_static = */ false, 3); |
| 155 | } |
| 156 | |
| 157 | TEST_F(QpackHeaderTableTest, FindStaticHeaderField) { |
| 158 | // A header name that has multiple entries with different values. |
| 159 | ExpectMatch(":method", "GET", QpackHeaderTable::MatchType::kNameAndValue, |
| 160 | true, 17u); |
| 161 | |
| 162 | ExpectMatch(":method", "POST", QpackHeaderTable::MatchType::kNameAndValue, |
| 163 | true, 20u); |
| 164 | |
| 165 | ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kName, true, |
| 166 | 15u); |
| 167 | |
| 168 | // A header name that has a single entry with non-empty value. |
| 169 | ExpectMatch("accept-encoding", "gzip, deflate, br", |
| 170 | QpackHeaderTable::MatchType::kNameAndValue, true, 31u); |
| 171 | |
| 172 | ExpectMatch("accept-encoding", "compress", QpackHeaderTable::MatchType::kName, |
| 173 | true, 31u); |
| 174 | |
| 175 | ExpectMatch("accept-encoding", "", QpackHeaderTable::MatchType::kName, true, |
| 176 | 31u); |
| 177 | |
| 178 | // A header name that has a single entry with empty value. |
| 179 | ExpectMatch("location", "", QpackHeaderTable::MatchType::kNameAndValue, true, |
| 180 | 12u); |
| 181 | |
| 182 | ExpectMatch("location", "foo", QpackHeaderTable::MatchType::kName, true, 12u); |
| 183 | |
| 184 | // No matching header name. |
| 185 | ExpectNoMatch("foo", ""); |
| 186 | ExpectNoMatch("foo", "bar"); |
| 187 | } |
| 188 | |
| 189 | TEST_F(QpackHeaderTableTest, FindDynamicHeaderField) { |
| 190 | // Dynamic table is initially entry. |
| 191 | ExpectNoMatch("foo", "bar"); |
| 192 | ExpectNoMatch("foo", "baz"); |
| 193 | |
| 194 | // Insert one entry. |
| 195 | InsertEntry("foo", "bar"); |
| 196 | |
| 197 | // Match name and value. |
| 198 | ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, false, |
| 199 | 0u); |
| 200 | |
| 201 | // Match name only. |
| 202 | ExpectMatch("foo", "baz", QpackHeaderTable::MatchType::kName, false, 0u); |
| 203 | |
| 204 | // Insert an identical entry. FindHeaderField() should return the index of |
| 205 | // the most recently inserted matching entry. |
| 206 | InsertEntry("foo", "bar"); |
| 207 | |
| 208 | // Match name and value. |
| 209 | ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, false, |
| 210 | 1u); |
| 211 | |
| 212 | // Match name only. |
| 213 | ExpectMatch("foo", "baz", QpackHeaderTable::MatchType::kName, false, 1u); |
| 214 | } |
| 215 | |
| 216 | TEST_F(QpackHeaderTableTest, FindHeaderFieldPrefersStaticTable) { |
| 217 | // Insert an entry to the dynamic table that exists in the static table. |
| 218 | InsertEntry(":method", "GET"); |
| 219 | |
| 220 | // Insertion works. |
| 221 | ExpectEntryAtIndex(/* is_static = */ false, 0, ":method", "GET"); |
| 222 | |
| 223 | // FindHeaderField() prefers static table if both have name-and-value match. |
| 224 | ExpectMatch(":method", "GET", QpackHeaderTable::MatchType::kNameAndValue, |
| 225 | true, 17u); |
| 226 | |
| 227 | // FindHeaderField() prefers static table if both have name match but no value |
| 228 | // match, and prefers the first entry with matching name. |
| 229 | ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kName, true, |
| 230 | 15u); |
| 231 | |
| 232 | // Add new entry to the dynamic table. |
| 233 | InsertEntry(":method", "TRACE"); |
| 234 | |
| 235 | // FindHeaderField prefers name-and-value match in dynamic table over name |
| 236 | // only match in static table. |
| 237 | ExpectMatch(":method", "TRACE", QpackHeaderTable::MatchType::kNameAndValue, |
| 238 | false, 1u); |
| 239 | } |
| 240 | |
| 241 | // MaxEntries is determined by maximum dynamic table capacity, |
| 242 | // which is set at construction time. |
| 243 | TEST_F(QpackHeaderTableTest, MaxEntries) { |
| 244 | QpackHeaderTable table1; |
| 245 | table1.SetMaximumDynamicTableCapacity(1024); |
| 246 | EXPECT_EQ(32u, table1.max_entries()); |
| 247 | |
| 248 | QpackHeaderTable table2; |
| 249 | table2.SetMaximumDynamicTableCapacity(500); |
| 250 | EXPECT_EQ(15u, table2.max_entries()); |
| 251 | } |
| 252 | |
| 253 | TEST_F(QpackHeaderTableTest, SetDynamicTableCapacity) { |
| 254 | // Dynamic table capacity does not affect MaxEntries. |
| 255 | EXPECT_TRUE(SetDynamicTableCapacity(1024)); |
| 256 | EXPECT_EQ(32u * 1024, max_entries()); |
| 257 | |
| 258 | EXPECT_TRUE(SetDynamicTableCapacity(500)); |
| 259 | EXPECT_EQ(32u * 1024, max_entries()); |
| 260 | |
| 261 | // Dynamic table capacity cannot exceed maximum dynamic table capacity. |
| 262 | EXPECT_FALSE( |
| 263 | SetDynamicTableCapacity(2 * kMaximumDynamicTableCapacityForTesting)); |
| 264 | } |
| 265 | |
| 266 | TEST_F(QpackHeaderTableTest, EvictByInsertion) { |
| 267 | EXPECT_TRUE(SetDynamicTableCapacity(40)); |
| 268 | |
| 269 | // Entry size is 3 + 3 + 32 = 38. |
| 270 | InsertEntry("foo", "bar"); |
| 271 | EXPECT_EQ(1u, inserted_entry_count()); |
| 272 | EXPECT_EQ(0u, dropped_entry_count()); |
| 273 | |
| 274 | ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, |
| 275 | /* expected_is_static = */ false, 0u); |
| 276 | |
| 277 | // Inserting second entry evicts the first one. |
| 278 | InsertEntry("baz", "qux"); |
| 279 | EXPECT_EQ(2u, inserted_entry_count()); |
| 280 | EXPECT_EQ(1u, dropped_entry_count()); |
| 281 | |
| 282 | ExpectNoMatch("foo", "bar"); |
| 283 | ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue, |
| 284 | /* expected_is_static = */ false, 1u); |
| 285 | |
| 286 | // Inserting an entry that does not fit results in error. |
| 287 | ExpectToFailInsertingEntry("foobar", "foobar"); |
| 288 | } |
| 289 | |
| 290 | TEST_F(QpackHeaderTableTest, EvictByUpdateTableSize) { |
| 291 | // Entry size is 3 + 3 + 32 = 38. |
| 292 | InsertEntry("foo", "bar"); |
| 293 | InsertEntry("baz", "qux"); |
| 294 | EXPECT_EQ(2u, inserted_entry_count()); |
| 295 | EXPECT_EQ(0u, dropped_entry_count()); |
| 296 | |
| 297 | ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, |
| 298 | /* expected_is_static = */ false, 0u); |
| 299 | ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue, |
| 300 | /* expected_is_static = */ false, 1u); |
| 301 | |
| 302 | EXPECT_TRUE(SetDynamicTableCapacity(40)); |
| 303 | EXPECT_EQ(2u, inserted_entry_count()); |
| 304 | EXPECT_EQ(1u, dropped_entry_count()); |
| 305 | |
| 306 | ExpectNoMatch("foo", "bar"); |
| 307 | ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue, |
| 308 | /* expected_is_static = */ false, 1u); |
| 309 | |
| 310 | EXPECT_TRUE(SetDynamicTableCapacity(20)); |
| 311 | EXPECT_EQ(2u, inserted_entry_count()); |
| 312 | EXPECT_EQ(2u, dropped_entry_count()); |
| 313 | |
| 314 | ExpectNoMatch("foo", "bar"); |
| 315 | ExpectNoMatch("baz", "qux"); |
| 316 | } |
| 317 | |
| 318 | TEST_F(QpackHeaderTableTest, EvictOldestOfIdentical) { |
| 319 | EXPECT_TRUE(SetDynamicTableCapacity(80)); |
| 320 | |
| 321 | // Entry size is 3 + 3 + 32 = 38. |
| 322 | // Insert same entry twice. |
| 323 | InsertEntry("foo", "bar"); |
| 324 | InsertEntry("foo", "bar"); |
| 325 | EXPECT_EQ(2u, inserted_entry_count()); |
| 326 | EXPECT_EQ(0u, dropped_entry_count()); |
| 327 | |
| 328 | // Find most recently inserted entry. |
| 329 | ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, |
| 330 | /* expected_is_static = */ false, 1u); |
| 331 | |
| 332 | // Inserting third entry evicts the first one, not the second. |
| 333 | InsertEntry("baz", "qux"); |
| 334 | EXPECT_EQ(3u, inserted_entry_count()); |
| 335 | EXPECT_EQ(1u, dropped_entry_count()); |
| 336 | |
| 337 | ExpectMatch("foo", "bar", QpackHeaderTable::MatchType::kNameAndValue, |
| 338 | /* expected_is_static = */ false, 1u); |
| 339 | ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue, |
| 340 | /* expected_is_static = */ false, 2u); |
| 341 | } |
| 342 | |
| 343 | TEST_F(QpackHeaderTableTest, EvictOldestOfSameName) { |
| 344 | EXPECT_TRUE(SetDynamicTableCapacity(80)); |
| 345 | |
| 346 | // Entry size is 3 + 3 + 32 = 38. |
| 347 | // Insert two entries with same name but different values. |
| 348 | InsertEntry("foo", "bar"); |
| 349 | InsertEntry("foo", "baz"); |
| 350 | EXPECT_EQ(2u, inserted_entry_count()); |
| 351 | EXPECT_EQ(0u, dropped_entry_count()); |
| 352 | |
| 353 | // Find most recently inserted entry with matching name. |
| 354 | ExpectMatch("foo", "foo", QpackHeaderTable::MatchType::kName, |
| 355 | /* expected_is_static = */ false, 1u); |
| 356 | |
| 357 | // Inserting third entry evicts the first one, not the second. |
| 358 | InsertEntry("baz", "qux"); |
| 359 | EXPECT_EQ(3u, inserted_entry_count()); |
| 360 | EXPECT_EQ(1u, dropped_entry_count()); |
| 361 | |
| 362 | ExpectMatch("foo", "foo", QpackHeaderTable::MatchType::kName, |
| 363 | /* expected_is_static = */ false, 1u); |
| 364 | ExpectMatch("baz", "qux", QpackHeaderTable::MatchType::kNameAndValue, |
| 365 | /* expected_is_static = */ false, 2u); |
| 366 | } |
| 367 | |
bnc | 7a1c21c | 2019-07-08 19:09:50 -0700 | [diff] [blame] | 368 | TEST_F(QpackHeaderTableTest, Observer) { |
| 369 | StrictMock<MockObserver> observer1; |
| 370 | RegisterObserver(&observer1, 1); |
| 371 | EXPECT_CALL(observer1, OnInsertCountReachedThreshold); |
| 372 | InsertEntry("foo", "bar"); |
| 373 | EXPECT_EQ(1u, inserted_entry_count()); |
| 374 | Mock::VerifyAndClearExpectations(&observer1); |
| 375 | |
| 376 | // Registration order does not matter. |
| 377 | StrictMock<MockObserver> observer2; |
| 378 | StrictMock<MockObserver> observer3; |
| 379 | RegisterObserver(&observer3, 3); |
| 380 | RegisterObserver(&observer2, 2); |
| 381 | |
| 382 | EXPECT_CALL(observer2, OnInsertCountReachedThreshold); |
| 383 | InsertEntry("foo", "bar"); |
| 384 | EXPECT_EQ(2u, inserted_entry_count()); |
| 385 | Mock::VerifyAndClearExpectations(&observer3); |
| 386 | |
| 387 | EXPECT_CALL(observer3, OnInsertCountReachedThreshold); |
| 388 | InsertEntry("foo", "bar"); |
| 389 | EXPECT_EQ(3u, inserted_entry_count()); |
| 390 | Mock::VerifyAndClearExpectations(&observer2); |
| 391 | |
| 392 | // Multiple observers with identical |required_insert_count| should all be |
| 393 | // notified. |
| 394 | StrictMock<MockObserver> observer4; |
| 395 | StrictMock<MockObserver> observer5; |
| 396 | RegisterObserver(&observer4, 4); |
| 397 | RegisterObserver(&observer5, 4); |
| 398 | |
| 399 | EXPECT_CALL(observer4, OnInsertCountReachedThreshold); |
| 400 | EXPECT_CALL(observer5, OnInsertCountReachedThreshold); |
| 401 | InsertEntry("foo", "bar"); |
| 402 | EXPECT_EQ(4u, inserted_entry_count()); |
| 403 | Mock::VerifyAndClearExpectations(&observer4); |
| 404 | Mock::VerifyAndClearExpectations(&observer5); |
| 405 | } |
| 406 | |
bnc | b34a7ec | 2019-07-25 08:53:44 -0700 | [diff] [blame] | 407 | TEST_F(QpackHeaderTableTest, DrainingIndex) { |
| 408 | QpackHeaderTable table; |
| 409 | table.SetMaximumDynamicTableCapacity(4 * QpackEntry::Size("foo", "bar")); |
| 410 | |
| 411 | // Empty table: no draining entry. |
| 412 | EXPECT_EQ(0u, table.draining_index(0.0)); |
| 413 | EXPECT_EQ(0u, table.draining_index(1.0)); |
| 414 | |
| 415 | // Table with one entry. |
| 416 | EXPECT_TRUE(table.InsertEntry("foo", "bar")); |
| 417 | // Any entry can be referenced if none of the table is draining. |
| 418 | EXPECT_EQ(0u, table.draining_index(0.0)); |
| 419 | // No entry can be referenced if all of the table is draining. |
| 420 | EXPECT_EQ(1u, table.draining_index(1.0)); |
| 421 | |
| 422 | // Table with two entries is at half capacity. |
| 423 | EXPECT_TRUE(table.InsertEntry("foo", "bar")); |
| 424 | // Any entry can be referenced if at most half of the table is draining, |
| 425 | // because current entries only take up half of total capacity. |
| 426 | EXPECT_EQ(0u, table.draining_index(0.0)); |
| 427 | EXPECT_EQ(0u, table.draining_index(0.5)); |
| 428 | // No entry can be referenced if all of the table is draining. |
| 429 | EXPECT_EQ(2u, table.draining_index(1.0)); |
| 430 | |
| 431 | // Table with four entries is full. |
| 432 | EXPECT_TRUE(table.InsertEntry("foo", "bar")); |
| 433 | EXPECT_TRUE(table.InsertEntry("foo", "bar")); |
| 434 | // Any entry can be referenced if none of the table is draining. |
| 435 | EXPECT_EQ(0u, table.draining_index(0.0)); |
| 436 | // In a full table with identically sized entries, |draining_fraction| of all |
| 437 | // entries are draining. |
| 438 | EXPECT_EQ(2u, table.draining_index(0.5)); |
| 439 | // No entry can be referenced if all of the table is draining. |
| 440 | EXPECT_EQ(4u, table.draining_index(1.0)); |
| 441 | } |
| 442 | |
QUICHE team | a6ef0a6 | 2019-03-07 20:34:33 -0500 | [diff] [blame] | 443 | } // namespace |
| 444 | } // namespace test |
| 445 | } // namespace quic |