| #include <cstring> | |
| #include <string> | |
| #include <string_view> | |
| #include "google/protobuf/descriptor.pb.h" | |
| #include "google/protobuf/descriptor.upb.h" | |
| #include <gmock/gmock.h> | |
| #include <gtest/gtest.h> | |
| #include "absl/log/absl_check.h" | |
| #include "absl/status/status.h" | |
| #include "absl/status/statusor.h" | |
| #include "absl/strings/str_format.h" | |
| #include "absl/strings/str_join.h" | |
| #include "google/protobuf/unittest.upbdefs.h" | |
| #include "upb/base/status.hpp" | |
| #include "upb/mem/alloc.h" | |
| #include "upb/mem/arena.hpp" | |
| #include "upb/reflection/def.h" | |
| #include "upb/reflection/def.hpp" | |
| #include "upb/reflection/internal/def_pool.h" | |
| #include "upb/test/parse_text_proto.h" | |
| namespace upb_test { | |
| namespace { | |
| using ::testing::HasSubstr; | |
| using ::testing::NotNull; | |
| google_protobuf_FileDescriptorProto* ToUpbDescriptorSet( | |
| const google::protobuf::FileDescriptorProto& proto, upb::Arena& arena) { | |
| std::string serialized; | |
| (void)proto.SerializeToString(&serialized); | |
| return google_protobuf_FileDescriptorProto_parse(serialized.data(), serialized.size(), | |
| arena.ptr()); | |
| } | |
| absl::StatusOr<upb::DefPool> LoadDescriptorSetFromProto( | |
| const google::protobuf::FileDescriptorSet& set) { | |
| upb::Arena arena; | |
| upb::DefPool defpool; | |
| upb::Status status; | |
| for (const auto& file : set.file()) { | |
| google_protobuf_FileDescriptorProto* upb_proto = ToUpbDescriptorSet(file, arena); | |
| ABSL_CHECK(upb_proto); | |
| upb::FileDefPtr file_def = defpool.AddFile(upb_proto, &status); | |
| if (!file_def) return absl::InternalError(status.error_message()); | |
| } | |
| return defpool; | |
| } | |
| absl::StatusOr<upb::DefPool> LoadDescriptorProto(absl::string_view proto_text) { | |
| google::protobuf::FileDescriptorProto proto = ParseTextProtoOrDie(proto_text); | |
| google::protobuf::FileDescriptorSet set; | |
| *set.add_file() = proto; | |
| return LoadDescriptorSetFromProto(set); | |
| } | |
| TEST(ReflectionTest, OpenEnumWithNonZeroDefault) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| syntax: "proto3" | |
| name: "F" | |
| enum_type { | |
| name: "BadEnum" | |
| value { name: "v1" number: 1 } | |
| } | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(std::string_view(status.message()), | |
| "for open enums, the first value must be zero (BadEnum)"); | |
| } | |
| TEST(ReflectionTest, FailedAddFileDoesNotRetainExtensions) { | |
| // Add a valid message with one available extension number. | |
| google::protobuf::FileDescriptorProto base; | |
| base.set_name("base.proto"); | |
| base.set_package("base"); | |
| base.set_syntax("proto2"); | |
| auto* target = base.add_message_type(); | |
| target->set_name("Target"); | |
| auto* range = target->add_extension_range(); | |
| range->set_start(100); | |
| range->set_end(101); | |
| // This file is rejected because both extensions use that number. | |
| google::protobuf::FileDescriptorProto duplicate; | |
| duplicate.set_name("duplicate.proto"); | |
| duplicate.set_package("duplicate"); | |
| duplicate.set_syntax("proto2"); | |
| duplicate.add_dependency("base.proto"); | |
| for (const char* name : {"first", "second"}) { | |
| auto* extension = duplicate.add_extension(); | |
| extension->set_name(name); | |
| extension->set_extendee(".base.Target"); | |
| extension->set_number(100); | |
| extension->set_label(google::protobuf::FieldDescriptorProto::LABEL_OPTIONAL); | |
| extension->set_type(google::protobuf::FieldDescriptorProto::TYPE_INT32); | |
| } | |
| upb::Arena arena; | |
| upb::DefPool pool; | |
| upb::Status status; | |
| ASSERT_TRUE(pool.AddFile(ToUpbDescriptorSet(base, arena), &status)); | |
| status.Clear(); | |
| EXPECT_FALSE(pool.AddFile(ToUpbDescriptorSet(duplicate, arena), &status)); | |
| upb::MessageDefPtr message = pool.FindMessageByName("base.Target"); | |
| ASSERT_TRUE(message); | |
| // A failed addition must not leave extensions in the pool. | |
| size_t count; | |
| const upb_FieldDef** extensions = | |
| upb_DefPool_GetAllExtensions(pool.ptr(), message.ptr(), &count); | |
| EXPECT_EQ(count, 0); | |
| upb_gfree(extensions); | |
| } | |
| TEST(ReflectionTest, EnumDefault) { | |
| upb::DefPool pool = LoadDescriptorProto( | |
| R"pb( | |
| syntax: "proto2" | |
| name: "F" | |
| enum_type { | |
| name: "FooEnum" | |
| value { name: "v1" number: 1 } | |
| } | |
| )pb") | |
| .value(); | |
| upb::EnumDefPtr e = pool.FindEnumByName("FooEnum"); | |
| EXPECT_EQ(e.default_value(), 1); | |
| } | |
| TEST(ReflectionTest, ImplicitPresenceWithDefault) { | |
| absl::Status status = | |
| LoadDescriptorProto( | |
| R"pb( | |
| syntax: "editions" | |
| edition: EDITION_2023 | |
| name: "F" | |
| message_type { | |
| name: "FooMessage" | |
| field { | |
| name: "f1" | |
| number: 1 | |
| type: TYPE_INT32 | |
| default_value: "1" | |
| options { features { field_presence: IMPLICIT } } | |
| } | |
| } | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(std::string_view(status.message()), | |
| "fields with implicit presence cannot have explicit defaults " | |
| "(FooMessage.f1)"); | |
| } | |
| TEST(ReflectionTest, ImplicitPresenceWithNonZeroDefaultEnum) { | |
| absl::Status status = | |
| LoadDescriptorProto( | |
| R"pb( | |
| syntax: "editions" | |
| edition: EDITION_2023 | |
| name: "F" | |
| enum_type { | |
| name: "FooEnum" | |
| value { name: "v1" number: 1 } | |
| options { features { enum_type: CLOSED } } | |
| } | |
| message_type { | |
| name: "FooMessage" | |
| field { | |
| name: "f1" | |
| number: 1 | |
| type: TYPE_ENUM | |
| type_name: "FooEnum" | |
| options { features { field_presence: IMPLICIT } } | |
| } | |
| } | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(std::string_view(status.message()), | |
| "Implicit presence field (FooMessage.f1) cannot use an enum type " | |
| "with a non-zero default (FooEnum)"); | |
| } | |
| TEST(ReflectionTest, EditionWithoutSyntax) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| edition: EDITION_2023 | |
| )pb") | |
| .status(); | |
| EXPECT_EQ( | |
| status.message(), | |
| R"(Setting edition requires that syntax="editions", but syntax is "")"); | |
| } | |
| TEST(ReflectionTest, EditionWithWrongSyntax) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| edition: EDITION_2023 syntax: "proto2" | |
| )pb") | |
| .status(); | |
| EXPECT_EQ( | |
| status.message(), | |
| R"(Setting edition requires that syntax="editions", but syntax is "proto2")"); | |
| } | |
| TEST(ReflectionTest, SyntaxEditionsWithNoEdition) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| syntax: "editions" | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(status.message(), | |
| R"(File has syntax="editions", but no edition is specified)"); | |
| } | |
| TEST(ReflectionTest, InvalidSyntax) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| syntax: "abc123" | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(status.message(), R"(Invalid syntax 'abc123')"); | |
| } | |
| TEST(ReflectionTest, ExplicitFeatureOnProto2File) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| syntax: "proto2" | |
| options { features { field_presence: EXPLICIT } } | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(status.message(), R"(Features can only be specified for editions)"); | |
| } | |
| TEST(ReflectionTest, ExplicitFeatureOnProto2Message) { | |
| absl::Status status = | |
| LoadDescriptorProto( | |
| R"pb( | |
| syntax: "proto2" | |
| message_type { | |
| name: "M" | |
| options { features { field_presence: EXPLICIT } } | |
| } | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(status.message(), R"(Features can only be specified for editions)"); | |
| } | |
| TEST(ReflectionTest, ExplicitFeatureOnProto2Enum) { | |
| absl::Status status = | |
| LoadDescriptorProto( | |
| R"pb( | |
| syntax: "proto2" | |
| enum_type { | |
| name: "E" | |
| options { features { field_presence: EXPLICIT } } | |
| } | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(status.message(), R"(Features can only be specified for editions)"); | |
| } | |
| TEST(ReflectionTest, ExplicitFeatureOnProto2EnumValue) { | |
| absl::Status status = | |
| LoadDescriptorProto( | |
| R"pb( | |
| syntax: "proto2" | |
| enum_type { | |
| name: "E" | |
| value { | |
| name: "V" | |
| options { features { field_presence: EXPLICIT } } | |
| } | |
| } | |
| )pb") | |
| .status(); | |
| EXPECT_EQ(status.message(), R"(Features can only be specified for editions)"); | |
| } | |
| TEST(ReflectionTest, TooManyRequiredFieldsFailGracefully) { | |
| const auto make_desc = [](int n) { | |
| std::vector<std::string> fields; | |
| for (int i = 1; i <= n; ++i) { | |
| fields.push_back(absl::StrFormat(R"pb( | |
| field { | |
| name: "f%d" | |
| number: %d | |
| type: TYPE_INT32 | |
| label: LABEL_REQUIRED | |
| })pb", | |
| i, i)); | |
| } | |
| return absl::StrFormat( | |
| R"pb( | |
| syntax: "proto2" | |
| name: "F" | |
| message_type { name: "FooMessage" %s } | |
| )pb", | |
| absl::StrJoin(fields, "\n")); | |
| }; | |
| // 63 required fields is ok. | |
| upb::DefPool good = LoadDescriptorProto(make_desc(63)).value(); | |
| auto m = good.FindMessageByName("FooMessage"); | |
| auto f = m.FindFieldByNumber(63); | |
| EXPECT_STREQ(f.full_name(), "FooMessage.f63"); | |
| // 64 is too much. | |
| EXPECT_THAT(LoadDescriptorProto(make_desc(64)).status().message(), | |
| HasSubstr("Too many required fields")); | |
| } | |
| #define STRING_AND_SIZE(string) string, strlen(string) | |
| TEST(ReflectionTest, FindMethodByName) { | |
| upb::Arena arena; | |
| upb::DefPool defpool; | |
| upb::Status status; | |
| ASSERT_TRUE(_upb_DefPool_LoadDefInit( | |
| defpool.ptr(), &google_protobuf_unittest_proto_upbdefinit)); | |
| const upb_ServiceDef* service_def = upb_DefPool_FindServiceByName( | |
| defpool.ptr(), "proto2_unittest.TestService"); | |
| ASSERT_THAT(service_def, NotNull()); | |
| EXPECT_STREQ(upb_ServiceDef_Name(service_def), "TestService"); | |
| EXPECT_STREQ(upb_ServiceDef_FullName(service_def), | |
| "proto2_unittest.TestService"); | |
| EXPECT_EQ(upb_DefPool_FindServiceByNameWithSize( | |
| defpool.ptr(), STRING_AND_SIZE("proto2_unittest.TestService")), | |
| service_def); | |
| const upb_MethodDef* method_def = | |
| upb_ServiceDef_FindMethodByName(service_def, "Bar"); | |
| ASSERT_THAT(method_def, NotNull()); | |
| EXPECT_STREQ(upb_MethodDef_Name(method_def), "Bar"); | |
| EXPECT_STREQ(upb_MethodDef_FullName(method_def), | |
| "proto2_unittest.TestService.Bar"); | |
| EXPECT_EQ(upb_ServiceDef_FindMethodByNameWithSize(service_def, | |
| STRING_AND_SIZE("Bar")), | |
| method_def); | |
| } | |
| TEST(ReflectionTest, FindEnumByName) { | |
| upb::Arena arena; | |
| upb::DefPool defpool; | |
| upb::Status status; | |
| ASSERT_TRUE(_upb_DefPool_LoadDefInit( | |
| defpool.ptr(), &google_protobuf_unittest_proto_upbdefinit)); | |
| const upb_EnumDef* enum_def = upb_DefPool_FindEnumByName( | |
| defpool.ptr(), "proto2_unittest.TestAllTypes.NestedEnum"); | |
| ASSERT_THAT(enum_def, NotNull()); | |
| EXPECT_STREQ(upb_EnumDef_Name(enum_def), "NestedEnum"); | |
| EXPECT_STREQ(upb_EnumDef_FullName(enum_def), | |
| "proto2_unittest.TestAllTypes.NestedEnum"); | |
| EXPECT_EQ(upb_DefPool_FindEnumByNameWithSize( | |
| defpool.ptr(), | |
| STRING_AND_SIZE("proto2_unittest.TestAllTypes.NestedEnum")), | |
| enum_def); | |
| } | |
| TEST(ReflectionTest, FindEnumValueByName) { | |
| upb::Arena arena; | |
| upb::DefPool defpool; | |
| upb::Status status; | |
| ASSERT_TRUE(_upb_DefPool_LoadDefInit( | |
| defpool.ptr(), &google_protobuf_unittest_proto_upbdefinit)); | |
| const upb_EnumValueDef* enum_value_def = upb_DefPool_FindEnumValueByName( | |
| defpool.ptr(), "proto2_unittest.TestAllTypes.BAR"); | |
| ASSERT_THAT(enum_value_def, NotNull()); | |
| EXPECT_STREQ(upb_EnumValueDef_Name(enum_value_def), "BAR"); | |
| EXPECT_STREQ(upb_EnumValueDef_FullName(enum_value_def), | |
| "proto2_unittest.TestAllTypes.BAR"); | |
| EXPECT_EQ( | |
| upb_DefPool_FindEnumValueByNameWithSize( | |
| defpool.ptr(), STRING_AND_SIZE("proto2_unittest.TestAllTypes.BAR")), | |
| enum_value_def); | |
| const upb_EnumDef* enum_def = upb_DefPool_FindEnumByName( | |
| defpool.ptr(), "proto2_unittest.TestAllTypes.NestedEnum"); | |
| ASSERT_THAT(enum_def, NotNull()); | |
| EXPECT_EQ(upb_EnumDef_FindValueByName(enum_def, "BAR"), enum_value_def); | |
| EXPECT_EQ( | |
| upb_EnumDef_FindValueByNameWithSize(enum_def, STRING_AND_SIZE("BAR")), | |
| enum_value_def); | |
| } | |
| TEST(ReflectionTest, NegativePublicDependencyIndex) { | |
| // Verify that a negative public_dependency index is rejected rather than | |
| // causing an out-of-bounds read when the index is later used to access the | |
| // deps array. | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| name: "test.proto" public_dependency: -1 | |
| )pb") | |
| .status(); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_THAT(std::string(status.message()), HasSubstr("out of range")); | |
| } | |
| TEST(ReflectionTest, NegativeWeakDependencyIndex) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| name: "test.proto" weak_dependency: -1 | |
| )pb") | |
| .status(); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_THAT(std::string(status.message()), HasSubstr("out of range")); | |
| } | |
| TEST(ReflectionTest, ZeroPublicDependencyIndexWithNoDeps) { | |
| // Index 0 is out of range when there are no dependencies (dep_count=0). | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| name: "test.proto" public_dependency: 0 | |
| )pb") | |
| .status(); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_THAT(std::string(status.message()), HasSubstr("out of range")); | |
| } | |
| TEST(ReflectionTest, ZeroWeakDependencyIndexWithNoDeps) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| name: "test.proto" weak_dependency: 0 | |
| )pb") | |
| .status(); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_THAT(std::string(status.message()), HasSubstr("out of range")); | |
| } | |
| // Sets (pb.enumvalue.json).string option (extension field 998) on | |
| // EnumValueOptions. | |
| static void SetCustomJsonOption(google::protobuf::EnumValueOptions* options, | |
| absl::string_view json_name) { | |
| // Wire format for JsonEnumValueOptions { string string = 1 }: | |
| // Field 1 (string, length-delimited): tag = (1 << 3) | 2 = 0x0a. | |
| std::string payload; | |
| payload.push_back('\x0a'); | |
| payload.push_back(static_cast<char>(json_name.size())); | |
| payload.append(json_name); | |
| // Field 998 is extension (pb.enumvalue.json) on EnumValueOptions. | |
| options->mutable_unknown_fields()->AddLengthDelimited(998, payload); | |
| } | |
| struct TestEnumValueSpec { | |
| std::string name; | |
| int number; | |
| std::string json_name; | |
| }; | |
| static absl::StatusOr<upb::DefPool> LoadEnumDescriptorWithValues( | |
| absl::Span<const TestEnumValueSpec> values) { | |
| google::protobuf::FileDescriptorProto file_proto; | |
| file_proto.set_name("test.proto"); | |
| file_proto.set_syntax("editions"); | |
| file_proto.set_edition(google::protobuf::EDITION_2026); | |
| google::protobuf::EnumDescriptorProto* enum_proto = file_proto.add_enum_type(); | |
| enum_proto->set_name("TestEnum"); | |
| for (const TestEnumValueSpec& v : values) { | |
| google::protobuf::EnumValueDescriptorProto* val_proto = enum_proto->add_value(); | |
| val_proto->set_name(v.name); | |
| val_proto->set_number(v.number); | |
| if (!v.json_name.empty()) { | |
| SetCustomJsonOption(val_proto->mutable_options(), v.json_name); | |
| } | |
| } | |
| google::protobuf::FileDescriptorSet set; | |
| *set.add_file() = file_proto; | |
| return LoadDescriptorSetFromProto(set); | |
| } | |
| TEST(ReflectionTest, EnumCustomJsonNameConflictDifferentNumberFails) { | |
| // Two enum values with DIFFERENT numbers (1 and 2) sharing the same custom | |
| // JSON name must fail descriptor validation. | |
| absl::Status status = | |
| LoadEnumDescriptorWithValues({ | |
| {"VAL_ZERO", 0, ""}, | |
| {"VAL_A", 1, "custom_name"}, | |
| {"VAL_B", 2, "custom_name"}, | |
| }) | |
| .status(); | |
| EXPECT_FALSE(status.ok()); | |
| EXPECT_THAT(std::string(status.message()), | |
| HasSubstr("duplicate custom json_name (custom_name) in enum")); | |
| } | |
| TEST(ReflectionTest, EnumCustomJsonNameAliasedSameNumberSucceeds) { | |
| // Two aliased enum values with the SAME number (1 and 1) sharing the same | |
| // custom JSON name must succeed descriptor validation. | |
| absl::Status status = | |
| LoadEnumDescriptorWithValues({ | |
| {"VAL_ZERO", 0, ""}, | |
| {"VAL_A", 1, "custom_name"}, | |
| {"VAL_B", 1, "custom_name"}, | |
| }) | |
| .status(); | |
| EXPECT_TRUE(status.ok()) << status.message(); | |
| } | |
| TEST(ReflectionTest, LegacyJsonFieldConflictsKeepsCorrectNumberMapping) { | |
| // With deprecated_legacy_json_field_conflicts, two fields may share a | |
| // json_name. Each field number must still map to its own FieldDef. | |
| upb::DefPool pool = | |
| LoadDescriptorProto( | |
| R"pb( | |
| syntax: "proto2" | |
| name: "F" | |
| message_type { name: "Sub" } | |
| message_type { | |
| name: "M" | |
| options { deprecated_legacy_json_field_conflicts: true } | |
| field { | |
| name: "a" | |
| number: 1 | |
| label: LABEL_OPTIONAL | |
| type: TYPE_MESSAGE | |
| type_name: ".Sub" | |
| json_name: "j" | |
| } | |
| field { | |
| name: "b" | |
| number: 2 | |
| label: LABEL_OPTIONAL | |
| type: TYPE_FIXED64 | |
| json_name: "j" | |
| } | |
| } | |
| )pb") | |
| .value(); | |
| upb::MessageDefPtr m = pool.FindMessageByName("M"); | |
| ASSERT_TRUE(m); | |
| upb::FieldDefPtr a = m.FindFieldByNumber(1); | |
| upb::FieldDefPtr b = m.FindFieldByNumber(2); | |
| ASSERT_TRUE(a); | |
| ASSERT_TRUE(b); | |
| EXPECT_STREQ(a.name(), "a"); | |
| EXPECT_EQ(a.number(), 1); | |
| EXPECT_STREQ(b.name(), "b"); | |
| EXPECT_EQ(b.number(), 2); | |
| EXPECT_EQ(b.type(), kUpb_FieldType_Fixed64); | |
| } | |
| TEST(ReflectionTest, JsonFieldConflictsRejectedWithoutLegacyOption) { | |
| absl::Status status = LoadDescriptorProto( | |
| R"pb( | |
| syntax: "proto2" | |
| name: "F" | |
| message_type { | |
| name: "M" | |
| field { | |
| name: "a" | |
| number: 1 | |
| label: LABEL_OPTIONAL | |
| type: TYPE_FIXED64 | |
| json_name: "j" | |
| } | |
| field { | |
| name: "b" | |
| number: 2 | |
| label: LABEL_OPTIONAL | |
| type: TYPE_FIXED64 | |
| json_name: "j" | |
| } | |
| } | |
| )pb") | |
| .status(); | |
| EXPECT_THAT(std::string(status.message()), | |
| HasSubstr("duplicate json_name (j)")); | |
| } | |
| TEST(ReflectionTest, DefPoolFindMethodsRespectSize) { | |
| upb::DefPool pool = LoadDescriptorProto(R"pb( | |
| syntax: "proto2" | |
| name: "test.proto" | |
| package: "pkg" | |
| message_type { | |
| name: "TestMessage" | |
| extension_range { start: 1 end: 1000 } | |
| } | |
| enum_type { | |
| name: "TestEnum" | |
| value { name: "VAL" number: 1 } | |
| } | |
| extension { | |
| name: "test_ext" | |
| number: 100 | |
| label: LABEL_OPTIONAL | |
| type: TYPE_INT32 | |
| extendee: ".pkg.TestMessage" | |
| } | |
| )pb") | |
| .value(); | |
| // Test FindMessageByName | |
| { | |
| absl::string_view full_name = "pkg.TestMessage"; | |
| EXPECT_TRUE(pool.FindMessageByName(full_name)); | |
| EXPECT_FALSE( | |
| pool.FindMessageByName(full_name.substr(0, 12))); // "pkg.TestMess" | |
| } | |
| // Test FindEnumByName | |
| { | |
| absl::string_view full_name = "pkg.TestEnum"; | |
| EXPECT_TRUE(pool.FindEnumByName(full_name)); | |
| EXPECT_FALSE(pool.FindEnumByName(full_name.substr(0, 10))); // "pkg.TestEn" | |
| } | |
| // Test FindFileByName | |
| { | |
| absl::string_view full_name = "test.proto"; | |
| EXPECT_TRUE(pool.FindFileByName(full_name)); | |
| EXPECT_FALSE(pool.FindFileByName(full_name.substr(0, 7))); // "test.pr" | |
| } | |
| // Test FindExtensionByName | |
| { | |
| absl::string_view full_name = "pkg.test_ext"; | |
| EXPECT_TRUE(pool.FindExtensionByName(full_name)); | |
| EXPECT_FALSE( | |
| pool.FindExtensionByName(full_name.substr(0, 11))); // "pkg.test_ex" | |
| } | |
| } | |
| } // namespace | |
| } // namespace upb_test |