There are 4 different feature sets for every descriptor: * Runtime/resolved - Used to make runtime decisions * Source/resolved - Used to make codegen decisions * Runtime/raw - Used to reason about the original proto file during runtime * Source/raw - Used to validate features during codegen All of these will need to be shipped to code generators. While C++ generators can use the C++ runtime to reconstruct these, they won't end up in the FileDescriptorProtos we send to runtimes. Similarly, non-C++ generators wouldn't be able to get them without duplicating our feature resolution logic. This change adds a helper that allows us to bundle all of these into the protos we send to generators. PiperOrigin-RevId: 547849243pull/13316/head
parent
b81d2cc8c5
commit
c5a1dbeb1e
10 changed files with 512 additions and 16 deletions
@ -0,0 +1,299 @@ |
|||||||
|
// 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/compiler/code_generator.h" |
||||||
|
|
||||||
|
#include <string> |
||||||
|
|
||||||
|
#include "google/protobuf/descriptor.pb.h" |
||||||
|
#include <gmock/gmock.h> |
||||||
|
#include <gtest/gtest.h> |
||||||
|
#include "absl/log/absl_log.h" |
||||||
|
#include "absl/strings/str_format.h" |
||||||
|
#include "absl/strings/str_replace.h" |
||||||
|
#include "absl/strings/string_view.h" |
||||||
|
#include "google/protobuf/compiler/parser.h" |
||||||
|
#include "google/protobuf/io/tokenizer.h" |
||||||
|
#include "google/protobuf/io/zero_copy_stream_impl_lite.h" |
||||||
|
#include "google/protobuf/test_textproto.h" |
||||||
|
#include "google/protobuf/unittest_features.pb.h" |
||||||
|
|
||||||
|
namespace google { |
||||||
|
namespace protobuf { |
||||||
|
namespace compiler { |
||||||
|
namespace { |
||||||
|
|
||||||
|
using ::testing::NotNull; |
||||||
|
|
||||||
|
class TestGenerator : public CodeGenerator { |
||||||
|
public: |
||||||
|
bool Generate(const FileDescriptor* file, const std::string& parameter, |
||||||
|
GeneratorContext* generator_context, |
||||||
|
std::string* error) const override { |
||||||
|
return true; |
||||||
|
} |
||||||
|
|
||||||
|
// Expose the protected methods for testing.
|
||||||
|
using CodeGenerator::GetRuntimeProto; |
||||||
|
using CodeGenerator::GetSourceFeatures; |
||||||
|
using CodeGenerator::GetSourceRawFeatures; |
||||||
|
}; |
||||||
|
|
||||||
|
class SimpleErrorCollector : public io::ErrorCollector { |
||||||
|
public: |
||||||
|
void RecordError(int line, int column, absl::string_view message) override { |
||||||
|
ABSL_LOG(ERROR) << absl::StrFormat("%d:%d:%s", line, column, message); |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
class CodeGeneratorTest : public ::testing::Test { |
||||||
|
protected: |
||||||
|
void SetUp() override { |
||||||
|
ASSERT_THAT(BuildFile(DescriptorProto::descriptor()->file()), NotNull()); |
||||||
|
ASSERT_THAT(BuildFile(pb::TestMessage::descriptor()->file()), NotNull()); |
||||||
|
} |
||||||
|
|
||||||
|
const FileDescriptor* BuildFile(absl::string_view schema) { |
||||||
|
io::ArrayInputStream input_stream(schema.data(), |
||||||
|
static_cast<int>(schema.size())); |
||||||
|
SimpleErrorCollector error_collector; |
||||||
|
io::Tokenizer tokenizer(&input_stream, &error_collector); |
||||||
|
Parser parser; |
||||||
|
parser.RecordErrorsTo(&error_collector); |
||||||
|
FileDescriptorProto proto; |
||||||
|
ABSL_CHECK(parser.Parse(&tokenizer, &proto)) << schema; |
||||||
|
proto.set_name("test.proto"); |
||||||
|
return pool_.BuildFile(proto); |
||||||
|
} |
||||||
|
|
||||||
|
const FileDescriptor* BuildFile(const FileDescriptor* file) { |
||||||
|
FileDescriptorProto proto; |
||||||
|
file->CopyTo(&proto); |
||||||
|
return pool_.BuildFile(proto); |
||||||
|
} |
||||||
|
|
||||||
|
DescriptorPool pool_; |
||||||
|
}; |
||||||
|
|
||||||
|
TEST_F(CodeGeneratorTest, GetSourceRawFeaturesRoot) { |
||||||
|
auto file = BuildFile(R"schema( |
||||||
|
edition = "2023"; |
||||||
|
package protobuf_unittest; |
||||||
|
|
||||||
|
import "google/protobuf/unittest_features.proto"; |
||||||
|
|
||||||
|
option features.field_presence = EXPLICIT; // 2023 default
|
||||||
|
option features.enum_type = CLOSED; // override
|
||||||
|
option features.(pb.test).int_file_feature = 8; |
||||||
|
option features.(pb.test).string_source_feature = "file"; |
||||||
|
)schema"); |
||||||
|
ASSERT_THAT(file, NotNull()); |
||||||
|
|
||||||
|
EXPECT_THAT(TestGenerator::GetSourceRawFeatures(*file), |
||||||
|
google::protobuf::EqualsProto(R"pb( |
||||||
|
field_presence: EXPLICIT |
||||||
|
enum_type: CLOSED |
||||||
|
[pb.test] { int_file_feature: 8 string_source_feature: "file" } |
||||||
|
)pb")); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CodeGeneratorTest, GetSourceRawFeaturesInherited) { |
||||||
|
auto file = BuildFile(R"schema( |
||||||
|
edition = "2023"; |
||||||
|
package protobuf_unittest; |
||||||
|
|
||||||
|
import "google/protobuf/unittest_features.proto"; |
||||||
|
|
||||||
|
option features.enum_type = OPEN; |
||||||
|
option features.(pb.test).int_file_feature = 6; |
||||||
|
message EditionsMessage { |
||||||
|
option features.(pb.test).int_message_feature = 7; |
||||||
|
option features.(pb.test).int_multiple_feature = 8; |
||||||
|
|
||||||
|
string field = 1 [ |
||||||
|
features.field_presence = EXPLICIT, |
||||||
|
features.(pb.test).int_multiple_feature = 9, |
||||||
|
features.(pb.test).string_source_feature = "field" |
||||||
|
]; |
||||||
|
} |
||||||
|
)schema"); |
||||||
|
ASSERT_THAT(file, NotNull()); |
||||||
|
|
||||||
|
const FieldDescriptor* field = |
||||||
|
file->FindMessageTypeByName("EditionsMessage")->FindFieldByName("field"); |
||||||
|
ASSERT_THAT(field, NotNull()); |
||||||
|
|
||||||
|
EXPECT_THAT( |
||||||
|
TestGenerator::GetSourceRawFeatures(*field), google::protobuf::EqualsProto(R"pb( |
||||||
|
field_presence: EXPLICIT |
||||||
|
[pb.test] { int_multiple_feature: 9 string_source_feature: "field" } |
||||||
|
)pb")); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CodeGeneratorTest, GetSourceFeaturesRoot) { |
||||||
|
auto file = BuildFile(R"schema( |
||||||
|
edition = "2023"; |
||||||
|
package protobuf_unittest; |
||||||
|
|
||||||
|
import "google/protobuf/unittest_features.proto"; |
||||||
|
|
||||||
|
option features.field_presence = EXPLICIT; // 2023 default
|
||||||
|
option features.enum_type = CLOSED; // override
|
||||||
|
option features.(pb.test).int_file_feature = 8; |
||||||
|
option features.(pb.test).string_source_feature = "file"; |
||||||
|
)schema"); |
||||||
|
ASSERT_THAT(file, NotNull()); |
||||||
|
|
||||||
|
const FeatureSet& features = TestGenerator::GetSourceFeatures(*file); |
||||||
|
const pb::TestFeatures& ext = features.GetExtension(pb::test); |
||||||
|
|
||||||
|
EXPECT_TRUE(features.has_repeated_field_encoding()); |
||||||
|
EXPECT_TRUE(features.field_presence()); |
||||||
|
EXPECT_EQ(features.field_presence(), FeatureSet::EXPLICIT); |
||||||
|
EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED); |
||||||
|
|
||||||
|
EXPECT_TRUE(ext.has_int_message_feature()); |
||||||
|
EXPECT_EQ(ext.int_file_feature(), 8); |
||||||
|
EXPECT_EQ(ext.string_source_feature(), "file"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CodeGeneratorTest, GetSourceFeaturesInherited) { |
||||||
|
auto file = BuildFile(R"schema( |
||||||
|
edition = "2023"; |
||||||
|
package protobuf_unittest; |
||||||
|
|
||||||
|
import "google/protobuf/unittest_features.proto"; |
||||||
|
|
||||||
|
option features.enum_type = CLOSED; |
||||||
|
option features.(pb.test).int_source_feature = 5; |
||||||
|
option features.(pb.test).int_file_feature = 6; |
||||||
|
message EditionsMessage { |
||||||
|
option features.(pb.test).int_message_feature = 7; |
||||||
|
option features.(pb.test).int_multiple_feature = 8; |
||||||
|
option features.(pb.test).string_source_feature = "message"; |
||||||
|
|
||||||
|
string field = 1 [ |
||||||
|
features.field_presence = IMPLICIT, |
||||||
|
features.(pb.test).int_multiple_feature = 9, |
||||||
|
features.(pb.test).string_source_feature = "field" |
||||||
|
]; |
||||||
|
} |
||||||
|
)schema"); |
||||||
|
ASSERT_THAT(file, NotNull()); |
||||||
|
|
||||||
|
const FieldDescriptor* field = |
||||||
|
file->FindMessageTypeByName("EditionsMessage")->FindFieldByName("field"); |
||||||
|
ASSERT_THAT(field, NotNull()); |
||||||
|
const FeatureSet& features = TestGenerator::GetSourceFeatures(*field); |
||||||
|
const pb::TestFeatures& ext = features.GetExtension(pb::test); |
||||||
|
|
||||||
|
EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED); |
||||||
|
EXPECT_EQ(features.field_presence(), FeatureSet::IMPLICIT); |
||||||
|
|
||||||
|
EXPECT_EQ(ext.int_message_feature(), 7); |
||||||
|
EXPECT_EQ(ext.int_file_feature(), 6); |
||||||
|
EXPECT_EQ(ext.int_multiple_feature(), 9); |
||||||
|
EXPECT_EQ(ext.int_source_feature(), 5); |
||||||
|
EXPECT_EQ(ext.string_source_feature(), "field"); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CodeGeneratorTest, GetRuntimeProtoRoot) { |
||||||
|
auto file = BuildFile(R"schema( |
||||||
|
edition = "2023"; |
||||||
|
package protobuf_unittest; |
||||||
|
|
||||||
|
import "google/protobuf/unittest_features.proto"; |
||||||
|
|
||||||
|
option features.enum_type = CLOSED; |
||||||
|
option features.(pb.test).int_source_feature = 5; |
||||||
|
option features.(pb.test).int_file_feature = 6; |
||||||
|
)schema"); |
||||||
|
ASSERT_THAT(file, NotNull()); |
||||||
|
|
||||||
|
FileDescriptorProto proto = TestGenerator::GetRuntimeProto(*file); |
||||||
|
const FeatureSet& features = proto.options().features(); |
||||||
|
const pb::TestFeatures& ext = features.GetExtension(pb::test); |
||||||
|
|
||||||
|
EXPECT_THAT(features.raw_features(), |
||||||
|
EqualsProto(R"pb(enum_type: CLOSED |
||||||
|
[pb.test] { int_file_feature: 6 })pb")); |
||||||
|
EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED); |
||||||
|
EXPECT_TRUE(features.has_field_presence()); |
||||||
|
EXPECT_EQ(features.field_presence(), FeatureSet::EXPLICIT); |
||||||
|
|
||||||
|
EXPECT_FALSE(ext.has_int_source_feature()); |
||||||
|
EXPECT_EQ(ext.int_file_feature(), 6); |
||||||
|
} |
||||||
|
|
||||||
|
TEST_F(CodeGeneratorTest, GetRuntimeProtoInherited) { |
||||||
|
auto file = BuildFile(R"schema( |
||||||
|
edition = "2023"; |
||||||
|
package protobuf_unittest; |
||||||
|
|
||||||
|
import "google/protobuf/unittest_features.proto"; |
||||||
|
|
||||||
|
option features.enum_type = CLOSED; |
||||||
|
option features.(pb.test).int_source_feature = 5; |
||||||
|
option features.(pb.test).int_file_feature = 6; |
||||||
|
message EditionsMessage { |
||||||
|
option features.(pb.test).int_message_feature = 7; |
||||||
|
option features.(pb.test).int_multiple_feature = 8; |
||||||
|
|
||||||
|
string field = 1 [ |
||||||
|
features.field_presence = IMPLICIT, |
||||||
|
features.(pb.test).int_multiple_feature = 9, |
||||||
|
features.(pb.test).string_source_feature = "field" |
||||||
|
]; |
||||||
|
} |
||||||
|
)schema"); |
||||||
|
ASSERT_THAT(file, NotNull()); |
||||||
|
|
||||||
|
FileDescriptorProto proto = TestGenerator::GetRuntimeProto(*file); |
||||||
|
const FieldDescriptorProto& field = proto.message_type(0).field(0); |
||||||
|
const FeatureSet& features = field.options().features(); |
||||||
|
const pb::TestFeatures& ext = features.GetExtension(pb::test); |
||||||
|
|
||||||
|
EXPECT_THAT(features.raw_features(), google::protobuf::EqualsProto(R"pb( |
||||||
|
field_presence: IMPLICIT |
||||||
|
[pb.test] { int_multiple_feature: 9 } |
||||||
|
)pb")); |
||||||
|
EXPECT_EQ(features.enum_type(), FeatureSet::CLOSED); |
||||||
|
EXPECT_EQ(features.field_presence(), FeatureSet::IMPLICIT); |
||||||
|
EXPECT_EQ(ext.int_multiple_feature(), 9); |
||||||
|
EXPECT_EQ(ext.int_message_feature(), 7); |
||||||
|
EXPECT_EQ(ext.int_file_feature(), 6); |
||||||
|
EXPECT_FALSE(ext.has_int_source_feature()); |
||||||
|
EXPECT_FALSE(ext.has_string_source_feature()); |
||||||
|
} |
||||||
|
|
||||||
|
} // namespace
|
||||||
|
} // namespace compiler
|
||||||
|
} // namespace protobuf
|
||||||
|
} // namespace google
|
Loading…
Reference in new issue