// Protocol Buffers - Google's data interchange format // Copyright 2023 Google LLC. All rights reserved. // // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file or at // https://developers.google.com/open-source/licenses/bsd // Tests for upb_table. #include #include #include #include #include #include #include #include #include "absl/container/flat_hash_map.h" #include "upb/hash/int_table.h" #include "upb/hash/str_table.h" #include "upb/mem/arena.hpp" // Must be last. #include "upb/port/def.inc" using std::vector; TEST(Table, StringTable) { vector keys; keys.push_back("google.protobuf.FileDescriptorSet"); keys.push_back("google.protobuf.FileDescriptorProto"); keys.push_back("google.protobuf.DescriptorProto"); keys.push_back("google.protobuf.DescriptorProto.ExtensionRange"); keys.push_back("google.protobuf.FieldDescriptorProto"); keys.push_back("google.protobuf.EnumDescriptorProto"); keys.push_back("google.protobuf.EnumValueDescriptorProto"); keys.push_back("google.protobuf.ServiceDescriptorProto"); keys.push_back("google.protobuf.MethodDescriptorProto"); keys.push_back("google.protobuf.FileOptions"); keys.push_back("google.protobuf.MessageOptions"); keys.push_back("google.protobuf.FieldOptions"); keys.push_back("google.protobuf.EnumOptions"); keys.push_back("google.protobuf.EnumValueOptions"); keys.push_back("google.protobuf.ServiceOptions"); keys.push_back("google.protobuf.MethodOptions"); keys.push_back("google.protobuf.UninterpretedOption"); keys.push_back("google.protobuf.UninterpretedOption.NamePart"); /* Initialize structures. */ upb::Arena arena; upb_strtable t; upb_strtable_init(&t, keys.size(), arena.ptr()); std::map m; std::set all; for (const auto& key : keys) { all.insert(key); upb_value val = {uint64_t(key[0])}; upb_strtable_insert(&t, key.data(), key.size(), val, arena.ptr()); m[key] = key[0]; } /* Test correctness. */ for (const auto& key : keys) { upb_value val; bool ok = upb_strtable_lookup2(&t, key.data(), key.size(), &val); EXPECT_TRUE(ok); EXPECT_EQ(val.val, uint64_t(key[0])); EXPECT_EQ(m[key], key[0]); } intptr_t iter = UPB_STRTABLE_BEGIN; upb_StringView key; upb_value val; while (upb_strtable_next2(&t, &key, &val, &iter)) { std::set::iterator i = all.find(key.data); EXPECT_NE(i, all.end()); all.erase(i); } EXPECT_TRUE(all.empty()); // Test iteration with resizes. for (int i = 0; i < 10; i++) { intptr_t iter = UPB_STRTABLE_BEGIN; while (upb_strtable_next2(&t, &key, &val, &iter)) { // Even if we invalidate the iterator it should only return real elements. EXPECT_EQ(val.val, m[key.data]); // Force a resize even though the size isn't changing. // Also forces the table size to grow so some new buckets end up empty. bool ok = upb_strtable_resize(&t, 5 + i, arena.ptr()); EXPECT_TRUE(ok); } } } class IntTableTest : public testing::TestWithParam { void SetUp() override { if (GetParam() > 0) { for (int i = 0; i < GetParam(); i++) { keys_.push_back(i + 1); } } else { for (int32_t i = 0; i < 64; i++) { if (i < 32) keys_.push_back(i + 1); else keys_.push_back(10101 + i); } } } protected: std::vector keys_; }; TEST_P(IntTableTest, TestIntTable) { /* Initialize structures. */ upb::Arena arena; upb_inttable t; upb_inttable_init(&t, arena.ptr()); uint32_t largest_key = 0; std::map m; absl::flat_hash_map hm; for (const auto& key : keys_) { largest_key = UPB_MAX((int32_t)largest_key, key); upb_value val = upb_value_uint32(key * 2); bool ok = upb_inttable_insert(&t, key, val, arena.ptr()); EXPECT_TRUE(ok); m[key] = key * 2; hm[key] = key * 2; } EXPECT_EQ(upb_inttable_count(&t), keys_.size()); /* Test correctness. */ int count = 0; for (uint32_t i = 0; i <= largest_key; i++) { upb_value val; bool ok = upb_inttable_lookup(&t, i, &val); if (ok) { /* Assume map implementation is correct. */ EXPECT_EQ(val.val, i * 2); EXPECT_EQ(m[i], i * 2); EXPECT_EQ(hm[i], i * 2); count++; } } EXPECT_EQ(count, keys_.size()); EXPECT_EQ(count, upb_inttable_count(&t)); // Test replace. count = 0; for (uint32_t i = 0; i <= largest_key; i++) { upb_value val = upb_value_uint32(i * 3); bool ok = upb_inttable_replace(&t, i, val); if (ok) { /* Assume map implementation is correct. */ m[i] = i * 3; hm[i] = i * 3; count++; } } EXPECT_EQ(count, keys_.size()); EXPECT_EQ(count, upb_inttable_count(&t)); // Compact and test correctness again. upb_inttable_compact(&t, arena.ptr()); count = 0; for (uint32_t i = 0; i <= largest_key; i++) { upb_value val; bool ok = upb_inttable_lookup(&t, i, &val); if (ok) { /* Assume map implementation is correct. */ EXPECT_EQ(val.val, i * 3); EXPECT_EQ(m[i], i * 3); EXPECT_EQ(hm[i], i * 3); count++; } } EXPECT_EQ(count, keys_.size()); EXPECT_EQ(count, upb_inttable_count(&t)); for (const auto& key : keys_) { upb_value val; bool ok = upb_inttable_remove(&t, key, &val); EXPECT_TRUE(ok); EXPECT_EQ(val.val, (uint32_t)key * 3); count--; EXPECT_EQ(count, upb_inttable_count(&t)); } EXPECT_EQ(0, upb_inttable_count(&t)); } INSTANTIATE_TEST_SUITE_P(IntTableParams, IntTableTest, testing::Values(8, 64, 512, -32)); /* * This test can't pass right now because the table can't store a value of * (uint64_t)-1. */ TEST(Table, MaxValue) { /* typedef upb::TypedIntTable Table; Table table; uintptr_t uint64_max = (uint64_t)-1; table.Insert(1, uint64_max); std::pair found = table.Lookup(1); ASSERT(found.first); ASSERT(found.second == uint64_max); */ } TEST(Table, Delete) { upb::Arena arena; upb_inttable t; upb_inttable_init(&t, arena.ptr()); upb_inttable_insert(&t, 0, upb_value_bool(true), arena.ptr()); upb_inttable_insert(&t, 2, upb_value_bool(true), arena.ptr()); upb_inttable_insert(&t, 4, upb_value_bool(true), arena.ptr()); upb_inttable_compact(&t, arena.ptr()); upb_inttable_remove(&t, 0, nullptr); upb_inttable_remove(&t, 2, nullptr); upb_inttable_remove(&t, 4, nullptr); intptr_t iter = UPB_INTTABLE_BEGIN; uintptr_t key; upb_value val; while (upb_inttable_next(&t, &key, &val, &iter)) { FAIL(); } } TEST(Table, Init) { for (int i = 0; i < 2048; i++) { /* Tests that the size calculations in init() (lg2 size for target load) * work for all expected sizes. */ upb::Arena arena; upb_strtable t; upb_strtable_init(&t, i, arena.ptr()); } }