@ -40,8 +40,11 @@ namespace protobuf {
namespace {
using : : testing : : AllOf ;
using : : testing : : ElementsAre ;
using : : testing : : ExplainMatchResult ;
using : : testing : : HasSubstr ;
using : : testing : : IsEmpty ;
using : : testing : : UnorderedElementsAre ;
// TODO: Use the gtest versions once that's available in OSS.
template < typename T >
@ -459,6 +462,100 @@ TEST(FeatureResolverTest, MergeFeaturesDistantFuture) {
HasSubstr ( " maximum supported edition 99997_TEST_ONLY " ) ) ) ) ;
}
TEST ( FeatureResolverLifetimesTest , Valid ) {
FeatureSet features = ParseTextOrDie ( R " pb(
[ pb . test ] { file_feature : VALUE1 }
) pb " );
auto results = FeatureResolver : : ValidateFeatureLifetimes ( EDITION_2023 ,
features , nullptr ) ;
EXPECT_THAT ( results . errors , IsEmpty ( ) ) ;
EXPECT_THAT ( results . warnings , IsEmpty ( ) ) ;
}
TEST ( FeatureResolverLifetimesTest , DeprecatedFeature ) {
FeatureSet features = ParseTextOrDie ( R " pb(
[ pb . test ] { removed_feature : VALUE1 }
) pb " );
auto results = FeatureResolver : : ValidateFeatureLifetimes ( EDITION_2023 ,
features , nullptr ) ;
EXPECT_THAT ( results . errors , IsEmpty ( ) ) ;
EXPECT_THAT (
results . warnings ,
ElementsAre ( AllOf ( HasSubstr ( " pb.TestFeatures.removed_feature " ) ,
HasSubstr ( " deprecated in edition 2023 " ) ,
HasSubstr ( " Custom feature deprecation warning " ) ) ) ) ;
}
TEST ( FeatureResolverLifetimesTest , RemovedFeature ) {
FeatureSet features = ParseTextOrDie ( R " pb(
[ pb . test ] { removed_feature : VALUE1 }
) pb " );
auto results = FeatureResolver : : ValidateFeatureLifetimes ( EDITION_2024 ,
features , nullptr ) ;
EXPECT_THAT ( results . errors ,
ElementsAre ( AllOf ( HasSubstr ( " pb.TestFeatures.removed_feature " ) ,
HasSubstr ( " removed in edition 2024 " ) ) ) ) ;
EXPECT_THAT ( results . warnings , IsEmpty ( ) ) ;
}
TEST ( FeatureResolverLifetimesTest , NotIntroduced ) {
FeatureSet features = ParseTextOrDie ( R " pb(
[ pb . test ] { future_feature : VALUE1 }
) pb " );
auto results = FeatureResolver : : ValidateFeatureLifetimes ( EDITION_2023 ,
features , nullptr ) ;
EXPECT_THAT ( results . errors ,
ElementsAre ( AllOf ( HasSubstr ( " pb.TestFeatures.future_feature " ) ,
HasSubstr ( " introduced until edition 2024 " ) ) ) ) ;
EXPECT_THAT ( results . warnings , IsEmpty ( ) ) ;
}
TEST ( FeatureResolverLifetimesTest , WarningsAndErrors ) {
FeatureSet features = ParseTextOrDie ( R " pb(
[ pb . test ] { future_feature : VALUE1 removed_feature : VALUE1 }
) pb " );
auto results = FeatureResolver : : ValidateFeatureLifetimes ( EDITION_2023 ,
features , nullptr ) ;
EXPECT_THAT ( results . errors ,
ElementsAre ( HasSubstr ( " pb.TestFeatures.future_feature " ) ) ) ;
EXPECT_THAT ( results . warnings ,
ElementsAre ( HasSubstr ( " pb.TestFeatures.removed_feature " ) ) ) ;
}
TEST ( FeatureResolverLifetimesTest , MultipleErrors ) {
FeatureSet features = ParseTextOrDie ( R " pb(
[ pb . test ] { future_feature : VALUE1 legacy_feature : VALUE1 }
) pb " );
auto results = FeatureResolver : : ValidateFeatureLifetimes ( EDITION_2023 ,
features , nullptr ) ;
EXPECT_THAT ( results . errors , UnorderedElementsAre (
HasSubstr ( " pb.TestFeatures.future_feature " ) ,
HasSubstr ( " pb.TestFeatures.legacy_feature " ) ) ) ;
EXPECT_THAT ( results . warnings , IsEmpty ( ) ) ;
}
TEST ( FeatureResolverLifetimesTest , DynamicPool ) {
DescriptorPool pool ;
FileDescriptorProto file ;
FileDescriptorProto : : GetDescriptor ( ) - > file ( ) - > CopyTo ( & file ) ;
ASSERT_NE ( pool . BuildFile ( file ) , nullptr ) ;
pb : : TestFeatures : : GetDescriptor ( ) - > file ( ) - > CopyTo ( & file ) ;
ASSERT_NE ( pool . BuildFile ( file ) , nullptr ) ;
const Descriptor * feature_set =
pool . FindMessageTypeByName ( " google.protobuf.FeatureSet " ) ;
ASSERT_NE ( feature_set , nullptr ) ;
FeatureSet features = ParseTextOrDie ( R " pb(
[ pb . test ] { future_feature : VALUE1 removed_feature : VALUE1 }
) pb " );
auto results = FeatureResolver : : ValidateFeatureLifetimes (
EDITION_2023 , features , feature_set ) ;
EXPECT_THAT ( results . errors ,
ElementsAre ( HasSubstr ( " pb.TestFeatures.future_feature " ) ) ) ;
EXPECT_THAT ( results . warnings ,
ElementsAre ( HasSubstr ( " pb.TestFeatures.removed_feature " ) ) ) ;
}
class FakeErrorCollector : public io : : ErrorCollector {
public :
FakeErrorCollector ( ) = default ;
@ -560,7 +657,8 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithExtensions) {
extend Foo {
optional Foo bar2 = 1 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_2023 , value : " " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " " }
] ;
}
) schema " );
@ -586,11 +684,13 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithOneof) {
oneof x {
int32 int_field = 1 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_2023 , value : " 1 " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " 1 " }
] ;
string string_field = 2 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_2023 , value : " 'hello' " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " 'hello' " }
] ;
}
}
@ -616,7 +716,8 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithRequired) {
message Foo {
required int32 required_field = 1 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_2023 , value : " " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " " }
] ;
}
) schema " );
@ -641,7 +742,8 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithRepeated) {
message Foo {
repeated int32 repeated_field = 1 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_2023 , value : " 1 " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " 1 " }
] ;
}
) schema " );
@ -665,7 +767,8 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithMissingTarget) {
}
message Foo {
optional bool bool_field = 1 [
edition_defaults = { edition : EDITION_2023 , value : " true " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
@ -678,6 +781,298 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithMissingTarget) {
HasSubstr ( " no target specified " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest , CompileDefaultsInvalidWithMissingSupport ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_OK ( FeatureResolver : : CompileDefaults ( feature_set_ , { ext } , EDITION_2023 ,
EDITION_2023 ) ) ;
}
TEST_F ( FeatureResolverPoolTest ,
CompileDefaultsInvalidWithMissingEditionIntroduced ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = { } ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT ( FeatureResolver : : CompileDefaults ( feature_set_ , { ext } ,
EDITION_2023 , EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " it was introduced in " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest ,
CompileDefaultsInvalidWithMissingDeprecationWarning ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = {
edition_introduced : EDITION_2023
edition_deprecated : EDITION_2023
} ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT ( FeatureResolver : : CompileDefaults ( feature_set_ , { ext } ,
EDITION_2023 , EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " deprecation warning " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest , CompileDefaultsInvalidWithMissingDeprecation ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = {
edition_introduced : EDITION_2023
deprecation_warning : " some message "
} ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT ( FeatureResolver : : CompileDefaults ( feature_set_ , { ext } ,
EDITION_2023 , EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " is not marked deprecated " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest ,
CompileDefaultsInvalidDeprecatedBeforeIntroduced ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = {
edition_introduced : EDITION_2024
edition_deprecated : EDITION_2023
deprecation_warning : " warning "
} ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT (
FeatureResolver : : CompileDefaults ( feature_set_ , { ext } , EDITION_2023 ,
EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " deprecated before it was introduced " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest , CompileDefaultsInvalidDeprecatedAfterRemoved ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = {
edition_introduced : EDITION_2023
edition_deprecated : EDITION_2024
deprecation_warning : " warning "
edition_removed : EDITION_2024
} ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT ( FeatureResolver : : CompileDefaults ( feature_set_ , { ext } ,
EDITION_2023 , EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " deprecated after it was removed " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest , CompileDefaultsInvalidRemovedBeforeIntroduced ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = {
edition_introduced : EDITION_2024
edition_removed : EDITION_2023
} ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT ( FeatureResolver : : CompileDefaults ( feature_set_ , { ext } ,
EDITION_2023 , EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " removed before it was introduced " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest , CompileDefaultsInvalidMissingLegacyDefaults ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = {
edition_introduced : EDITION_2024
} ,
edition_defaults = { edition : EDITION_2024 , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT (
FeatureResolver : : CompileDefaults ( feature_set_ , { ext } , EDITION_2023 ,
EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " no default specified for EDITION_LEGACY " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest ,
CompileDefaultsInvalidDefaultsBeforeIntroduced ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = {
edition_introduced : EDITION_2024
} ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " } ,
edition_defaults = { edition : EDITION_2023 , value : " false " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT ( FeatureResolver : : CompileDefaults ( feature_set_ , { ext } ,
EDITION_2023 , EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " specified for edition 2023 " ) ,
HasSubstr ( " before it was introduced " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest , CompileDefaultsInvalidDefaultsAfterRemoved ) {
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 bool bool_field = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support = {
edition_introduced : EDITION_PROTO2
edition_removed : EDITION_2023
} ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " } ,
edition_defaults = { edition : EDITION_2024 , value : " true " }
] ;
}
) schema " );
ASSERT_NE ( file , nullptr ) ;
const FieldDescriptor * ext = file - > extension ( 0 ) ;
EXPECT_THAT ( FeatureResolver : : CompileDefaults ( feature_set_ , { ext } ,
EDITION_2023 , EDITION_2023 ) ,
HasError ( AllOf ( HasSubstr ( " test.Foo.bool_field " ) ,
HasSubstr ( " specified for edition 2024 " ) ,
HasSubstr ( " after it was removed " ) ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest ,
CompileDefaultsInvalidDefaultsScalarParsingError ) {
const FileDescriptor * file = ParseSchema ( R " schema(
@ -691,7 +1086,8 @@ TEST_F(FeatureResolverPoolTest,
message Foo {
optional bool field_feature = 12 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_PROTO2 , value : " 1.23 " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " 1.23 " }
] ;
}
) schema " );
@ -717,8 +1113,9 @@ TEST_F(FeatureResolverPoolTest,
message Foo {
optional bool field_feature = 12 [
targets = TARGET_TYPE_FIELD ,
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_99997_TEST_ONLY , value : " 1.5 " } ,
edition_defaults = { edition : EDITION_PROTO2 , value : " true " }
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
@ -747,7 +1144,9 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDefaultsTooEarly) {
message Foo {
optional bool field_feature = 12 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_2_TEST_ONLY , value : " true " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_2_TEST_ONLY , value : " true " } ,
edition_defaults = { edition : EDITION_LEGACY , value : " false " }
] ;
}
) schema " );
@ -772,7 +1171,8 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumTooEarly) {
message Foo {
optional bool field_feature = 12 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_PROTO2 , value : " true " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " true " }
] ;
}
) schema " );
@ -782,7 +1182,7 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumTooEarly) {
EXPECT_THAT (
FeatureResolver : : CompileDefaults ( feature_set_ , { ext } , EDITION_1_TEST_ONLY ,
EDITION_99997_TEST_ONLY ) ,
HasError ( HasSubstr ( " No valid default found for edition 1_TEST_ONLY" ) ) ) ;
HasError ( HasSubstr ( " edition 1_TEST_ONLY is earlier than the oldest " ) ) ) ;
}
TEST_F ( FeatureResolverPoolTest , CompileDefaultsMinimumCovered ) {
@ -803,9 +1203,10 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumCovered) {
message Foo {
optional Bar file_feature = 1 [
targets = TARGET_TYPE_FIELD ,
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_99998_TEST_ONLY , value : " VALUE3 " } ,
edition_defaults = { edition : EDITION_2023 , value : " VALUE2 " } ,
edition_defaults = { edition : EDITION_PROTO2 , value : " VALUE1 " }
edition_defaults = { edition : EDITION_LEGACY , value : " VALUE1 " }
] ;
}
) schema " );
@ -889,7 +1290,8 @@ TEST_P(FeatureUnboundedTypeTest, CompileDefaults) {
message Foo {
optional $ 0 field_feature = 12 [
targets = TARGET_TYPE_FIELD ,
edition_defaults = { edition : EDITION_PROTO2 , value : " 1 " }
feature_support . edition_introduced = EDITION_2023 ,
edition_defaults = { edition : EDITION_LEGACY , value : " 1 " }
] ;
}
) schema " ,