| // 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_ExtensionRegistry_GetGenerated(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); |
| } |
| |
| // 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_ExtensionRegistry_GetGenerated(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_ExtensionRegistry_GetGenerated(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_ExtensionRegistry_GetGenerated(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_ExtensionRegistry_GetGenerated(ref2), nullptr); |
| upb_GeneratedRegistry_Release(ref2); |
| })); |
| } |
| |
| for (auto& t : threads) t.join(); |
| } |
| } |
| |
| } // namespace |