// Protocol Buffers - Google's data interchange format // Copyright 2023 Google Inc. All rights reserved. // https://developers.google.com/protocol-buffers/ // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are // met: // // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above // copyright notice, this list of conditions and the following disclaimer // in the documentation and/or other materials provided with the // distribution. // * Neither the name of Google Inc. nor the names of its // contributors may be used to endorse or promote products derived from // this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. #include "google/protobuf/feature_resolver.h" #include #include #include #include "absl/memory/memory.h" #include "absl/status/status.h" #include "absl/status/statusor.h" #include "google/protobuf/compiler/parser.h" #include "google/protobuf/descriptor.h" #include "google/protobuf/descriptor.pb.h" #include "google/protobuf/io/tokenizer.h" #include "google/protobuf/test_textproto.h" #include "google/protobuf/text_format.h" #include "google/protobuf/unittest_custom_options.pb.h" #include "google/protobuf/unittest_features.pb.h" #include "google/protobuf/stubs/status_macros.h" // Must be included last. #include "google/protobuf/port_def.inc" namespace google { namespace protobuf { namespace { using ::testing::AllOf; using ::testing::ExplainMatchResult; using ::testing::HasSubstr; // TODO(b/234474291): Use the gtest versions once that's available in OSS. absl::Status GetStatus(const absl::Status& s) { return s; } template absl::Status GetStatus(const absl::StatusOr& s) { return s.status(); } MATCHER_P(HasError, msg_matcher, "") { return GetStatus(arg).code() == absl::StatusCode::kFailedPrecondition && ExplainMatchResult(msg_matcher, GetStatus(arg).message(), result_listener); } MATCHER_P(StatusIs, status, absl::StrCat(".status() is ", testing::PrintToString(status))) { return GetStatus(arg).code() == status; } #define EXPECT_OK(x) EXPECT_THAT(x, StatusIs(absl::StatusCode::kOk)) #define ASSERT_OK(x) ASSERT_THAT(x, StatusIs(absl::StatusCode::kOk)) template const FileDescriptor& GetExtensionFile( const ExtensionT& ext, const Descriptor* descriptor = FeatureSet::descriptor()) { return *DescriptorPool::generated_pool() ->FindExtensionByNumber(descriptor, ext.number()) ->file(); } template absl::StatusOr SetupFeatureResolver(absl::string_view edition, Extensions... extensions) { auto resolver = FeatureResolver::Create(edition, FeatureSet::descriptor()); RETURN_IF_ERROR(resolver.status()); std::vector results{ resolver->RegisterExtensions(GetExtensionFile(extensions))...}; for (absl::Status result : results) { RETURN_IF_ERROR(result); } return resolver; } template absl::StatusOr GetDefaults(absl::string_view edition, Extensions... extensions) { absl::StatusOr resolver = SetupFeatureResolver(edition, extensions...); RETURN_IF_ERROR(resolver.status()); FeatureSet parent, child; return resolver->MergeFeatures(parent, child); } TEST(FeatureResolverTest, DefaultsCore2023) { absl::StatusOr merged = GetDefaults("2023"); ASSERT_OK(merged); EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT); EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN); EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED); EXPECT_EQ(merged->string_field_validation(), FeatureSet::MANDATORY); EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED); } TEST(FeatureResolverTest, DefaultsTest2023) { absl::StatusOr merged = GetDefaults("2023", pb::test); ASSERT_OK(merged); EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT); EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN); EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED); EXPECT_EQ(merged->string_field_validation(), FeatureSet::MANDATORY); EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED); const pb::TestFeatures& ext = merged->GetExtension(pb::test); EXPECT_EQ(ext.int_file_feature(), 1); EXPECT_EQ(ext.int_extension_range_feature(), 1); EXPECT_EQ(ext.int_message_feature(), 1); EXPECT_EQ(ext.int_field_feature(), 1); EXPECT_EQ(ext.int_oneof_feature(), 1); EXPECT_EQ(ext.int_enum_feature(), 1); EXPECT_EQ(ext.int_enum_entry_feature(), 1); EXPECT_EQ(ext.int_service_feature(), 1); EXPECT_EQ(ext.int_method_feature(), 1); EXPECT_EQ(ext.bool_field_feature(), false); EXPECT_FLOAT_EQ(ext.float_field_feature(), 1.1); EXPECT_THAT(ext.message_field_feature(), EqualsProto("bool_field: true int_field: 1 float_field: 1.5 " "string_field: '2023'")); EXPECT_EQ(ext.enum_field_feature(), pb::TestFeatures::ENUM_VALUE1); } TEST(FeatureResolverTest, DefaultsTestMessageExtension) { absl::StatusOr merged = GetDefaults("2023", pb::TestMessage::test_message); ASSERT_OK(merged); EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT); EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN); EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED); EXPECT_EQ(merged->string_field_validation(), FeatureSet::MANDATORY); EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED); const pb::TestFeatures& ext = merged->GetExtension(pb::test); EXPECT_EQ(ext.int_file_feature(), 1); EXPECT_EQ(ext.int_extension_range_feature(), 1); EXPECT_EQ(ext.int_message_feature(), 1); EXPECT_EQ(ext.int_field_feature(), 1); EXPECT_EQ(ext.int_oneof_feature(), 1); EXPECT_EQ(ext.int_enum_feature(), 1); EXPECT_EQ(ext.int_enum_entry_feature(), 1); EXPECT_EQ(ext.int_service_feature(), 1); EXPECT_EQ(ext.int_method_feature(), 1); EXPECT_EQ(ext.bool_field_feature(), false); EXPECT_FLOAT_EQ(ext.float_field_feature(), 1.1); EXPECT_THAT(ext.message_field_feature(), EqualsProto("bool_field: true int_field: 1 float_field: 1.5 " "string_field: '2023'")); EXPECT_EQ(ext.enum_field_feature(), pb::TestFeatures::ENUM_VALUE1); } TEST(FeatureResolverTest, DefaultsTestNestedExtension) { absl::StatusOr merged = GetDefaults("2023", pb::TestMessage::Nested::test_nested); ASSERT_OK(merged); EXPECT_EQ(merged->field_presence(), FeatureSet::EXPLICIT); EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN); EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED); EXPECT_EQ(merged->string_field_validation(), FeatureSet::MANDATORY); EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED); const pb::TestFeatures& ext = merged->GetExtension(pb::test); EXPECT_EQ(ext.int_file_feature(), 1); EXPECT_EQ(ext.int_extension_range_feature(), 1); EXPECT_EQ(ext.int_message_feature(), 1); EXPECT_EQ(ext.int_field_feature(), 1); EXPECT_EQ(ext.int_oneof_feature(), 1); EXPECT_EQ(ext.int_enum_feature(), 1); EXPECT_EQ(ext.int_enum_entry_feature(), 1); EXPECT_EQ(ext.int_service_feature(), 1); EXPECT_EQ(ext.int_method_feature(), 1); EXPECT_EQ(ext.bool_field_feature(), false); EXPECT_FLOAT_EQ(ext.float_field_feature(), 1.1); EXPECT_THAT(ext.message_field_feature(), EqualsProto("bool_field: true int_field: 1 float_field: 1.5 " "string_field: '2023'")); EXPECT_EQ(ext.enum_field_feature(), pb::TestFeatures::ENUM_VALUE1); } TEST(FeatureResolverTest, DefaultsTooEarly) { absl::StatusOr merged = GetDefaults("2022", pb::test); EXPECT_THAT(merged, HasError(AllOf(HasSubstr("No valid default found"), HasSubstr("2022")))); } TEST(FeatureResolverTest, DefaultsFarFuture) { absl::StatusOr merged = GetDefaults("3456", pb::test); ASSERT_OK(merged); pb::TestFeatures ext = merged->GetExtension(pb::test); EXPECT_EQ(ext.int_file_feature(), 5); EXPECT_THAT(ext.message_field_feature(), EqualsProto("bool_field: true int_field: 4 float_field: 1.5 " "string_field: '2025'")); EXPECT_EQ(ext.enum_field_feature(), pb::TestFeatures::ENUM_VALUE5); } TEST(FeatureResolverTest, DefaultsMiddleEdition) { absl::StatusOr merged = GetDefaults("2024.1", pb::test); ASSERT_OK(merged); pb::TestFeatures ext = merged->GetExtension(pb::test); EXPECT_EQ(ext.int_file_feature(), 4); EXPECT_EQ(ext.enum_field_feature(), pb::TestFeatures::ENUM_VALUE4); } TEST(FeatureResolverTest, DefaultsDifferentDigitCount) { absl::StatusOr merged = GetDefaults("2023.90", pb::test); ASSERT_OK(merged); pb::TestFeatures ext = merged->GetExtension(pb::test); EXPECT_EQ(ext.int_file_feature(), 3); EXPECT_EQ(ext.enum_field_feature(), pb::TestFeatures::ENUM_VALUE3); } TEST(FeatureResolverTest, DefaultsMessageMerge) { { absl::StatusOr merged = GetDefaults("2023", pb::test); ASSERT_OK(merged); pb::TestFeatures ext = merged->GetExtension(pb::test); EXPECT_THAT(ext.message_field_feature(), EqualsProto(R"pb(bool_field: true int_field: 1 float_field: 1.5 string_field: '2023')pb")); } { absl::StatusOr merged = GetDefaults("2023.1", pb::test); ASSERT_OK(merged); pb::TestFeatures ext = merged->GetExtension(pb::test); EXPECT_THAT(ext.message_field_feature(), EqualsProto(R"pb(bool_field: true int_field: 2 float_field: 1.5 string_field: '2023')pb")); } { absl::StatusOr merged = GetDefaults("2024", pb::test); ASSERT_OK(merged); pb::TestFeatures ext = merged->GetExtension(pb::test); EXPECT_THAT(ext.message_field_feature(), EqualsProto(R"pb(bool_field: true int_field: 2 float_field: 1.5 string_field: '2024')pb")); } } TEST(FeatureResolverTest, InitializePoolMissingDescriptor) { DescriptorPool pool; EXPECT_THAT(FeatureResolver::Create("2023", nullptr), HasError(HasSubstr("find definition of google.protobuf.FeatureSet"))); } TEST(FeatureResolverTest, RegisterExtensionDifferentContainingType) { auto resolver = FeatureResolver::Create("2023", FeatureSet::descriptor()); ASSERT_OK(resolver); EXPECT_OK(resolver->RegisterExtensions( GetExtensionFile(protobuf_unittest::file_opt1, FileOptions::descriptor()))); } TEST(FeatureResolverTest, RegisterExtensionTwice) { auto resolver = FeatureResolver::Create("2023", FeatureSet::descriptor()); ASSERT_OK(resolver); EXPECT_OK(resolver->RegisterExtensions(GetExtensionFile(pb::test))); EXPECT_OK(resolver->RegisterExtensions(GetExtensionFile(pb::test))); } TEST(FeatureResolverTest, MergeFeaturesChildOverrideCore) { absl::StatusOr resolver = SetupFeatureResolver("2023"); ASSERT_OK(resolver); FeatureSet child = ParseTextOrDie(R"pb( field_presence: IMPLICIT repeated_field_encoding: EXPANDED )pb"); absl::StatusOr merged = resolver->MergeFeatures(FeatureSet(), child); ASSERT_OK(merged); EXPECT_EQ(merged->field_presence(), FeatureSet::IMPLICIT); EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN); EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::EXPANDED); EXPECT_EQ(merged->string_field_validation(), FeatureSet::MANDATORY); EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED); } TEST(FeatureResolverTest, MergeFeaturesChildOverrideComplex) { absl::StatusOr resolver = SetupFeatureResolver("2023", pb::test); ASSERT_OK(resolver); FeatureSet child = ParseTextOrDie(R"pb( field_presence: IMPLICIT repeated_field_encoding: EXPANDED [pb.test] { int_field_feature: 5 enum_field_feature: ENUM_VALUE4 message_field_feature { int_field: 10 } } )pb"); absl::StatusOr merged = resolver->MergeFeatures(FeatureSet(), child); ASSERT_OK(merged); EXPECT_EQ(merged->field_presence(), FeatureSet::IMPLICIT); EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN); EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::EXPANDED); EXPECT_EQ(merged->string_field_validation(), FeatureSet::MANDATORY); EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED); pb::TestFeatures ext = merged->GetExtension(pb::test); EXPECT_EQ(ext.int_file_feature(), 1); EXPECT_EQ(ext.int_field_feature(), 5); EXPECT_FLOAT_EQ(ext.float_field_feature(), 1.1); EXPECT_THAT(ext.message_field_feature(), EqualsProto("bool_field: true int_field: 10 float_field: 1.5 " "string_field: '2023'")); EXPECT_EQ(ext.enum_field_feature(), pb::TestFeatures::ENUM_VALUE4); } TEST(FeatureResolverTest, MergeFeaturesParentOverrides) { absl::StatusOr resolver = SetupFeatureResolver("2023", pb::test); ASSERT_OK(resolver); FeatureSet parent = ParseTextOrDie(R"pb( field_presence: IMPLICIT repeated_field_encoding: EXPANDED [pb.test] { int_field_feature: 5 enum_field_feature: ENUM_VALUE4 message_field_feature { int_field: 10 string_field: "parent" } } )pb"); FeatureSet child = ParseTextOrDie(R"pb( string_field_validation: NONE repeated_field_encoding: PACKED [pb.test] { int_field_feature: 9 message_field_feature { bool_field: false int_field: 9 } } )pb"); absl::StatusOr merged = resolver->MergeFeatures(parent, child); ASSERT_OK(merged); EXPECT_EQ(merged->field_presence(), FeatureSet::IMPLICIT); EXPECT_EQ(merged->enum_type(), FeatureSet::OPEN); EXPECT_EQ(merged->repeated_field_encoding(), FeatureSet::PACKED); EXPECT_EQ(merged->string_field_validation(), FeatureSet::NONE); EXPECT_EQ(merged->message_encoding(), FeatureSet::LENGTH_PREFIXED); pb::TestFeatures ext = merged->GetExtension(pb::test); EXPECT_EQ(ext.int_file_feature(), 1); EXPECT_EQ(ext.int_extension_range_feature(), 1); EXPECT_EQ(ext.int_message_feature(), 1); EXPECT_EQ(ext.int_field_feature(), 9); EXPECT_EQ(ext.int_oneof_feature(), 1); EXPECT_EQ(ext.int_enum_feature(), 1); EXPECT_EQ(ext.int_enum_entry_feature(), 1); EXPECT_EQ(ext.int_service_feature(), 1); EXPECT_EQ(ext.int_method_feature(), 1); EXPECT_EQ(ext.bool_field_feature(), false); EXPECT_FLOAT_EQ(ext.float_field_feature(), 1.1); EXPECT_THAT(ext.message_field_feature(), EqualsProto("bool_field: false int_field: 9 float_field: 1.5 " "string_field: 'parent'")); EXPECT_EQ(ext.enum_field_feature(), pb::TestFeatures::ENUM_VALUE4); } TEST(FeatureResolverTest, MergeFeaturesFieldPresenceUnknown) { absl::StatusOr resolver = SetupFeatureResolver("2023"); ASSERT_OK(resolver); FeatureSet child = ParseTextOrDie(R"pb( field_presence: FIELD_PRESENCE_UNKNOWN )pb"); EXPECT_THAT( resolver->MergeFeatures(FeatureSet(), child), HasError(AllOf(HasSubstr("field google.protobuf.FeatureSet.field_presence"), HasSubstr("FIELD_PRESENCE_UNKNOWN")))); } TEST(FeatureResolverTest, MergeFeaturesEnumTypeUnknown) { absl::StatusOr resolver = SetupFeatureResolver("2023"); ASSERT_OK(resolver); FeatureSet child = ParseTextOrDie(R"pb( enum_type: ENUM_TYPE_UNKNOWN )pb"); EXPECT_THAT(resolver->MergeFeatures(FeatureSet(), child), HasError(AllOf(HasSubstr("field google.protobuf.FeatureSet.enum_type"), HasSubstr("ENUM_TYPE_UNKNOWN")))); } TEST(FeatureResolverTest, MergeFeaturesRepeatedFieldEncodingUnknown) { absl::StatusOr resolver = SetupFeatureResolver("2023"); ASSERT_OK(resolver); FeatureSet child = ParseTextOrDie(R"pb( repeated_field_encoding: REPEATED_FIELD_ENCODING_UNKNOWN )pb"); EXPECT_THAT(resolver->MergeFeatures(FeatureSet(), child), HasError(AllOf( HasSubstr("field google.protobuf.FeatureSet.repeated_field_encoding"), HasSubstr("REPEATED_FIELD_ENCODING_UNKNOWN")))); } TEST(FeatureResolverTest, MergeFeaturesStringFieldValidationUnknown) { absl::StatusOr resolver = SetupFeatureResolver("2023"); ASSERT_OK(resolver); FeatureSet child = ParseTextOrDie(R"pb( string_field_validation: STRING_FIELD_VALIDATION_UNKNOWN )pb"); EXPECT_THAT(resolver->MergeFeatures(FeatureSet(), child), HasError(AllOf( HasSubstr("field google.protobuf.FeatureSet.string_field_validation"), HasSubstr("STRING_FIELD_VALIDATION_UNKNOWN")))); } TEST(FeatureResolverTest, MergeFeaturesMessageEncodingUnknown) { absl::StatusOr resolver = SetupFeatureResolver("2023"); ASSERT_OK(resolver); FeatureSet child = ParseTextOrDie(R"pb( message_encoding: MESSAGE_ENCODING_UNKNOWN )pb"); EXPECT_THAT( resolver->MergeFeatures(FeatureSet(), child), HasError(AllOf(HasSubstr("field google.protobuf.FeatureSet.message_encoding"), HasSubstr("MESSAGE_ENCODING_UNKNOWN")))); } TEST(FeatureResolverTest, MergeFeaturesExtensionEnumUnknown) { absl::StatusOr resolver = SetupFeatureResolver("2023", pb::test); ASSERT_OK(resolver); FeatureSet child = ParseTextOrDie(R"pb( [pb.test] { enum_field_feature: TEST_ENUM_FEATURE_UNKNOWN } )pb"); absl::StatusOr merged = resolver->MergeFeatures(FeatureSet(), child); ASSERT_OK(merged); EXPECT_EQ(merged->GetExtension(pb::test).enum_field_feature(), pb::TestFeatures::TEST_ENUM_FEATURE_UNKNOWN); } // TODO(b/273611177) Move the following tests to descriptor_unittest.cc once // FeatureResolver is hooked up. These will all fail during the descriptor // build, invalidating the test setup. // // Note: We should make sure to keep coverage over the custom DescriptorPool // cases. These are the only tests covering issues there (the ones above use // the generated descriptor pool). class FakeErrorCollector : public io::ErrorCollector { public: FakeErrorCollector() = default; ~FakeErrorCollector() override = default; void RecordWarning(int line, int column, absl::string_view message) override { ABSL_LOG(WARNING) << line << ":" << column << ": " << message; } void RecordError(int line, int column, absl::string_view message) override { ABSL_LOG(ERROR) << line << ":" << column << ": " << message; } }; class FeatureResolverPoolTest : public testing::Test { protected: void SetUp() override { FileDescriptorProto file; FileDescriptorProto::GetDescriptor()->file()->CopyTo(&file); ASSERT_NE(pool_.BuildFile(file), nullptr); } const FileDescriptor* ParseSchema(absl::string_view schema) { FakeErrorCollector error_collector; io::ArrayInputStream raw_input(schema.data(), schema.size()); io::Tokenizer input(&raw_input, &error_collector); compiler::Parser parser; parser.RecordErrorsTo(&error_collector); FileDescriptorProto file; ABSL_CHECK(parser.Parse(&input, &file)); file.set_name("foo.proto"); return pool_.BuildFile(file); } DescriptorPool pool_; FileDescriptorProto file_proto_; }; TEST_F(FeatureResolverPoolTest, RegisterExtensionNonMessage) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; message Foo {} extend google.protobuf.FeatureSet { optional string bar = 9999; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT(resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("test.bar"), HasSubstr("is not of message type")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionRepeated) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; message Foo {} extend google.protobuf.FeatureSet { repeated Foo bar = 9999; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT( resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("test.bar"), HasSubstr("repeated extension")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionWithExtensions) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; message Foo { extensions 1; } extend google.protobuf.FeatureSet { optional Foo bar = 9999; } extend Foo { optional Foo bar2 = 1 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2023", value: "" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT( resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("test.bar"), HasSubstr("Nested extensions")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionWithOneof) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { oneof x { int32 int_field = 1 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2023", value: "1" } ]; string string_field = 2 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2023", value: "'hello'" } ]; } } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT(resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("test.Foo"), HasSubstr("oneof feature fields")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionWithRequired) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { required int32 required_field = 1 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2023", value: "" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT(resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("test.Foo.required_field"), HasSubstr("required field")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionWithRepeated) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { repeated int32 repeated_field = 1 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2023", value: "1" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT(resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("test.Foo.repeated_field"), HasSubstr("repeated field")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionWithMissingTarget) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { optional int32 int_field = 1 [ edition_defaults = { edition: "2023", value: "1" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT(resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("test.Foo.int_field"), HasSubstr("no target specified")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionDefaultsMessageParsingError) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { message MessageFeature { optional int32 int_field = 1; } optional MessageFeature message_field_feature = 12 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2023", value: "9987" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT( resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("in edition_defaults"), HasSubstr("9987")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionDefaultsMessageParsingErrorMerged) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { message MessageFeature { optional int32 int_field = 1; } optional MessageFeature message_field_feature = 12 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2025", value: "int_field: 2" }, edition_defaults = { edition: "2024", value: "int_field: 1" }, edition_defaults = { edition: "2023", value: "9987" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2024", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT( resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("in edition_defaults"), HasSubstr("9987")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionDefaultsMessageParsingErrorSkipped) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { message MessageFeature { optional int32 int_field = 1; } optional MessageFeature message_field_feature = 12 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2024", value: "int_field: 2" }, edition_defaults = { edition: "2023", value: "int_field: 1" }, edition_defaults = { edition: "2025", value: "9987" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2024", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_OK(resolver->RegisterExtensions(*file)); FeatureSet parent, child; EXPECT_OK(resolver->MergeFeatures(parent, child)); } TEST_F(FeatureResolverPoolTest, RegisterExtensionDefaultsScalarParsingError) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { optional int32 int_field_feature = 12 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2023", value: "1.23" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_THAT( resolver->RegisterExtensions(*file), HasError(AllOf(HasSubstr("in edition_defaults"), HasSubstr("1.23")))); } TEST_F(FeatureResolverPoolTest, RegisterExtensionDefaultsScalarParsingErrorSkipped) { const FileDescriptor* file = ParseSchema(R"schema( syntax = "proto2"; package test; import "google/protobuf/descriptor.proto"; extend google.protobuf.FeatureSet { optional Foo bar = 9999; } message Foo { optional int32 int_field_feature = 12 [ targets = TARGET_TYPE_FIELD, edition_defaults = { edition: "2024", value: "1.5" }, edition_defaults = { edition: "2023", value: "1" } ]; } )schema"); ASSERT_NE(file, nullptr); auto resolver = FeatureResolver::Create( "2023", pool_.FindMessageTypeByName("google.protobuf.FeatureSet")); ASSERT_OK(resolver); EXPECT_OK(resolver->RegisterExtensions(*file)); FeatureSet parent, child; EXPECT_OK(resolver->MergeFeatures(parent, child)); } } // namespace } // namespace protobuf } // namespace google #include "google/protobuf/port_undef.inc"