Skip to content

Commit 027d8df

Browse files
Improve map tests:
- Make sure the hash flooding test actually does hash flooding. The technique used to choose the inputs stopped working when random seeding was added. Now we assert trees are used in the test so it won't silently break again. - Improve test coverage for tree buckets. - Update the hash flood test to use absl::Time/Duration. - Improve the map tests to use long strings. This way we can test that the values are being properly destroyed. Small strings could be leaked without side effects because of SSO. PiperOrigin-RevId: 530917835
1 parent 64cf6ff commit 027d8df

File tree

6 files changed

+156
-87
lines changed

6 files changed

+156
-87
lines changed

src/google/protobuf/BUILD.bazel

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,6 +849,7 @@ cc_library(
849849
"//src/google/protobuf/testing",
850850
"@com_google_absl//absl/container:flat_hash_map",
851851
"@com_google_absl//absl/log:absl_check",
852+
"@com_google_absl//absl/time",
852853
"@com_google_googletest//:gtest",
853854
],
854855
)
@@ -980,9 +981,9 @@ cc_library(
980981
visibility = ["//pkg:__pkg__"],
981982
deps = [
982983
":protobuf",
983-
"@com_google_googletest//:gtest",
984984
"@com_google_absl//absl/log:absl_check",
985985
"@com_google_absl//absl/memory",
986+
"@com_google_googletest//:gtest",
986987
],
987988
)
988989

src/google/protobuf/map_test.inc

Lines changed: 83 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
#include "absl/log/absl_check.h"
5757
#include "absl/log/absl_log.h"
5858
#include "absl/strings/substitute.h"
59+
#include "absl/time/time.h"
5960
#include "google/protobuf/arena_test_util.h"
6061
#include "google/protobuf/descriptor.h"
6162
#include "google/protobuf/descriptor_database.h"
@@ -73,7 +74,6 @@
7374
#include "google/protobuf/test_util2.h"
7475
#include "google/protobuf/text_format.h"
7576
#include "google/protobuf/util/message_differencer.h"
76-
#include "google/protobuf/util/time_util.h"
7777
#include "google/protobuf/wire_format.h"
7878

7979

@@ -204,6 +204,24 @@ struct MapTestPeer {
204204
}
205205
return true;
206206
}
207+
208+
template <typename T>
209+
static size_t BucketNumber(T& map, typename T::key_type key) {
210+
return map.BucketNumber(key);
211+
}
212+
213+
template <typename T>
214+
static void Resize(T& map, size_t num_buckets) {
215+
map.Resize(num_buckets);
216+
}
217+
218+
template <typename T>
219+
static bool HasTreeBuckets(T& map) {
220+
for (size_t i = 0; i < map.num_buckets_; ++i) {
221+
if (TableEntryIsTree(map.table_[i])) return true;
222+
}
223+
return false;
224+
}
207225
};
208226

209227
namespace {
@@ -463,57 +481,87 @@ TEST_F(MapImplTest, IteratorBasic) {
463481
EXPECT_TRUE(it == cit);
464482
}
465483

466-
template <typename Iterator>
467-
static int64_t median(Iterator i0, Iterator i1) {
468-
std::vector<int64_t> v(i0, i1);
484+
static absl::Duration median(absl::Span<absl::Duration> v) {
469485
std::nth_element(v.begin(), v.begin() + v.size() / 2, v.end());
470486
return v[v.size() / 2];
471487
}
472488

473-
static int64_t Now() {
474-
return util::TimeUtil::TimestampToNanoseconds(
475-
util::TimeUtil::GetCurrentTime());
476-
}
477-
478489
// Arbitrary odd integers for creating test data.
479490
static int k0 = 812398771;
480491
static int k1 = 1312938717;
481492
static int k2 = 1321555333;
482493

483-
// Try to create kTestSize keys that will land in just a few buckets, and
484-
// time the insertions, to get a rough estimate of whether an O(n^2) worst case
485-
// was triggered. This test is a hacky, but probably better than nothing.
486-
TEST_F(MapImplTest, HashFlood) {
487-
const int kTestSize = 1024; // must be a power of 2
488-
absl::btree_set<int> s;
489-
for (int i = 0; s.size() < kTestSize; i++) {
490-
if ((map_.hash_function()(i) & (kTestSize - 1)) < 3) {
491-
s.insert(i);
494+
// Finds inputs that will fall in the first few buckets for this particular map
495+
// (with the random seed it has) and this particular size.
496+
static std::vector<int> FindBadInputs(Map<int, int>& map, int num_inputs) {
497+
// Make sure the seed and the size is set so that BucketNumber works.
498+
while (map.size() < num_inputs) map[map.size()];
499+
map.clear();
500+
501+
std::vector<int> out;
502+
503+
for (int i = 0; out.size() < num_inputs; ++i) {
504+
if (MapTestPeer::BucketNumber(map, i) < 3) {
505+
out.push_back(i);
492506
}
493507
}
494-
// Create hash table with kTestSize entries that hash flood a table with
495-
// 1024 (or 512 or 2048 or ...) entries. This assumes that map_ uses powers
496-
// of 2 for table sizes, and that it's sufficient to "flood" with respect to
497-
// the low bits of the output of map_.hash_function().
498-
std::vector<int64_t> times;
499-
auto it = s.begin();
508+
509+
// Reset the table to get it to grow from scratch again.
510+
// The capacity will be lost, but we will get it again on insertion.
511+
// It will also keep the seed.
512+
map.clear();
513+
MapTestPeer::Resize(map, 8);
514+
515+
return out;
516+
}
517+
518+
TEST_F(MapImplTest, TreePathWorksAsExpected) {
519+
const std::vector<int> s = FindBadInputs(map_, 1000);
520+
521+
for (int i : s) {
522+
map_[i] = 0;
523+
}
524+
// Make sure we are testing what we think we are testing.
525+
ASSERT_TRUE(MapTestPeer::HasTreeBuckets(map_));
526+
for (int i : s) {
527+
ASSERT_NE(map_.find(i), map_.end()) << i;
528+
}
529+
for (int i : s) {
530+
ASSERT_EQ(1, map_.erase(i)) << i;
531+
}
532+
EXPECT_FALSE(MapTestPeer::HasTreeBuckets(map_));
533+
EXPECT_TRUE(map_.empty());
534+
}
535+
536+
// Create kTestSize keys that will land in just a few buckets, and time the
537+
// insertions, to get a rough estimate of whether an O(n^2) worst case was
538+
// triggered. This test is a hacky, but probably better than nothing.
539+
TEST_F(MapImplTest, HashFlood) {
540+
const std::vector<int> s = FindBadInputs(map_, 1000);
541+
542+
// Create hash table with 1000 entries that hash flood a table. The entries
543+
// were chosen so that they all fall in a few buckets.
544+
std::vector<absl::Duration> times;
500545
int count = 0;
501-
do {
502-
const int64_t start = Now();
503-
map_[*it] = 0;
504-
const int64_t end = Now();
546+
for (int i : s) {
547+
const auto start = absl::Now();
548+
map_[i] = 0;
549+
const auto end = absl::Now();
505550
if (end > start) {
506551
times.push_back(end - start);
507552
}
508553
++count;
509-
++it;
510-
} while (it != s.end());
554+
}
511555
if (times.size() < .99 * count) return;
512-
int64_t x0 = median(times.begin(), times.begin() + 9);
513-
int64_t x1 = median(times.begin() + times.size() - 9, times.end());
514-
// x1 will greatly exceed x0 if the code we just executed took O(n^2) time.
515-
// But we want to allow O(n log n). A factor of 20 should be generous enough.
516-
EXPECT_LE(x1, x0 * 20);
556+
const auto x0 = median(absl::MakeSpan(times).subspan(0, 9));
557+
const auto x1 = median(absl::MakeSpan(times).subspan(times.size() - 9));
558+
ABSL_LOG(INFO) << "number of samples=" << times.size() << " x0=" << x0
559+
<< " x1=" << x1;
560+
// x1 will greatly exceed x0 if inserting the elements was O(n) time.
561+
// We know it will not be O(1) because of all the collisions, but we want to
562+
// allow O(log n).
563+
// lg2(1000) is ~10, times a constant for the cost of the tree.
564+
EXPECT_LE(x1, x0 * 50);
517565
}
518566

519567
TEST_F(MapImplTest, CopyIteratorStressTest) {

src/google/protobuf/map_test_util_impl.h

Lines changed: 31 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
#ifndef GOOGLE_PROTOBUF_MAP_TEST_UTIL_IMPL_H__
3232
#define GOOGLE_PROTOBUF_MAP_TEST_UTIL_IMPL_H__
3333

34+
#include <string>
35+
3436
#include <gtest/gtest.h>
3537

3638

@@ -100,6 +102,13 @@ class MapTestUtilImpl {
100102
// // Get pointers of map entries from release.
101103
// static std::vector<const Message*> GetMapEntriesFromRelease(
102104
// MapMessage* message);
105+
106+
static std::string long_string() {
107+
return "This is a very long string that goes in the heap";
108+
}
109+
static std::string long_string_2() {
110+
return "This is another very long string that goes in the heap";
111+
}
103112
};
104113

105114
template <typename EnumType, EnumType enum_value0, EnumType enum_value1,
@@ -119,8 +128,8 @@ void MapTestUtilImpl::SetMapFields(MapMessage* message) {
119128
(*message->mutable_map_int32_float())[0] = 0.0;
120129
(*message->mutable_map_int32_double())[0] = 0.0;
121130
(*message->mutable_map_bool_bool())[0] = false;
122-
(*message->mutable_map_string_string())["0"] = "0";
123-
(*message->mutable_map_int32_bytes())[0] = "0";
131+
(*message->mutable_map_string_string())[long_string()] = long_string();
132+
(*message->mutable_map_int32_bytes())[0] = long_string();
124133
(*message->mutable_map_int32_enum())[0] = enum_value0;
125134
(*message->mutable_map_int32_foreign_message())[0].set_c(0);
126135

@@ -138,8 +147,8 @@ void MapTestUtilImpl::SetMapFields(MapMessage* message) {
138147
(*message->mutable_map_int32_float())[1] = 1.0;
139148
(*message->mutable_map_int32_double())[1] = 1.0;
140149
(*message->mutable_map_bool_bool())[1] = true;
141-
(*message->mutable_map_string_string())["1"] = "1";
142-
(*message->mutable_map_int32_bytes())[1] = "1";
150+
(*message->mutable_map_string_string())[long_string_2()] = long_string_2();
151+
(*message->mutable_map_int32_bytes())[1] = long_string_2();
143152
(*message->mutable_map_int32_enum())[1] = enum_value1;
144153
(*message->mutable_map_int32_foreign_message())[1].set_c(1);
145154
}
@@ -161,8 +170,8 @@ void MapTestUtilImpl::SetArenaMapFields(MapMessage* message) {
161170
(*message->mutable_map_int32_float())[0] = 0.0;
162171
(*message->mutable_map_int32_double())[0] = 0.0;
163172
(*message->mutable_map_bool_bool())[0] = false;
164-
(*message->mutable_map_string_string())["0"] = "0";
165-
(*message->mutable_map_int32_bytes())[0] = "0";
173+
(*message->mutable_map_string_string())[long_string()] = long_string();
174+
(*message->mutable_map_int32_bytes())[0] = long_string();
166175
(*message->mutable_map_int32_enum())[0] = enum_value0;
167176
(*message->mutable_map_int32_foreign_message())[0].set_c(0);
168177

@@ -180,8 +189,8 @@ void MapTestUtilImpl::SetArenaMapFields(MapMessage* message) {
180189
(*message->mutable_map_int32_float())[1] = 1.0;
181190
(*message->mutable_map_int32_double())[1] = 1.0;
182191
(*message->mutable_map_bool_bool())[1] = true;
183-
(*message->mutable_map_string_string())["1"] = "1";
184-
(*message->mutable_map_int32_bytes())[1] = "1";
192+
(*message->mutable_map_string_string())[long_string_2()] = long_string_2();
193+
(*message->mutable_map_int32_bytes())[1] = long_string_2();
185194
(*message->mutable_map_int32_enum())[1] = enum_value1;
186195
(*message->mutable_map_int32_foreign_message())[1].set_c(1);
187196
}
@@ -203,7 +212,7 @@ void MapTestUtilImpl::SetMapFieldsInitialized(MapMessage* message) {
203212
(*message->mutable_map_int32_float())[0];
204213
(*message->mutable_map_int32_double())[0];
205214
(*message->mutable_map_bool_bool())[0];
206-
(*message->mutable_map_string_string())["0"];
215+
(*message->mutable_map_string_string())[long_string()];
207216
(*message->mutable_map_int32_bytes())[0];
208217
(*message->mutable_map_int32_enum())[0];
209218
(*message->mutable_map_int32_foreign_message())[0];
@@ -224,7 +233,7 @@ void MapTestUtilImpl::ModifyMapFields(MapMessage* message) {
224233
(*message->mutable_map_int32_float())[1] = 2.0;
225234
(*message->mutable_map_int32_double())[1] = 2.0;
226235
(*message->mutable_map_bool_bool())[1] = false;
227-
(*message->mutable_map_string_string())["1"] = "2";
236+
(*message->mutable_map_string_string())[long_string_2()] = "2";
228237
(*message->mutable_map_int32_bytes())[1] = "2";
229238
(*message->mutable_map_int32_enum())[1] = enum_value;
230239
(*message->mutable_map_int32_foreign_message())[1].set_c(2);
@@ -285,8 +294,8 @@ void MapTestUtilImpl::ExpectMapFieldsSet(const MapMessage& message) {
285294
EXPECT_EQ(0, message.map_int32_float().at(0));
286295
EXPECT_EQ(0, message.map_int32_double().at(0));
287296
EXPECT_EQ(false, message.map_bool_bool().at(0));
288-
EXPECT_EQ("0", message.map_string_string().at("0"));
289-
EXPECT_EQ("0", message.map_int32_bytes().at(0));
297+
EXPECT_EQ(long_string(), message.map_string_string().at(long_string()));
298+
EXPECT_EQ(long_string(), message.map_int32_bytes().at(0));
290299
EXPECT_EQ(enum_value0, message.map_int32_enum().at(0));
291300
EXPECT_EQ(0, message.map_int32_foreign_message().at(0).c());
292301

@@ -303,8 +312,8 @@ void MapTestUtilImpl::ExpectMapFieldsSet(const MapMessage& message) {
303312
EXPECT_EQ(1, message.map_int32_float().at(1));
304313
EXPECT_EQ(1, message.map_int32_double().at(1));
305314
EXPECT_EQ(true, message.map_bool_bool().at(1));
306-
EXPECT_EQ("1", message.map_string_string().at("1"));
307-
EXPECT_EQ("1", message.map_int32_bytes().at(1));
315+
EXPECT_EQ(long_string_2(), message.map_string_string().at(long_string_2()));
316+
EXPECT_EQ(long_string_2(), message.map_int32_bytes().at(1));
308317
EXPECT_EQ(enum_value1, message.map_int32_enum().at(1));
309318
EXPECT_EQ(1, message.map_int32_foreign_message().at(1).c());
310319
}
@@ -343,8 +352,8 @@ void MapTestUtilImpl::ExpectArenaMapFieldsSet(const MapMessage& message) {
343352
EXPECT_EQ(0, message.map_int32_float().at(0));
344353
EXPECT_EQ(0, message.map_int32_double().at(0));
345354
EXPECT_EQ(false, message.map_bool_bool().at(0));
346-
EXPECT_EQ("0", message.map_string_string().at("0"));
347-
EXPECT_EQ("0", message.map_int32_bytes().at(0));
355+
EXPECT_EQ(long_string(), message.map_string_string().at(long_string()));
356+
EXPECT_EQ(long_string(), message.map_int32_bytes().at(0));
348357
EXPECT_EQ(enum_value0, message.map_int32_enum().at(0));
349358
EXPECT_EQ(0, message.map_int32_foreign_message().at(0).c());
350359

@@ -361,8 +370,8 @@ void MapTestUtilImpl::ExpectArenaMapFieldsSet(const MapMessage& message) {
361370
EXPECT_EQ(1, message.map_int32_float().at(1));
362371
EXPECT_EQ(1, message.map_int32_double().at(1));
363372
EXPECT_EQ(true, message.map_bool_bool().at(1));
364-
EXPECT_EQ("1", message.map_string_string().at("1"));
365-
EXPECT_EQ("1", message.map_int32_bytes().at(1));
373+
EXPECT_EQ(long_string_2(), message.map_string_string().at(long_string_2()));
374+
EXPECT_EQ(long_string_2(), message.map_int32_bytes().at(1));
366375
EXPECT_EQ(enum_value1, message.map_int32_enum().at(1));
367376
EXPECT_EQ(1, message.map_int32_foreign_message().at(1).c());
368377
}
@@ -400,7 +409,7 @@ void MapTestUtilImpl::ExpectMapFieldsSetInitialized(const MapMessage& message) {
400409
EXPECT_EQ(0, message.map_int32_float().at(0));
401410
EXPECT_EQ(0, message.map_int32_double().at(0));
402411
EXPECT_EQ(false, message.map_bool_bool().at(0));
403-
EXPECT_EQ("", message.map_string_string().at("0"));
412+
EXPECT_EQ("", message.map_string_string().at(long_string()));
404413
EXPECT_EQ("", message.map_int32_bytes().at(0));
405414
EXPECT_EQ(enum_value, message.map_int32_enum().at(0));
406415
EXPECT_EQ(0, message.map_int32_foreign_message().at(0).ByteSizeLong());
@@ -443,8 +452,8 @@ void MapTestUtilImpl::ExpectMapFieldsModified(const MapMessage& message) {
443452
EXPECT_EQ(0, message.map_int32_float().at(0));
444453
EXPECT_EQ(0, message.map_int32_double().at(0));
445454
EXPECT_EQ(false, message.map_bool_bool().at(0));
446-
EXPECT_EQ("0", message.map_string_string().at("0"));
447-
EXPECT_EQ("0", message.map_int32_bytes().at(0));
455+
EXPECT_EQ(long_string(), message.map_string_string().at(long_string()));
456+
EXPECT_EQ(long_string(), message.map_int32_bytes().at(0));
448457
EXPECT_EQ(enum_value0, message.map_int32_enum().at(0));
449458
EXPECT_EQ(0, message.map_int32_foreign_message().at(0).c());
450459

@@ -462,7 +471,7 @@ void MapTestUtilImpl::ExpectMapFieldsModified(const MapMessage& message) {
462471
EXPECT_EQ(2, message.map_int32_float().at(1));
463472
EXPECT_EQ(2, message.map_int32_double().at(1));
464473
EXPECT_EQ(false, message.map_bool_bool().at(1));
465-
EXPECT_EQ("2", message.map_string_string().at("1"));
474+
EXPECT_EQ("2", message.map_string_string().at(long_string_2()));
466475
EXPECT_EQ("2", message.map_int32_bytes().at(1));
467476
EXPECT_EQ(enum_value1, message.map_int32_enum().at(1));
468477
EXPECT_EQ(2, message.map_int32_foreign_message().at(1).c());

0 commit comments

Comments
 (0)