| // Protocol Buffers - Google's data interchange format | |
| // Copyright 2025 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 | |
| #include "upb/mini_table/generated_registry.h" | |
| #include <array> | |
| #include <thread> // NOLINT | |
| #include <vector> | |
| #include "google/protobuf/descriptor.upb_minitable.h" | |
| #include <gtest/gtest.h> | |
| #include "absl/synchronization/barrier.h" | |
| #include "upb/mini_table/extension.h" | |
| #include "upb/mini_table/extension_registry.h" | |
| #include "upb/test/custom_options.upb_minitable.h" | |
| #include "upb/test/editions_test.upb_minitable.h" | |
| #include "upb/test/test_multiple_files.upb_minitable.h" | |
| #include "upb/test/test_multiple_files2.upb_minitable.h" | |
| namespace { | |
| class GeneratedRegistryTest : public ::testing::Test { | |
| protected: | |
| void SetUp() override { | |
| ref_ = upb_GeneratedRegistry_Load(); | |
| ASSERT_NE(ref_, nullptr); | |
| reg_ = upb_GeneratedRegistry_Get(ref_); | |
| ASSERT_NE(reg_, nullptr); | |
| } | |
| void TearDown() override { upb_GeneratedRegistry_Release(ref_); } | |
| const upb_GeneratedRegistryRef* ref_ = nullptr; | |
| const upb_ExtensionRegistry* reg_ = nullptr; | |
| }; | |
| TEST_F(GeneratedRegistryTest, LocalExtensionExists) { | |
| // Test an arbitrary extension defined in the same .proto file that we expect | |
| // to be linked in. | |
| // Force linkage and sanity check extension numbers. | |
| ASSERT_EQ(upb_MiniTableExtension_Number(upb_test_2023_ext_ext), 100); | |
| EXPECT_NE(upb_ExtensionRegistry_Lookup( | |
| reg_, &upb__test_02023__EditionsMessage_msg_init, 100), | |
| nullptr); | |
| } | |
| TEST_F(GeneratedRegistryTest, ForeignExtensionExists) { | |
| // Test an arbitrary extension defined by an import that we expect to be | |
| // linked in. | |
| // Force linkage and sanity check extension numbers. | |
| ASSERT_EQ(upb_MiniTableExtension_Number(upb_message_opt_ext), 7739036); | |
| EXPECT_NE(upb_ExtensionRegistry_Lookup(reg_, &google__protobuf__MessageOptions_msg_init, | |
| 7739036), | |
| nullptr); | |
| } | |
| TEST_F(GeneratedRegistryTest, UnlinkedExtensionDoesNotExist) { | |
| // Test an arbitrary extension defined by an option import that we do not | |
| // expect to be linked in. | |
| // Corresponds to upb.message_opt_unlinked in custom_options_unlinked.proto. | |
| EXPECT_EQ(upb_ExtensionRegistry_Lookup(reg_, &google__protobuf__MessageOptions_msg_init, | |
| 7739037), | |
| nullptr); | |
| } | |
| TEST_F(GeneratedRegistryTest, MultipleFilesExtensionExists) { | |
| // Test that multiple .proto files from the same proto_library can be linked | |
| // in and registered. | |
| // Force linkage and sanity check extension numbers. | |
| ASSERT_EQ(upb_MiniTableExtension_Number(upb_multiple_files_ext1_ext), 100); | |
| ASSERT_EQ(upb_MiniTableExtension_Number(upb_multiple_files_ext2_ext), 100); | |
| EXPECT_NE(upb_ExtensionRegistry_Lookup( | |
| reg_, &upb__MultipleFilesMessage1_msg_init, 100), | |
| nullptr); | |
| EXPECT_NE(upb_ExtensionRegistry_Lookup( | |
| reg_, &upb__MultipleFilesMessage2_msg_init, 100), | |
| nullptr); | |
| } | |
| TEST_F(GeneratedRegistryTest, ReleaseOnError) { | |
| upb_GeneratedRegistry_Release(nullptr); | |
| } | |
| TEST_F(GeneratedRegistryTest, GetOnError) { | |
| EXPECT_EQ(upb_GeneratedRegistry_Get(nullptr), nullptr); | |
| } | |
| // On 32-bit systems, the stack size is smaller, so we can't run too many | |
| // concurrent threads without overflowing the stack. | |
| constexpr int kRaceIterations = sizeof(void*) < 8 ? 100 : 2000; | |
| TEST(GeneratedRegistryRaceTest, Load) { | |
| absl::Barrier barrier(kRaceIterations); | |
| std::vector<std::thread> threads; | |
| std::array<const upb_GeneratedRegistryRef*, kRaceIterations> refs; | |
| for (int i = 0; i < kRaceIterations; ++i) { | |
| threads.push_back(std::thread([&, i]() { | |
| barrier.Block(); | |
| const upb_GeneratedRegistryRef* ref = upb_GeneratedRegistry_Load(); | |
| EXPECT_NE(upb_GeneratedRegistry_Get(ref), nullptr); | |
| refs[i] = ref; | |
| })); | |
| } | |
| for (auto& t : threads) t.join(); | |
| for (int i = 0; i < kRaceIterations; ++i) { | |
| EXPECT_NE(refs[i], nullptr); | |
| upb_GeneratedRegistry_Release(refs[i]); | |
| } | |
| } | |
| TEST(GeneratedRegistryRaceTest, Release) { | |
| absl::Barrier barrier(kRaceIterations); | |
| std::vector<std::thread> threads; | |
| std::array<const upb_GeneratedRegistryRef*, kRaceIterations> refs; | |
| for (int i = 0; i < kRaceIterations; ++i) { | |
| refs[i] = upb_GeneratedRegistry_Load(); | |
| ASSERT_NE(refs[i], nullptr); | |
| } | |
| for (int i = 0; i < kRaceIterations; ++i) { | |
| threads.push_back(std::thread([&, i]() { | |
| EXPECT_NE(upb_GeneratedRegistry_Get(refs[i]), nullptr); | |
| barrier.Block(); | |
| upb_GeneratedRegistry_Release(refs[i]); | |
| })); | |
| } | |
| for (auto& t : threads) t.join(); | |
| } | |
| TEST(GeneratedRegistryRaceTest, LoadRelease) { | |
| absl::Barrier barrier(kRaceIterations); | |
| std::vector<std::thread> threads; | |
| for (int i = 0; i < kRaceIterations; ++i) { | |
| threads.push_back(std::thread([&]() { | |
| barrier.Block(); | |
| const upb_GeneratedRegistryRef* ref = upb_GeneratedRegistry_Load(); | |
| EXPECT_NE(upb_GeneratedRegistry_Get(ref), nullptr); | |
| upb_GeneratedRegistry_Release(ref); | |
| })); | |
| } | |
| for (auto& t : threads) t.join(); | |
| } | |
| TEST(GeneratedRegistryRaceTest, ReleaseLastAndLoadMultiple) { | |
| for (int i = 0; i < kRaceIterations; ++i) { | |
| // Load one reference. The registry is now alive with ref_count == 1. | |
| const upb_GeneratedRegistryRef* ref = upb_GeneratedRegistry_Load(); | |
| ASSERT_NE(ref, nullptr); | |
| constexpr int kNumLoaders = 4; | |
| absl::Barrier barrier(kNumLoaders + 1); | |
| std::vector<std::thread> threads; | |
| threads.push_back(std::thread([&]() { | |
| barrier.Block(); | |
| // This release should bring the ref_count to 0 and trigger cleanup. | |
| upb_GeneratedRegistry_Release(ref); | |
| })); | |
| for (int j = 0; j < kNumLoaders; ++j) { | |
| threads.push_back(std::thread([&]() { | |
| barrier.Block(); | |
| const upb_GeneratedRegistryRef* ref2 = upb_GeneratedRegistry_Load(); | |
| EXPECT_NE(upb_GeneratedRegistry_Get(ref2), nullptr); | |
| upb_GeneratedRegistry_Release(ref2); | |
| })); | |
| } | |
| for (auto& t : threads) t.join(); | |
| } | |
| } | |
| } // namespace |