| |
| #include <string> |
| #include <string_view> |
| |
| #include "google/protobuf/descriptor.pb.h" |
| #include "google/protobuf/descriptor.upb.h" |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| #include "absl/log/absl_check.h" |
| #include "absl/status/status.h" |
| #include "absl/status/statusor.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_join.h" |
| #include "upb/base/status.hpp" |
| #include "upb/mem/arena.hpp" |
| #include "upb/reflection/def.hpp" |
| #include "upb/test/parse_text_proto.h" |
| |
| namespace upb_test { |
| namespace { |
| |
| using testing::HasSubstr; |
| |
| google_protobuf_FileDescriptorProto* ToUpbDescriptorSet( |
| const google::protobuf::FileDescriptorProto& proto, upb::Arena& arena) { |
| std::string serialized; |
| (void)proto.SerializeToString(&serialized); |
| return google_protobuf_FileDescriptorProto_parse(serialized.data(), serialized.size(), |
| arena.ptr()); |
| } |
| |
| absl::StatusOr<upb::DefPool> LoadDescriptorSetFromProto( |
| const google::protobuf::FileDescriptorSet& set) { |
| upb::Arena arena; |
| upb::DefPool defpool; |
| upb::Status status; |
| for (const auto& file : set.file()) { |
| google_protobuf_FileDescriptorProto* upb_proto = ToUpbDescriptorSet(file, arena); |
| ABSL_CHECK(upb_proto); |
| upb::FileDefPtr file_def = defpool.AddFile(upb_proto, &status); |
| if (!file_def) return absl::InternalError(status.error_message()); |
| } |
| return defpool; |
| } |
| |
| absl::StatusOr<upb::DefPool> LoadDescriptorProto(absl::string_view proto_text) { |
| google::protobuf::FileDescriptorProto proto = ParseTextProtoOrDie(proto_text); |
| google::protobuf::FileDescriptorSet set; |
| *set.add_file() = proto; |
| return LoadDescriptorSetFromProto(set); |
| } |
| |
| TEST(ReflectionTest, OpenEnumWithNonZeroDefault) { |
| absl::Status status = LoadDescriptorProto( |
| R"pb( |
| syntax: "proto3" |
| name: "F" |
| enum_type { |
| name: "BadEnum" |
| value { name: "v1" number: 1 } |
| } |
| )pb") |
| .status(); |
| EXPECT_EQ(std::string_view(status.message()), |
| "for open enums, the first value must be zero (BadEnum)"); |
| } |
| |
| TEST(ReflectionTest, EnumDefault) { |
| upb::DefPool pool = LoadDescriptorProto( |
| R"pb( |
| syntax: "proto2" |
| name: "F" |
| enum_type { |
| name: "FooEnum" |
| value { name: "v1" number: 1 } |
| } |
| )pb") |
| .value(); |
| upb::EnumDefPtr e = pool.FindEnumByName("FooEnum"); |
| EXPECT_EQ(e.default_value(), 1); |
| } |
| |
| TEST(ReflectionTest, ImplicitPresenceWithDefault) { |
| absl::Status status = |
| LoadDescriptorProto( |
| R"pb( |
| syntax: "editions" |
| edition: EDITION_2023 |
| name: "F" |
| message_type { |
| name: "FooMessage" |
| field { |
| name: "f1" |
| number: 1 |
| type: TYPE_INT32 |
| default_value: "1" |
| options { features { field_presence: IMPLICIT } } |
| } |
| } |
| )pb") |
| .status(); |
| EXPECT_EQ(std::string_view(status.message()), |
| "fields with implicit presence cannot have explicit defaults " |
| "(FooMessage.f1)"); |
| } |
| |
| TEST(ReflectionTest, ImplicitPresenceWithNonZeroDefaultEnum) { |
| absl::Status status = |
| LoadDescriptorProto( |
| R"pb( |
| syntax: "editions" |
| edition: EDITION_2023 |
| name: "F" |
| enum_type { |
| name: "FooEnum" |
| value { name: "v1" number: 1 } |
| options { features { enum_type: CLOSED } } |
| } |
| message_type { |
| name: "FooMessage" |
| field { |
| name: "f1" |
| number: 1 |
| type: TYPE_ENUM |
| type_name: "FooEnum" |
| options { features { field_presence: IMPLICIT } } |
| } |
| } |
| )pb") |
| .status(); |
| EXPECT_EQ(std::string_view(status.message()), |
| "Implicit presence field (FooMessage.f1) cannot use an enum type " |
| "with a non-zero default (FooEnum)"); |
| } |
| |
| TEST(ReflectionTest, EditionWithoutSyntax) { |
| absl::Status status = LoadDescriptorProto( |
| R"pb( |
| edition: EDITION_2023 |
| )pb") |
| .status(); |
| EXPECT_EQ( |
| status.message(), |
| R"(Setting edition requires that syntax="editions", but syntax is "")"); |
| } |
| |
| TEST(ReflectionTest, EditionWithWrongSyntax) { |
| absl::Status status = LoadDescriptorProto( |
| R"pb( |
| edition: EDITION_2023 syntax: "proto2" |
| )pb") |
| .status(); |
| EXPECT_EQ( |
| status.message(), |
| R"(Setting edition requires that syntax="editions", but syntax is "proto2")"); |
| } |
| |
| TEST(ReflectionTest, SyntaxEditionsWithNoEdition) { |
| absl::Status status = LoadDescriptorProto( |
| R"pb( |
| syntax: "editions" |
| )pb") |
| .status(); |
| EXPECT_EQ(status.message(), |
| R"(File has syntax="editions", but no edition is specified)"); |
| } |
| |
| TEST(ReflectionTest, InvalidSyntax) { |
| absl::Status status = LoadDescriptorProto( |
| R"pb( |
| syntax: "abc123" |
| )pb") |
| .status(); |
| EXPECT_EQ(status.message(), R"(Invalid syntax 'abc123')"); |
| } |
| |
| TEST(ReflectionTest, ExplicitFeatureOnProto2File) { |
| absl::Status status = LoadDescriptorProto( |
| R"pb( |
| syntax: "proto2" |
| options { features { field_presence: EXPLICIT } } |
| )pb") |
| .status(); |
| EXPECT_EQ(status.message(), R"(Features can only be specified for editions)"); |
| } |
| |
| TEST(ReflectionTest, ExplicitFeatureOnProto2Message) { |
| absl::Status status = |
| LoadDescriptorProto( |
| R"pb( |
| syntax: "proto2" |
| message_type { |
| name: "M" |
| options { features { field_presence: EXPLICIT } } |
| } |
| )pb") |
| .status(); |
| EXPECT_EQ(status.message(), R"(Features can only be specified for editions)"); |
| } |
| |
| TEST(ReflectionTest, ExplicitFeatureOnProto2Enum) { |
| absl::Status status = |
| LoadDescriptorProto( |
| R"pb( |
| syntax: "proto2" |
| enum_type { |
| name: "E" |
| options { features { field_presence: EXPLICIT } } |
| } |
| )pb") |
| .status(); |
| EXPECT_EQ(status.message(), R"(Features can only be specified for editions)"); |
| } |
| |
| TEST(ReflectionTest, ExplicitFeatureOnProto2EnumValue) { |
| absl::Status status = |
| LoadDescriptorProto( |
| R"pb( |
| syntax: "proto2" |
| enum_type { |
| name: "E" |
| value { |
| name: "V" |
| options { features { field_presence: EXPLICIT } } |
| } |
| } |
| )pb") |
| .status(); |
| EXPECT_EQ(status.message(), R"(Features can only be specified for editions)"); |
| } |
| |
| TEST(ReflectionTest, TooManyRequiredFieldsFailGracefully) { |
| const auto make_desc = [](int n) { |
| std::vector<std::string> fields; |
| for (int i = 1; i <= n; ++i) { |
| fields.push_back(absl::StrFormat(R"pb( |
| field { |
| name: "f%d" |
| number: %d |
| type: TYPE_INT32 |
| label: LABEL_REQUIRED |
| })pb", |
| i, i)); |
| } |
| return absl::StrFormat( |
| R"pb( |
| syntax: "proto2" |
| name: "F" |
| message_type { name: "FooMessage" %s } |
| )pb", |
| absl::StrJoin(fields, "\n")); |
| }; |
| // 63 required fields is ok. |
| upb::DefPool good = LoadDescriptorProto(make_desc(63)).value(); |
| auto m = good.FindMessageByName("FooMessage"); |
| auto f = m.FindFieldByNumber(63); |
| EXPECT_STREQ(f.full_name(), "FooMessage.f63"); |
| |
| // 64 is too much. |
| EXPECT_THAT(LoadDescriptorProto(make_desc(64)).status().message(), |
| HasSubstr("Too many required fields")); |
| } |
| |
| } // namespace |
| } // namespace upb_test |