blob: 912675b8362fbb8c529e3cc40c3cac3c9e079c8a [file] [log] [blame]
// 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