Generalizing and implementing ValidateFeatureSupport for both Options and Features during proto parsing

PiperOrigin-RevId: 845387685
This commit is contained in:
Karen Wu 2025-12-16 12:26:33 -08:00 committed by Copybara-Service
parent ab964c0c80
commit ed3c57114d
10 changed files with 465 additions and 222 deletions

View file

@ -92,5 +92,6 @@ else()
)
set(protobuf_ABSL_USED_TEST_TARGETS
absl::scoped_mock_log
absl::status_matchers
)
endif ()

View file

@ -1893,6 +1893,7 @@ cc_test(
"@abseil-cpp//absl/log:die_if_null",
"@abseil-cpp//absl/memory",
"@abseil-cpp//absl/status",
"@abseil-cpp//absl/status:status_matchers",
"@abseil-cpp//absl/status:statusor",
"@abseil-cpp//absl/strings",
"@googletest//:gtest",

View file

@ -1367,6 +1367,7 @@ int CommandLineInterface::Run(int argc, const char* const argv[]) {
descriptor_pool->EnforceWeakDependencies(true);
descriptor_pool->EnforceSymbolVisibility(true);
descriptor_pool->EnforceNamingStyle(true);
descriptor_pool->EnforceFeatureSupportValidation(true);
if (!SetupFeatureResolution(*descriptor_pool)) {
return EXIT_FAILURE;

View file

@ -1617,6 +1617,41 @@ TEST_F(CommandLineInterfaceTest, ImportOptions_MissingImport) {
ExpectErrorSubstring("options.proto: File not found");
}
TEST_F(CommandLineInterfaceTest, ValidateFeatureSupportError) {
CreateTempFile("foo.proto",
R"schema(
edition = "2023";
option features.field_presence = IMPLICIT;
message Foo {
int32 bar = 1 [
feature_support = {
edition_removed: EDITION_2023
}
];
})schema");
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir foo.proto");
ExpectErrorSubstring(
"foo.proto: Foo.bar has been removed but does not specify a removal "
"error.");
}
TEST_F(CommandLineInterfaceTest, ValidateFeatureSupportValid) {
CreateTempFile("foo.proto",
R"schema(
edition = "2023";
option features.field_presence = IMPLICIT;
message Foo {
int32 bar = 1 [
feature_support = {
edition_removed: EDITION_2023
removal_error: "Custom removal error"
}
];
})schema");
Run("protocol_compiler --proto_path=$tmpdir --test_out=$tmpdir foo.proto");
ExpectNoErrors();
}
TEST_F(CommandLineInterfaceTest, FeatureValidationError) {
CreateTempFile("foo.proto",
R"schema(

View file

@ -1638,7 +1638,9 @@ class DescriptorPool::DeferredValidation {
}
bool Validate() {
if (lifetimes_info_map_.empty()) return true;
if (lifetimes_info_map_.empty()) {
return true;
}
static absl::string_view feature_set_name = "google.protobuf.FeatureSet";
bool has_errors = false;
@ -5013,6 +5015,9 @@ class DescriptorBuilder {
const DescriptorProto::ExtensionRange& proto) {}
void ValidateExtensionRangeOptions(const DescriptorProto& proto,
const Descriptor& message);
void MaybeAddFeatureSupportError(const absl::Status& feature_support_status,
const Message& proto,
absl::string_view full_name);
void ValidateExtensionDeclaration(
absl::string_view full_name,
const RepeatedPtrField<ExtensionRangeOptions_Declaration>& declarations,
@ -8503,11 +8508,28 @@ void DescriptorBuilder::ValidateOptions(const OneofDescriptor* /*oneof*/,
}
void DescriptorBuilder::MaybeAddFeatureSupportError(
const absl::Status& feature_support_status, const Message& proto,
absl::string_view full_name) {
if (feature_support_status.ok()) {
return;
}
std::string feature_support_error(feature_support_status.message());
AddError(full_name, proto, DescriptorPool::ErrorCollector::OPTION_NAME,
feature_support_error.c_str());
}
void DescriptorBuilder::ValidateOptions(const FieldDescriptor* field,
const FieldDescriptorProto& proto) {
if (pool_->lazily_build_dependencies_ && (!field || !field->message_type())) {
return;
}
if (pool_->enforce_feature_support_validation_) {
absl::Status feature_support_status =
FeatureResolver::ValidateFieldFeatureSupport(*field);
MaybeAddFeatureSupportError(feature_support_status, proto,
field->full_name());
}
ValidateFieldFeatures(field, proto);
@ -8838,10 +8860,15 @@ void DescriptorBuilder::ValidateOptions(const EnumDescriptor* enm,
}
}
void DescriptorBuilder::ValidateOptions(
const EnumValueDescriptor* /* enum_value */,
const EnumValueDescriptorProto& /* proto */) {
// Nothing to do so far.
void DescriptorBuilder::ValidateOptions(const EnumValueDescriptor* enum_value,
const EnumValueDescriptorProto& proto) {
if (pool_->enforce_feature_support_validation_) {
absl::Status feature_support_result =
FeatureResolver::ValidateFeatureSupport(
enum_value->options().feature_support(), enum_value->full_name());
MaybeAddFeatureSupportError(feature_support_result, proto,
enum_value->full_name());
}
}
namespace {

View file

@ -1594,9 +1594,9 @@ class PROTOBUF_EXPORT EnumValueDescriptor : private internal::SymbolBaseN<0>,
EnumValueDescriptor& operator=(const EnumValueDescriptor&) = delete;
#endif
absl::string_view name() const; // Name of this enum constant.
int index() const; // Index within the enums's Descriptor.
int number() const; // Numeric value of this enum constant.
absl::string_view name() const; // Name of this enum constant.
int index() const; // Index within the enums's Descriptor.
int number() const; // Numeric value of this enum constant.
// The full_name of an enum value is a sibling symbol of the enum type.
// e.g. the full name of FieldDescriptorProto::TYPE_INT32 is actually
@ -2365,6 +2365,14 @@ class PROTOBUF_EXPORT DescriptorPool {
// of this enforcement.
void EnforceNamingStyle(bool enforce) { enforce_naming_style_ = enforce; }
// Enforce validation of feature support.
//
// This is used to guard feature support validation for the lifetimes of
// options and features.
void EnforceFeatureSupportValidation(bool enforce) {
enforce_feature_support_validation_ = enforce;
}
// Enforce symbol visibility rules. This will enable enforcement of the
// `export` and `local` keywords added in edition 2024, honoring the behavior
// of the `default_symbol_visibility` feature.
@ -2670,6 +2678,7 @@ class PROTOBUF_EXPORT DescriptorPool {
bool disallow_enforce_utf8_;
bool deprecated_legacy_json_field_conflicts_;
bool enforce_naming_style_;
bool enforce_feature_support_validation_ = false;
bool enforce_symbol_visibility_ = false;
mutable bool build_started_ = false;
@ -3221,8 +3230,8 @@ PROTOBUF_EXPORT inline bool IsTrackingEnabled() {
}
template <typename F>
auto VisitDescriptorsInFileOrder(const Descriptor* desc,
F& f) -> decltype(f(desc)) {
auto VisitDescriptorsInFileOrder(const Descriptor* desc, F& f)
-> decltype(f(desc)) {
for (int i = 0; i < desc->nested_type_count(); i++) {
if (auto res = VisitDescriptorsInFileOrder(desc->nested_type(i), f)) {
return res;
@ -3238,8 +3247,8 @@ auto VisitDescriptorsInFileOrder(const Descriptor* desc,
// If any call returns a "truthy" value, it stops visitation and returns that
// value right away. Otherwise returns `{}` after visiting all types.
template <typename F>
auto VisitDescriptorsInFileOrder(const FileDescriptor* file,
F f) -> decltype(f(file->message_type(0))) {
auto VisitDescriptorsInFileOrder(const FileDescriptor* file, F f)
-> decltype(f(file->message_type(0))) {
for (int i = 0; i < file->message_type_count(); i++) {
if (auto res = VisitDescriptorsInFileOrder(file->message_type(i), f)) {
return res;

View file

@ -4904,6 +4904,154 @@ TEST(CustomOptions, DebugString) {
descriptor->DebugString());
}
TEST(CustomOptions, FeatureSupportInvalidDeprecatedAfterRemoved) {
DescriptorPool pool;
pool.EnforceFeatureSupportValidation(true);
FileDescriptorProto file_proto;
FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
ASSERT_TRUE(pool.BuildFile(file_proto) != nullptr);
ASSERT_TRUE(TextFormat::ParseFromString(
R"pb(
name: "foo.proto"
edition: EDITION_2024
package: "proto2_unittest"
dependency: "google/protobuf/descriptor.proto"
extension {
name: "file_opt1"
number: 7739974
label: LABEL_OPTIONAL
type: TYPE_UINT64
extendee: ".google.protobuf.FieldOptions"
options {
feature_support {
edition_introduced: EDITION_2023
edition_deprecated: EDITION_2024
deprecation_warning: "warning"
edition_removed: EDITION_2024
removal_error: "Custom feature removal error"
}
}
})pb",
&file_proto));
MockErrorCollector error_collector;
EXPECT_FALSE(pool.BuildFileCollectingErrors(file_proto, &error_collector));
EXPECT_EQ(error_collector.text_,
"foo.proto: proto2_unittest.file_opt1: OPTION_NAME: proto"
"2_unittest.file_opt1 was deprecated after it was removed.\n");
}
TEST(CustomOptions, FeatureSupportInvalidValueDeprecatedAfterOption) {
DescriptorPool pool;
pool.EnforceFeatureSupportValidation(true);
FileDescriptorProto file_proto;
FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
ASSERT_TRUE(pool.BuildFile(file_proto) != nullptr);
ASSERT_TRUE(TextFormat::ParseFromString(
R"pb(
name: "foo.proto"
edition: EDITION_2024
package: "proto2_unittest"
dependency: "google/protobuf/descriptor.proto"
enum_type {
name: "Foo"
value { name: "UNKNOWN" number: 0 }
value {
name: "VALUE"
number: 1
options {
feature_support {
edition_deprecated: EDITION_99997_TEST_ONLY
deprecation_warning: "warning"
}
}
}
}
message_type {
name: "Bar"
extension {
name: "bool_field"
number: 7739973
label: LABEL_OPTIONAL
type: TYPE_ENUM
type_name: "Foo"
extendee: ".google.protobuf.FieldOptions"
options {
feature_support {
edition_introduced: EDITION_2023
edition_deprecated: EDITION_2024
deprecation_warning: "warning"
}
}
}
})pb",
&file_proto));
MockErrorCollector error_collector;
EXPECT_FALSE(pool.BuildFileCollectingErrors(file_proto, &error_collector));
EXPECT_THAT(error_collector.text_,
testing::HasSubstr(
"foo.proto: proto2_unittest.Bar.bool_field: "
"OPTION_NAME: value proto2_unittest.VALUE was "
"deprecated after proto2_unittest.Bar.bool_field was.\n"));
}
TEST(CustomOptions, FeatureSupportValid) {
DescriptorPool pool;
pool.EnforceFeatureSupportValidation(true);
FileDescriptorProto file_proto;
FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
ASSERT_TRUE(pool.BuildFile(file_proto) != nullptr);
ASSERT_TRUE(TextFormat::ParseFromString(
R"pb(
name: "foo.proto"
edition: EDITION_2024
package: "proto2_unittest"
dependency: "google/protobuf/descriptor.proto"
enum_type {
name: "Foo"
value { name: "UNKNOWN" number: 0 }
value {
name: "VALUE"
number: 1
options {
feature_support {
edition_introduced: EDITION_2024
edition_deprecated: EDITION_99997_TEST_ONLY
deprecation_warning: "warning"
}
}
}
}
message_type {
name: "Bar"
extension {
name: "bool_field"
number: 7739971
label: LABEL_OPTIONAL
type: TYPE_ENUM
type_name: "Foo"
extendee: ".google.protobuf.FieldOptions"
options {
feature_support {
edition_introduced: EDITION_2023
edition_removed: EDITION_99998_TEST_ONLY
removal_error: "removed"
}
}
}
})pb",
&file_proto));
EXPECT_NE(pool.BuildFile(file_proto), nullptr);
}
// ===================================================================
TEST_F(ValidationErrorTest, AlreadyDefined) {

View file

@ -54,39 +54,7 @@ absl::Status Error(Args... args) {
return absl::FailedPreconditionError(absl::StrCat(args...));
}
absl::Status ValidateFeatureSupport(const FieldOptions::FeatureSupport& support,
absl::string_view full_name) {
if (support.has_edition_deprecated()) {
if (support.edition_deprecated() < support.edition_introduced()) {
return Error("Feature ", full_name,
" was deprecated before it was introduced.");
}
if (!support.has_deprecation_warning()) {
return Error(
"Feature ", full_name,
" is deprecated but does not specify a deprecation warning.");
}
}
if (!support.has_edition_deprecated() && support.has_deprecation_warning()) {
return Error("Feature ", full_name,
" specifies a deprecation warning but is not marked "
"deprecated in any edition.");
}
if (support.has_edition_removed()) {
if (support.edition_deprecated() >= support.edition_removed()) {
return Error("Feature ", full_name,
" was deprecated after it was removed.");
}
if (support.edition_removed() < support.edition_introduced()) {
return Error("Feature ", full_name,
" was removed before it was introduced.");
}
}
return absl::OkStatus();
}
absl::Status ValidateFieldFeatureSupport(const FieldDescriptor& field) {
absl::Status ValidateFieldDescriptor(const FieldDescriptor& field) {
if (!field.options().has_feature_support()) {
return Error("Feature field ", field.full_name(),
" has no feature support specified.");
@ -98,7 +66,6 @@ absl::Status ValidateFieldFeatureSupport(const FieldDescriptor& field) {
return Error("Feature field ", field.full_name(),
" does not specify the edition it was introduced in.");
}
RETURN_IF_ERROR(ValidateFeatureSupport(support, field.full_name()));
// Validate edition defaults specification wrt support windows.
for (const auto& d : field.options().edition_defaults()) {
@ -123,49 +90,36 @@ absl::Status ValidateFieldFeatureSupport(const FieldDescriptor& field) {
return absl::OkStatus();
}
absl::Status ValidateValueFeatureSupport(
absl::Status ValidateEnumValueFeatureSupport(
const FieldOptions::FeatureSupport& parent,
const EnumValueDescriptor& value, absl::string_view field_name) {
if (!value.options().has_feature_support()) {
// We allow missing support windows on feature values, and they'll inherit
// from the feature spec.
// We allow missing support windows on feature values, and they'll
// inherit from the feature spec.
// We will skip validation when parent has no feature support.
if (!value.options().has_feature_support() ||
&parent == &FieldOptions::FeatureSupport::default_instance()) {
return absl::OkStatus();
}
FieldOptions::FeatureSupport support = parent;
support.MergeFrom(value.options().feature_support());
RETURN_IF_ERROR(ValidateFeatureSupport(support, value.full_name()));
RETURN_IF_ERROR(
FeatureResolver::ValidateFeatureSupport(support, value.full_name()));
// Make sure the value doesn't expand any bounds.
if (support.edition_introduced() < parent.edition_introduced()) {
return Error("Feature value ", value.full_name(),
" was introduced before feature ", field_name, " was.");
return Error("value ", value.full_name(), " was introduced before ",
field_name, " was.");
}
if (parent.has_edition_removed() &&
support.edition_removed() > parent.edition_removed()) {
return Error("Feature value ", value.full_name(),
" was removed after feature ", field_name, " was.");
return Error("value ", value.full_name(), " was removed after ", field_name,
" was.");
}
if (parent.has_edition_deprecated() &&
support.edition_deprecated() > parent.edition_deprecated()) {
return Error("Feature value ", value.full_name(),
" was deprecated after feature ", field_name, " was.");
}
return absl::OkStatus();
}
absl::Status ValidateValuesFeatureSupport(const FieldDescriptor& field) {
// This only applies to enum features.
ABSL_CHECK(field.enum_type() != nullptr);
const FieldOptions::FeatureSupport& parent =
field.options().feature_support();
for (int i = 0; i < field.enum_type()->value_count(); ++i) {
const EnumValueDescriptor& value = *field.enum_type()->value(i);
RETURN_IF_ERROR(
ValidateValueFeatureSupport(parent, value, field.full_name()));
return Error("value ", value.full_name(), " was deprecated after ",
field_name, " was.");
}
return absl::OkStatus();
@ -210,10 +164,7 @@ absl::Status ValidateDescriptor(const Descriptor& descriptor) {
"was introduced.");
}
RETURN_IF_ERROR(ValidateFieldFeatureSupport(field));
if (field.enum_type() != nullptr) {
RETURN_IF_ERROR(ValidateValuesFeatureSupport(field));
}
RETURN_IF_ERROR(ValidateFieldDescriptor(field));
}
return absl::OkStatus();
@ -597,6 +548,61 @@ FeatureResolver::ValidationResults FeatureResolver::ValidateFeatureLifetimes(
return results;
}
absl::Status FeatureResolver::ValidateFeatureSupport(
const FieldOptions::FeatureSupport& support, absl::string_view full_name) {
if (support.has_edition_deprecated()) {
if (support.edition_deprecated() < support.edition_introduced()) {
return Error(full_name, " was deprecated before it was introduced.");
}
if (!support.has_deprecation_warning()) {
return Error(
full_name,
" is deprecated but does not specify a deprecation warning.");
}
}
if (!support.has_edition_deprecated() && support.has_deprecation_warning()) {
return Error(full_name,
" specifies a deprecation warning but is not marked "
"deprecated in any edition.");
}
if (support.has_edition_removed()) {
if (support.edition_deprecated() >= support.edition_removed()) {
return Error(full_name, " was deprecated after it was removed.");
}
if (support.edition_removed() < support.edition_introduced()) {
return Error(full_name, " was removed before it was introduced.");
}
// Not enforcing removal errors on features or options that have been
// introduced and removed in the same edition
if ((support.edition_introduced() != support.edition_removed()) &&
!support.has_removal_error()) {
return Error(full_name,
" has been removed but does not specify a removal error.");
}
} else if (support.has_removal_error()) {
return Error(full_name,
" specifies a removal error but is not marked removed in any "
"edition.");
}
return absl::OkStatus();
}
absl::Status FeatureResolver::ValidateFieldFeatureSupport(
const FieldDescriptor& field) {
const FieldOptions::FeatureSupport& parent =
field.options().feature_support();
RETURN_IF_ERROR(ValidateFeatureSupport(parent, field.full_name()));
if (field.enum_type() != nullptr) {
for (int i = 0; i < field.enum_type()->value_count(); ++i) {
const EnumValueDescriptor& value = *field.enum_type()->value(i);
RETURN_IF_ERROR(
ValidateEnumValueFeatureSupport(parent, value, field.full_name()));
}
}
return absl::OkStatus();
}
namespace internal {
absl::StatusOr<FeatureSet> GetEditionFeatureSetDefaults(
Edition edition, const FeatureSetDefaults& defaults) {

View file

@ -73,6 +73,21 @@ class PROTOBUF_EXPORT FeatureResolver {
Edition edition, const Message& option,
const Descriptor* pool_descriptor);
// Validates feature support on features and options
// to enforce feature support to be written correctly.
//
// This will return error status and error message for incorrectly written
// feature support.
static absl::Status ValidateFeatureSupport(
const FieldOptions::FeatureSupport& support, absl::string_view full_name);
// Calls ValidateFeatureSupport on fields during proto parsing.
// This will handle feature validation on fields of different types,
// especially of type ENUM.
//
// This will return error status and error message of ValidateFeatureSupport.
static absl::Status ValidateFieldFeatureSupport(const FieldDescriptor& field);
private:
explicit FeatureResolver(FeatureSet defaults)
: defaults_(std::move(defaults)) {}

View file

@ -18,6 +18,7 @@
#include "absl/log/die_if_null.h"
#include "absl/memory/memory.h"
#include "absl/status/status.h"
#include "absl/status/status_matchers.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "absl/strings/substitute.h"
@ -40,6 +41,7 @@ namespace google {
namespace protobuf {
namespace {
using ::absl_testing::StatusIs;
using ::testing::AllOf;
using ::testing::ElementsAre;
using ::testing::ExplainMatchResult;
@ -59,10 +61,6 @@ MATCHER_P(HasError, msg_matcher, "") {
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))
@ -1229,15 +1227,12 @@ TEST_F(FeatureResolverPoolTest,
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidWithMissingDeprecationWarning) {
ValidateFieldFeatureSupportInvalidWithMissingDeprecationWarning) {
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,
@ -1251,22 +1246,20 @@ TEST_F(FeatureResolverPoolTest,
)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"),
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
AllOf(HasSubstr("test.Foo.bool_field"),
HasSubstr("deprecation warning"))));
}
TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithMissingDeprecation) {
TEST_F(FeatureResolverPoolTest,
ValidateFieldFeatureSupportInvalidWithMissingDeprecation) {
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,
@ -1280,15 +1273,15 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidWithMissingDeprecation) {
)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"))));
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.Foo.bool_field"),
HasSubstr("is not marked deprecated")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidDeprecatedBeforeIntroduced) {
ValidateFieldFeatureSupportInvalidDeprecatedBeforeIntroduced) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
@ -1311,23 +1304,21 @@ TEST_F(FeatureResolverPoolTest,
)schema");
ASSERT_NE(file, nullptr);
const FieldDescriptor* ext = file->extension(0);
const FieldDescriptor* field = file->message_type(0)->field(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"))));
FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.Foo.bool_field"),
HasSubstr("deprecated before it was introduced")))));
}
TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDeprecatedAfterRemoved) {
TEST_F(FeatureResolverPoolTest,
ValidateFieldFeatureSupportInvalidDeprecatedAfterRemoved) {
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,
@ -1343,22 +1334,20 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDeprecatedAfterRemoved) {
)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"))));
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.Foo.bool_field"),
HasSubstr("deprecated after it was removed")))));
}
TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidRemovedBeforeIntroduced) {
TEST_F(FeatureResolverPoolTest,
ValidateFieldFeatureSupportInvalidRemovedBeforeIntroduced) {
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,
@ -1372,11 +1361,11 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidRemovedBeforeIntroduced) {
)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"))));
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.Foo.bool_field"),
HasSubstr("removed before it was introduced")))));
}
TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidMissingLegacyDefaults) {
@ -1454,6 +1443,7 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDefaultsAfterRemoved) {
feature_support = {
edition_introduced: EDITION_PROTO2
edition_removed: EDITION_2023
removal_error: "Custom feature removal error"
},
edition_defaults = { edition: EDITION_LEGACY, value: "true" },
edition_defaults = { edition: EDITION_2024, value: "true" }
@ -1557,15 +1547,37 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsInvalidDefaultsTooEarly) {
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueWithMissingDeprecationWarning) {
ValidateFieldFeatureSupportIgnoreValueWithMissingParentFeatureSupport) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support.edition_introduced = EDITION_2023];
}
message Foo {
optional FooValues bool_field = 1 [
targets = TARGET_TYPE_FIELD,
edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
];
}
)schema");
ASSERT_NE(file, nullptr);
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kOk));
}
TEST_F(FeatureResolverPoolTest,
ValidateFieldFeatureSupportInvalidValueWithMissingDeprecationWarning) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support.edition_deprecated = EDITION_2023];
@ -1580,23 +1592,20 @@ TEST_F(FeatureResolverPoolTest,
)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.VALUE"),
HasSubstr("deprecation warning"))));
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"),
HasSubstr("deprecation warning")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueWithMissingDeprecation) {
ValidateFieldFeatureSupportInvalidValueWithMissingDeprecation) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support.deprecation_warning = "some message"];
@ -1611,23 +1620,20 @@ TEST_F(FeatureResolverPoolTest,
)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.VALUE"),
HasSubstr("is not marked deprecated"))));
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"),
HasSubstr("is not marked deprecated")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueDeprecatedBeforeIntroduced) {
ValidateFieldFeatureSupportInvalidValueDeprecatedBeforeIntroduced) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support = {
@ -1646,24 +1652,21 @@ TEST_F(FeatureResolverPoolTest,
)schema");
ASSERT_NE(file, nullptr);
const FieldDescriptor* ext = file->extension(0);
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(
FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
EDITION_2023),
HasError(AllOf(HasSubstr("test.VALUE"),
HasSubstr("deprecated before it was introduced"))));
FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"),
HasSubstr("deprecated before it was introduced")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueDeprecatedBeforeIntroducedInherited) {
ValidateFieldFeatureSupportInvalidValueIntroducedInherited) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support = {
@ -1681,24 +1684,21 @@ TEST_F(FeatureResolverPoolTest,
)schema");
ASSERT_NE(file, nullptr);
const FieldDescriptor* ext = file->extension(0);
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(
FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
EDITION_2023),
HasError(AllOf(HasSubstr("test.VALUE"),
HasSubstr("deprecated before it was introduced"))));
FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"),
HasSubstr("deprecated before it was introduced")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueDeprecatedAfterRemoved) {
ValidateFieldFeatureSupportInvalidValueDeprecatedAfterRemoved) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support = {
@ -1706,6 +1706,7 @@ TEST_F(FeatureResolverPoolTest,
edition_deprecated: EDITION_2024
deprecation_warning: "warning"
edition_removed: EDITION_2024
removal_error: "Custom removal error"
}];
}
message Foo {
@ -1718,28 +1719,26 @@ TEST_F(FeatureResolverPoolTest,
)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.VALUE"),
HasSubstr("deprecated after it was removed"))));
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"),
HasSubstr("deprecated after it was removed")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueRemovedBeforeIntroduced) {
ValidateFieldFeatureSupportInvalidValueRemovedBeforeIntroduced) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support = {
edition_introduced: EDITION_2024
edition_removed: EDITION_2023
removal_error: "Custom removal error"
}];
}
message Foo {
@ -1752,23 +1751,20 @@ TEST_F(FeatureResolverPoolTest,
)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.VALUE"),
HasSubstr("removed before it was introduced"))));
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"),
HasSubstr("removed before it was introduced")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueIntroducedBeforeFeature) {
ValidateFieldFeatureSupportInvalidValueIntroducedBeforeFeature) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support = {
@ -1785,24 +1781,21 @@ TEST_F(FeatureResolverPoolTest,
)schema");
ASSERT_NE(file, nullptr);
const FieldDescriptor* ext = file->extension(0);
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(
FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
EDITION_2023),
HasError(AllOf(HasSubstr("test.VALUE"), HasSubstr("introduced before"),
HasSubstr("test.Foo.bool_field"))));
FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"), HasSubstr("introduced before"),
HasSubstr("test.Foo.bool_field")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueIntroducedAfterFeatureRemoved) {
ValidateFieldFeatureSupportInvalidValueIntroducedAfterFeatureRemoved) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support = {
@ -1812,66 +1805,68 @@ TEST_F(FeatureResolverPoolTest,
message Foo {
optional FooValues bool_field = 1 [
targets = TARGET_TYPE_FIELD,
feature_support.edition_introduced = EDITION_2023,
feature_support.edition_removed = EDITION_2024,
feature_support = {
edition_introduced: EDITION_2023
edition_removed: EDITION_2024
removal_error: "Custom removal error"
},
edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
];
}
)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.VALUE"),
HasSubstr("removed before it was introduced"))));
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"),
HasSubstr("removed before it was introduced")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueRemovedAfterFeature) {
ValidateFieldFeatureSupportInvalidValueRemovedAfterFeature) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support = {
edition_removed: EDITION_99997_TEST_ONLY
removal_error: "Custom removal error"
}];
}
message Foo {
optional FooValues bool_field = 1 [
targets = TARGET_TYPE_FIELD,
feature_support.edition_introduced = EDITION_2023,
feature_support.edition_removed = EDITION_2024,
feature_support = {
edition_introduced: EDITION_2023
edition_removed: EDITION_2024
removal_error: "Custom removal error"
},
edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
];
}
)schema");
ASSERT_NE(file, nullptr);
const FieldDescriptor* ext = file->extension(0);
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(
FeatureResolver::CompileDefaults(feature_set_, {ext}, EDITION_2023,
EDITION_2023),
HasError(AllOf(HasSubstr("test.VALUE"), HasSubstr("removed after"),
HasSubstr("test.Foo.bool_field"))));
FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"), HasSubstr("removed after"),
HasSubstr("test.Foo.bool_field")))));
}
TEST_F(FeatureResolverPoolTest,
CompileDefaultsInvalidValueDeprecatedAfterFeature) {
ValidateFieldFeatureSupportInvalidValueDeprecatedAfterFeature) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
package test;
import "google/protobuf/descriptor.proto";
extend google.protobuf.FeatureSet {
optional Foo bar = 9999;
}
enum FooValues {
UNKNOWN = 0;
VALUE = 1 [feature_support = {
@ -1882,23 +1877,25 @@ TEST_F(FeatureResolverPoolTest,
message Foo {
optional FooValues bool_field = 1 [
targets = TARGET_TYPE_FIELD,
feature_support.edition_introduced = EDITION_2023,
feature_support.edition_deprecated = EDITION_2024,
feature_support.deprecation_warning = "warning",
feature_support = {
edition_introduced: EDITION_2023
edition_deprecated: EDITION_2024
deprecation_warning: "warning"
},
edition_defaults = { edition: EDITION_LEGACY, value: "UNKNOWN" }
];
}
)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.VALUE"), HasSubstr("deprecated after"),
HasSubstr("test.Foo.bool_field"))));
}
const FieldDescriptor* field = file->message_type(0)->field(0);
EXPECT_THAT(
FeatureResolver::ValidateFieldFeatureSupport(*field),
StatusIs(absl::StatusCode::kFailedPrecondition,
(AllOf(HasSubstr("test.VALUE"), HasSubstr("deprecated after"),
HasSubstr("test.Foo.bool_field")))));
}
TEST_F(FeatureResolverPoolTest, CompileDefaultsMinimumTooEarly) {
const FileDescriptor* file = ParseSchema(R"schema(
syntax = "proto2";
@ -1942,8 +1939,11 @@ TEST_F(FeatureResolverPoolTest, CompileDefaultsRemovedOnly) {
message Foo {
optional Bar file_feature = 1 [
targets = TARGET_TYPE_FIELD,
feature_support.edition_introduced = EDITION_2023,
feature_support.edition_removed = EDITION_99998_TEST_ONLY,
feature_support = {
edition_introduced: EDITION_2023
edition_removed: EDITION_99998_TEST_ONLY
removal_error: "Custom feature removal error"
},
edition_defaults = { edition: EDITION_LEGACY, value: "VALUE1" }
];
}