blob: d993839643f1364c54f31369f709d27e674d89f6 [file] [log] [blame]
// Protocol Buffers - Google's data interchange format
// Copyright 2023 Google Inc. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file or at
// https://developers.google.com/open-source/licenses/bsd
#include "google/protobuf/compiler/retention.h"
#include <memory>
#include <string>
#include "google/protobuf/descriptor.pb.h"
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include "absl/log/absl_check.h"
#include "absl/log/die_if_null.h"
#include "absl/strings/substitute.h"
#include "google/protobuf/compiler/parser.h"
#include "google/protobuf/descriptor.h"
#include "google/protobuf/dynamic_message.h"
#include "google/protobuf/io/tokenizer.h"
#include "google/protobuf/io/zero_copy_stream_impl_lite.h"
#include "google/protobuf/text_format.h"
namespace google {
namespace protobuf {
namespace compiler {
namespace {
MATCHER_P(EqualsProto, msg, "") {
return msg.DebugString() == arg.DebugString();
}
class FakeErrorCollector : public io::ErrorCollector {
public:
FakeErrorCollector() = default;
~FakeErrorCollector() override = default;
void RecordError(int line, io::ColumnNumber column,
absl::string_view message) override {
ABSL_CHECK(false) << line << ":" << column << ": " << message;
}
void RecordWarning(int line, io::ColumnNumber column,
absl::string_view message) override {
ABSL_CHECK(false) << line << ":" << column << ": " << message;
}
};
class RetentionStripTest : public testing::Test {
protected:
void SetUp() override {
FileDescriptorProto descriptor_proto_descriptor;
FileDescriptorSet::descriptor()->file()->CopyTo(
&descriptor_proto_descriptor);
pool_.BuildFile(descriptor_proto_descriptor);
}
const FileDescriptor* ParseSchema(absl::string_view contents,
absl::string_view file_name = "foo.proto") {
std::string proto_file = absl::Substitute(
R"schema(
syntax = "proto2";
package google.protobuf.internal;
import "$0";
$1
)schema",
FileDescriptorSet::descriptor()->file()->name(), contents);
io::ArrayInputStream input_stream(proto_file.data(),
static_cast<int>(proto_file.size()));
FakeErrorCollector error_collector;
io::Tokenizer tokenizer(&input_stream, &error_collector);
Parser parser;
parser.RecordErrorsTo(&error_collector);
FileDescriptorProto file_descriptor;
ABSL_CHECK(parser.Parse(&tokenizer, &file_descriptor));
file_descriptor.set_name(file_name);
return pool_.BuildFile(file_descriptor);
}
template <typename ProtoType>
ProtoType BuildDynamicProto(absl::string_view data) {
// We use a dynamic message to generate the expected options proto. This
// lets us parse the custom options in text format.
const Descriptor* file_options_descriptor =
pool_.FindMessageTypeByName(ProtoType().GetTypeName());
DynamicMessageFactory factory;
std::unique_ptr<Message> dynamic_message(
factory.GetPrototype(file_options_descriptor)->New());
ABSL_CHECK(TextFormat::ParseFromString(data, dynamic_message.get()));
ProtoType ret;
ABSL_CHECK(ret.ParseFromString(dynamic_message->SerializeAsString()));
return ret;
}
DescriptorPool pool_;
};
TEST_F(RetentionStripTest, StripSourceRetentionFileOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
option (source_retention_option) = 123;
option (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
};
option (repeated_options) = {
i1: 111 i2: 222
};
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.FileOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})schema");
FileOptions expected_options = BuildDynamicProto<FileOptions>(
R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb");
FileDescriptorProto stripped_file = StripSourceRetentionOptions(*file);
EXPECT_THAT(StripSourceRetentionOptions(*file).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripLocalSourceRetentionOptions(*file),
EqualsProto(expected_options));
}
TEST_F(RetentionStripTest, StripSourceRetentionProtoFileOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
option (source_retention_option) = 123;
option (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
};
option (repeated_options) = {
i1: 111 i2: 222
};
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.FileOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
}
)schema");
FileDescriptorProto proto;
file->CopyTo(&proto);
ASSERT_THAT(proto.options(), EqualsProto(BuildDynamicProto<FileOptions>(R"pb(
[google.protobuf.internal.source_retention_option]: 123
[google.protobuf.internal.options] {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
}
[google.protobuf.internal.repeated_options] { i1: 111 i2: 222 })pb")));
StripSourceRetentionOptions(*file->pool(), proto);
EXPECT_THAT(proto.options(), EqualsProto(BuildDynamicProto<FileOptions>(R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb")));
}
TEST_F(RetentionStripTest, StripSourceRetentionMessageOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
message TestMessage {
option (source_retention_option) = 123;
option (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
};
option (repeated_options) = {
i1: 111 i2: 222
};
}
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.MessageOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})schema");
MessageOptions expected_options = BuildDynamicProto<MessageOptions>(
R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb");
const Descriptor* message =
ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
EXPECT_THAT(StripSourceRetentionOptions(*file).message_type(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*message).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripLocalSourceRetentionOptions(*message),
EqualsProto(expected_options));
}
TEST_F(RetentionStripTest, StripSourceRetentionEnumOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
enum TestEnum {
option (source_retention_option) = 123;
option (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
};
option (repeated_options) = {
i1: 111 i2: 222
};
VALUE1 = 0;
}
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.EnumOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})schema");
EnumOptions expected_options = BuildDynamicProto<EnumOptions>(
R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb");
const EnumDescriptor* enm =
ABSL_DIE_IF_NULL(file->FindEnumTypeByName("TestEnum"));
EXPECT_THAT(StripSourceRetentionOptions(*file).enum_type(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*enm).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripLocalSourceRetentionOptions(*enm),
EqualsProto(expected_options));
}
TEST_F(RetentionStripTest, StripSourceRetentionEnumValueOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
enum TestEnum {
VALUE1 = 0 [(source_retention_option) = 123, (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
}, (repeated_options) = {
i1: 111 i2: 222
}];
}
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.EnumValueOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})schema");
EnumValueOptions expected_options = BuildDynamicProto<EnumValueOptions>(
R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb");
const EnumDescriptor* enm =
ABSL_DIE_IF_NULL(file->FindEnumTypeByName("TestEnum"));
const EnumValueDescriptor* value = ABSL_DIE_IF_NULL(enm->value(0));
EXPECT_THAT(
StripSourceRetentionOptions(*file).enum_type(0).value(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*enm).value(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripLocalSourceRetentionOptions(*value),
EqualsProto(expected_options));
}
TEST_F(RetentionStripTest, StripSourceRetentionFieldOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
message TestMessage {
optional string test_field = 1 [(source_retention_option) = 123, (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
}, (repeated_options) = {
i1: 111 i2: 222
}];
}
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.FieldOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})schema");
FieldOptions expected_options = BuildDynamicProto<FieldOptions>(
R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb");
const Descriptor* message =
ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
const FieldDescriptor* field =
ABSL_DIE_IF_NULL(message->FindFieldByName("test_field"));
EXPECT_THAT(
StripSourceRetentionOptions(*file).message_type(0).field(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*message).field(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*field).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripLocalSourceRetentionOptions(*field),
EqualsProto(expected_options));
}
TEST_F(RetentionStripTest, StripSourceRetentionExtensionOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
message TestMessage {
extensions 1;
}
extend TestMessage {
optional string test_field = 1 [(source_retention_option) = 123, (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
}, (repeated_options) = {
i1: 111 i2: 222
}];
}
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.FieldOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})schema");
FieldOptions expected_options = BuildDynamicProto<FieldOptions>(
R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb");
const FieldDescriptor* field =
ABSL_DIE_IF_NULL(file->FindExtensionByName("test_field"));
EXPECT_THAT(StripSourceRetentionOptions(*file).extension(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*field).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripLocalSourceRetentionOptions(*field),
EqualsProto(expected_options));
}
TEST_F(RetentionStripTest, StripSourceRetentionOneofOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
message TestMessage {
oneof test_oneof {
option (source_retention_option) = 123;
option (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
};
option (repeated_options) = {
i1: 111 i2: 222
};
string field1 = 1;
string field2 = 2;
};
}
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.OneofOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})schema");
OneofOptions expected_options = BuildDynamicProto<OneofOptions>(
R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb");
const Descriptor* message =
ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
const OneofDescriptor* oneof =
ABSL_DIE_IF_NULL(message->FindOneofByName("test_oneof"));
EXPECT_THAT(StripSourceRetentionOptions(*file)
.message_type(0)
.oneof_decl(0)
.options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*message).oneof_decl(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*oneof).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripLocalSourceRetentionOptions(*oneof),
EqualsProto(expected_options));
}
TEST_F(RetentionStripTest, StripSourceRetentionExtensionRangeOptions) {
const FileDescriptor* file = ParseSchema(R"schema(
message TestMessage {
extensions 1 to max [(source_retention_option) = 123, (options) = {
i1: 123
i2: 456
c { s: "abc" }
rc { s: "abc" }
}, (repeated_options) = {
i1: 111 i2: 222
}];
}
message Options {
optional int32 i1 = 1 [retention = RETENTION_SOURCE];
optional int32 i2 = 2;
message ChildMessage {
optional string s = 1 [retention = RETENTION_SOURCE];
}
optional ChildMessage c = 3;
repeated ChildMessage rc = 4;
}
extend google.protobuf.ExtensionRangeOptions {
optional int32 source_retention_option = 50000 [retention = RETENTION_SOURCE];
optional Options options = 50001;
repeated Options repeated_options = 50002;
})schema");
ExtensionRangeOptions expected_options =
BuildDynamicProto<ExtensionRangeOptions>(
R"pb(
[google.protobuf.internal.options] {
i2: 456
c {}
rc {}
}
[google.protobuf.internal.repeated_options] { i2: 222 })pb");
const Descriptor* message =
ABSL_DIE_IF_NULL(file->FindMessageTypeByName("TestMessage"));
const Descriptor::ExtensionRange* range =
ABSL_DIE_IF_NULL(message->FindExtensionRangeContainingNumber(2));
EXPECT_THAT(StripSourceRetentionOptions(*file)
.message_type(0)
.extension_range(0)
.options(),
EqualsProto(expected_options));
EXPECT_THAT(
StripSourceRetentionOptions(*message).extension_range(0).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripSourceRetentionOptions(*message, *range).options(),
EqualsProto(expected_options));
EXPECT_THAT(StripLocalSourceRetentionOptions(*message, *range),
EqualsProto(expected_options));
}
} // namespace
} // namespace compiler
} // namespace protobuf
} // namespace google