| Index: third_party/protobuf/src/google/protobuf/descriptor_unittest.cc
|
| ===================================================================
|
| --- third_party/protobuf/src/google/protobuf/descriptor_unittest.cc (revision 216642)
|
| +++ third_party/protobuf/src/google/protobuf/descriptor_unittest.cc (working copy)
|
| @@ -36,13 +36,15 @@
|
|
|
| #include <vector>
|
|
|
| +#include <google/protobuf/compiler/importer.h>
|
| +#include <google/protobuf/unittest.pb.h>
|
| +#include <google/protobuf/unittest_custom_options.pb.h>
|
| +#include <google/protobuf/io/zero_copy_stream_impl.h>
|
| +#include <google/protobuf/descriptor.pb.h>
|
| #include <google/protobuf/descriptor.h>
|
| #include <google/protobuf/descriptor_database.h>
|
| #include <google/protobuf/dynamic_message.h>
|
| -#include <google/protobuf/descriptor.pb.h>
|
| #include <google/protobuf/text_format.h>
|
| -#include <google/protobuf/unittest.pb.h>
|
| -#include <google/protobuf/unittest_custom_options.pb.h>
|
| #include <google/protobuf/stubs/strutil.h>
|
| #include <google/protobuf/stubs/substitute.h>
|
|
|
| @@ -1518,9 +1520,8 @@
|
|
|
| class MiscTest : public testing::Test {
|
| protected:
|
| - // Function which makes a field of the given type just to find out what its
|
| - // cpp_type is.
|
| - FieldDescriptor::CppType GetCppTypeForFieldType(FieldDescriptor::Type type) {
|
| + // Function which makes a field descriptor of the given type.
|
| + const FieldDescriptor* GetFieldDescriptorOfType(FieldDescriptor::Type type) {
|
| FileDescriptorProto file_proto;
|
| file_proto.set_name("foo.proto");
|
| AddEmptyEnum(&file_proto, "DummyEnum");
|
| @@ -1538,19 +1539,62 @@
|
| }
|
|
|
| // Build the descriptors and get the pointers.
|
| - DescriptorPool pool;
|
| - const FileDescriptor* file = pool.BuildFile(file_proto);
|
| + pool_.reset(new DescriptorPool());
|
| + const FileDescriptor* file = pool_->BuildFile(file_proto);
|
|
|
| if (file != NULL &&
|
| file->message_type_count() == 1 &&
|
| file->message_type(0)->field_count() == 1) {
|
| - return file->message_type(0)->field(0)->cpp_type();
|
| + return file->message_type(0)->field(0);
|
| } else {
|
| - return static_cast<FieldDescriptor::CppType>(0);
|
| + return NULL;
|
| }
|
| }
|
| +
|
| + const char* GetTypeNameForFieldType(FieldDescriptor::Type type) {
|
| + const FieldDescriptor* field = GetFieldDescriptorOfType(type);
|
| + return field != NULL ? field->type_name() : "";
|
| + }
|
| +
|
| + FieldDescriptor::CppType GetCppTypeForFieldType(FieldDescriptor::Type type) {
|
| + const FieldDescriptor* field = GetFieldDescriptorOfType(type);
|
| + return field != NULL ? field->cpp_type() :
|
| + static_cast<FieldDescriptor::CppType>(0);
|
| + }
|
| +
|
| + const char* GetCppTypeNameForFieldType(FieldDescriptor::Type type) {
|
| + const FieldDescriptor* field = GetFieldDescriptorOfType(type);
|
| + return field != NULL ? field->cpp_type_name() : "";
|
| + }
|
| +
|
| + scoped_ptr<DescriptorPool> pool_;
|
| };
|
|
|
| +TEST_F(MiscTest, TypeNames) {
|
| + // Test that correct type names are returned.
|
| +
|
| + typedef FieldDescriptor FD; // avoid ugly line wrapping
|
| +
|
| + EXPECT_STREQ("double" , GetTypeNameForFieldType(FD::TYPE_DOUBLE ));
|
| + EXPECT_STREQ("float" , GetTypeNameForFieldType(FD::TYPE_FLOAT ));
|
| + EXPECT_STREQ("int64" , GetTypeNameForFieldType(FD::TYPE_INT64 ));
|
| + EXPECT_STREQ("uint64" , GetTypeNameForFieldType(FD::TYPE_UINT64 ));
|
| + EXPECT_STREQ("int32" , GetTypeNameForFieldType(FD::TYPE_INT32 ));
|
| + EXPECT_STREQ("fixed64" , GetTypeNameForFieldType(FD::TYPE_FIXED64 ));
|
| + EXPECT_STREQ("fixed32" , GetTypeNameForFieldType(FD::TYPE_FIXED32 ));
|
| + EXPECT_STREQ("bool" , GetTypeNameForFieldType(FD::TYPE_BOOL ));
|
| + EXPECT_STREQ("string" , GetTypeNameForFieldType(FD::TYPE_STRING ));
|
| + EXPECT_STREQ("group" , GetTypeNameForFieldType(FD::TYPE_GROUP ));
|
| + EXPECT_STREQ("message" , GetTypeNameForFieldType(FD::TYPE_MESSAGE ));
|
| + EXPECT_STREQ("bytes" , GetTypeNameForFieldType(FD::TYPE_BYTES ));
|
| + EXPECT_STREQ("uint32" , GetTypeNameForFieldType(FD::TYPE_UINT32 ));
|
| + EXPECT_STREQ("enum" , GetTypeNameForFieldType(FD::TYPE_ENUM ));
|
| + EXPECT_STREQ("sfixed32", GetTypeNameForFieldType(FD::TYPE_SFIXED32));
|
| + EXPECT_STREQ("sfixed64", GetTypeNameForFieldType(FD::TYPE_SFIXED64));
|
| + EXPECT_STREQ("sint32" , GetTypeNameForFieldType(FD::TYPE_SINT32 ));
|
| + EXPECT_STREQ("sint64" , GetTypeNameForFieldType(FD::TYPE_SINT64 ));
|
| +}
|
| +
|
| TEST_F(MiscTest, CppTypes) {
|
| // Test that CPP types are assigned correctly.
|
|
|
| @@ -1576,6 +1620,31 @@
|
| EXPECT_EQ(FD::CPPTYPE_INT64 , GetCppTypeForFieldType(FD::TYPE_SINT64 ));
|
| }
|
|
|
| +TEST_F(MiscTest, CppTypeNames) {
|
| + // Test that correct CPP type names are returned.
|
| +
|
| + typedef FieldDescriptor FD; // avoid ugly line wrapping
|
| +
|
| + EXPECT_STREQ("double" , GetCppTypeNameForFieldType(FD::TYPE_DOUBLE ));
|
| + EXPECT_STREQ("float" , GetCppTypeNameForFieldType(FD::TYPE_FLOAT ));
|
| + EXPECT_STREQ("int64" , GetCppTypeNameForFieldType(FD::TYPE_INT64 ));
|
| + EXPECT_STREQ("uint64" , GetCppTypeNameForFieldType(FD::TYPE_UINT64 ));
|
| + EXPECT_STREQ("int32" , GetCppTypeNameForFieldType(FD::TYPE_INT32 ));
|
| + EXPECT_STREQ("uint64" , GetCppTypeNameForFieldType(FD::TYPE_FIXED64 ));
|
| + EXPECT_STREQ("uint32" , GetCppTypeNameForFieldType(FD::TYPE_FIXED32 ));
|
| + EXPECT_STREQ("bool" , GetCppTypeNameForFieldType(FD::TYPE_BOOL ));
|
| + EXPECT_STREQ("string" , GetCppTypeNameForFieldType(FD::TYPE_STRING ));
|
| + EXPECT_STREQ("message", GetCppTypeNameForFieldType(FD::TYPE_GROUP ));
|
| + EXPECT_STREQ("message", GetCppTypeNameForFieldType(FD::TYPE_MESSAGE ));
|
| + EXPECT_STREQ("string" , GetCppTypeNameForFieldType(FD::TYPE_BYTES ));
|
| + EXPECT_STREQ("uint32" , GetCppTypeNameForFieldType(FD::TYPE_UINT32 ));
|
| + EXPECT_STREQ("enum" , GetCppTypeNameForFieldType(FD::TYPE_ENUM ));
|
| + EXPECT_STREQ("int32" , GetCppTypeNameForFieldType(FD::TYPE_SFIXED32));
|
| + EXPECT_STREQ("int64" , GetCppTypeNameForFieldType(FD::TYPE_SFIXED64));
|
| + EXPECT_STREQ("int32" , GetCppTypeNameForFieldType(FD::TYPE_SINT32 ));
|
| + EXPECT_STREQ("int64" , GetCppTypeNameForFieldType(FD::TYPE_SINT64 ));
|
| +}
|
| +
|
| TEST_F(MiscTest, DefaultValues) {
|
| // Test that setting default values works.
|
| FileDescriptorProto file_proto;
|
| @@ -1739,14 +1808,32 @@
|
| }
|
|
|
| // ===================================================================
|
| +enum DescriptorPoolMode {
|
| + NO_DATABASE,
|
| + FALLBACK_DATABASE
|
| +};
|
|
|
| -class AllowUnknownDependenciesTest : public testing::Test {
|
| +class AllowUnknownDependenciesTest
|
| + : public testing::TestWithParam<DescriptorPoolMode> {
|
| protected:
|
| + DescriptorPoolMode mode() {
|
| + return GetParam();
|
| + }
|
| +
|
| virtual void SetUp() {
|
| FileDescriptorProto foo_proto, bar_proto;
|
|
|
| - pool_.AllowUnknownDependencies();
|
| + switch (mode()) {
|
| + case NO_DATABASE:
|
| + pool_.reset(new DescriptorPool);
|
| + break;
|
| + case FALLBACK_DATABASE:
|
| + pool_.reset(new DescriptorPool(&db_));
|
| + break;
|
| + }
|
|
|
| + pool_->AllowUnknownDependencies();
|
| +
|
| ASSERT_TRUE(TextFormat::ParseFromString(
|
| "name: 'foo.proto'"
|
| "dependency: 'bar.proto'"
|
| @@ -1776,13 +1863,13 @@
|
| &bar_proto));
|
|
|
| // Collect pointers to stuff.
|
| - bar_file_ = pool_.BuildFile(bar_proto);
|
| + bar_file_ = BuildFile(bar_proto);
|
| ASSERT_TRUE(bar_file_ != NULL);
|
|
|
| ASSERT_EQ(1, bar_file_->message_type_count());
|
| bar_type_ = bar_file_->message_type(0);
|
|
|
| - foo_file_ = pool_.BuildFile(foo_proto);
|
| + foo_file_ = BuildFile(foo_proto);
|
| ASSERT_TRUE(foo_file_ != NULL);
|
|
|
| ASSERT_EQ(1, foo_file_->message_type_count());
|
| @@ -1794,6 +1881,20 @@
|
| qux_field_ = foo_type_->field(2);
|
| }
|
|
|
| + const FileDescriptor* BuildFile(const FileDescriptorProto& proto) {
|
| + switch (mode()) {
|
| + case NO_DATABASE:
|
| + return pool_->BuildFile(proto);
|
| + break;
|
| + case FALLBACK_DATABASE: {
|
| + EXPECT_TRUE(db_.Add(proto));
|
| + return pool_->FindFileByName(proto.name());
|
| + }
|
| + }
|
| + GOOGLE_LOG(FATAL) << "Can't get here.";
|
| + return NULL;
|
| + }
|
| +
|
| const FileDescriptor* bar_file_;
|
| const Descriptor* bar_type_;
|
| const FileDescriptor* foo_file_;
|
| @@ -1802,10 +1903,11 @@
|
| const FieldDescriptor* baz_field_;
|
| const FieldDescriptor* qux_field_;
|
|
|
| - DescriptorPool pool_;
|
| + SimpleDescriptorDatabase db_; // used if in FALLBACK_DATABASE mode.
|
| + scoped_ptr<DescriptorPool> pool_;
|
| };
|
|
|
| -TEST_F(AllowUnknownDependenciesTest, PlaceholderFile) {
|
| +TEST_P(AllowUnknownDependenciesTest, PlaceholderFile) {
|
| ASSERT_EQ(2, foo_file_->dependency_count());
|
| EXPECT_EQ(bar_file_, foo_file_->dependency(0));
|
|
|
| @@ -1814,11 +1916,11 @@
|
| EXPECT_EQ(0, baz_file->message_type_count());
|
|
|
| // Placeholder files should not be findable.
|
| - EXPECT_EQ(bar_file_, pool_.FindFileByName(bar_file_->name()));
|
| - EXPECT_TRUE(pool_.FindFileByName(baz_file->name()) == NULL);
|
| + EXPECT_EQ(bar_file_, pool_->FindFileByName(bar_file_->name()));
|
| + EXPECT_TRUE(pool_->FindFileByName(baz_file->name()) == NULL);
|
| }
|
|
|
| -TEST_F(AllowUnknownDependenciesTest, PlaceholderTypes) {
|
| +TEST_P(AllowUnknownDependenciesTest, PlaceholderTypes) {
|
| ASSERT_EQ(FieldDescriptor::TYPE_MESSAGE, bar_field_->type());
|
| EXPECT_EQ(bar_type_, bar_field_->message_type());
|
|
|
| @@ -1836,12 +1938,12 @@
|
| EXPECT_EQ("corge.Qux.placeholder.proto", qux_type->file()->name());
|
|
|
| // Placeholder types should not be findable.
|
| - EXPECT_EQ(bar_type_, pool_.FindMessageTypeByName(bar_type_->full_name()));
|
| - EXPECT_TRUE(pool_.FindMessageTypeByName(baz_type->full_name()) == NULL);
|
| - EXPECT_TRUE(pool_.FindEnumTypeByName(qux_type->full_name()) == NULL);
|
| + EXPECT_EQ(bar_type_, pool_->FindMessageTypeByName(bar_type_->full_name()));
|
| + EXPECT_TRUE(pool_->FindMessageTypeByName(baz_type->full_name()) == NULL);
|
| + EXPECT_TRUE(pool_->FindEnumTypeByName(qux_type->full_name()) == NULL);
|
| }
|
|
|
| -TEST_F(AllowUnknownDependenciesTest, CopyTo) {
|
| +TEST_P(AllowUnknownDependenciesTest, CopyTo) {
|
| // FieldDescriptor::CopyTo() should write non-fully-qualified type names
|
| // for placeholder types which were not originally fully-qualified.
|
| FieldDescriptorProto proto;
|
| @@ -1864,7 +1966,7 @@
|
| EXPECT_EQ(FieldDescriptorProto::TYPE_ENUM, proto.type());
|
| }
|
|
|
| -TEST_F(AllowUnknownDependenciesTest, CustomOptions) {
|
| +TEST_P(AllowUnknownDependenciesTest, CustomOptions) {
|
| // Qux should still have the uninterpreted option attached.
|
| ASSERT_EQ(1, qux_field_->options().uninterpreted_option_size());
|
| const UninterpretedOption& option =
|
| @@ -1873,7 +1975,7 @@
|
| EXPECT_EQ("grault", option.name(0).name_part());
|
| }
|
|
|
| -TEST_F(AllowUnknownDependenciesTest, UnknownExtendee) {
|
| +TEST_P(AllowUnknownDependenciesTest, UnknownExtendee) {
|
| // Test that we can extend an unknown type. This is slightly tricky because
|
| // it means that the placeholder type must have an extension range.
|
|
|
| @@ -1884,7 +1986,7 @@
|
| "extension { extendee: 'UnknownType' name:'some_extension' number:123"
|
| " label:LABEL_OPTIONAL type:TYPE_INT32 }",
|
| &extension_proto));
|
| - const FileDescriptor* file = pool_.BuildFile(extension_proto);
|
| + const FileDescriptor* file = BuildFile(extension_proto);
|
|
|
| ASSERT_TRUE(file != NULL);
|
|
|
| @@ -1896,7 +1998,7 @@
|
| EXPECT_EQ(FieldDescriptor::kMaxNumber + 1, extendee->extension_range(0)->end);
|
| }
|
|
|
| -TEST_F(AllowUnknownDependenciesTest, CustomOption) {
|
| +TEST_P(AllowUnknownDependenciesTest, CustomOption) {
|
| // Test that we can use a custom option without having parsed
|
| // descriptor.proto.
|
|
|
| @@ -1937,7 +2039,7 @@
|
| "}",
|
| &option_proto));
|
|
|
| - const FileDescriptor* file = pool_.BuildFile(option_proto);
|
| + const FileDescriptor* file = BuildFile(option_proto);
|
| ASSERT_TRUE(file != NULL);
|
|
|
| // Verify that no extension options were set, but they were left as
|
| @@ -1949,6 +2051,81 @@
|
| EXPECT_EQ(2, file->options().uninterpreted_option_size());
|
| }
|
|
|
| +TEST_P(AllowUnknownDependenciesTest,
|
| + UndeclaredDependencyTriggersBuildOfDependency) {
|
| + // Crazy case: suppose foo.proto refers to a symbol without declaring the
|
| + // dependency that finds it. In the event that the pool is backed by a
|
| + // DescriptorDatabase, the pool will attempt to find the symbol in the
|
| + // database. If successful, it will build the undeclared dependency to verify
|
| + // that the file does indeed contain the symbol. If that file fails to build,
|
| + // then its descriptors must be rolled back. However, we still want foo.proto
|
| + // to build successfully, since we are allowing unknown dependencies.
|
| +
|
| + FileDescriptorProto undeclared_dep_proto;
|
| + // We make this file fail to build by giving it two fields with tag 1.
|
| + ASSERT_TRUE(TextFormat::ParseFromString(
|
| + "name: \"invalid_file_as_undeclared_dep.proto\" "
|
| + "package: \"undeclared\" "
|
| + "message_type: { "
|
| + " name: \"Quux\" "
|
| + " field { "
|
| + " name:'qux' number:1 label:LABEL_OPTIONAL type: TYPE_INT32 "
|
| + " }"
|
| + " field { "
|
| + " name:'quux' number:1 label:LABEL_OPTIONAL type: TYPE_INT64 "
|
| + " }"
|
| + "}",
|
| + &undeclared_dep_proto));
|
| + // We can't use the BuildFile() helper because we don't actually want to build
|
| + // it into the descriptor pool in the fallback database case: it just needs to
|
| + // be sitting in the database so that it gets built during the building of
|
| + // test.proto below.
|
| + switch (mode()) {
|
| + case NO_DATABASE: {
|
| + ASSERT_TRUE(pool_->BuildFile(undeclared_dep_proto) == NULL);
|
| + break;
|
| + }
|
| + case FALLBACK_DATABASE: {
|
| + ASSERT_TRUE(db_.Add(undeclared_dep_proto));
|
| + }
|
| + }
|
| +
|
| + FileDescriptorProto test_proto;
|
| + ASSERT_TRUE(TextFormat::ParseFromString(
|
| + "name: \"test.proto\" "
|
| + "message_type: { "
|
| + " name: \"Corge\" "
|
| + " field { "
|
| + " name:'quux' number:1 label: LABEL_OPTIONAL "
|
| + " type_name:'undeclared.Quux' type: TYPE_MESSAGE "
|
| + " }"
|
| + "}",
|
| + &test_proto));
|
| +
|
| + const FileDescriptor* file = BuildFile(test_proto);
|
| + ASSERT_TRUE(file != NULL);
|
| + GOOGLE_LOG(INFO) << file->DebugString();
|
| +
|
| + EXPECT_EQ(0, file->dependency_count());
|
| + ASSERT_EQ(1, file->message_type_count());
|
| + const Descriptor* corge_desc = file->message_type(0);
|
| + ASSERT_EQ("Corge", corge_desc->name());
|
| + ASSERT_EQ(1, corge_desc->field_count());
|
| +
|
| + const FieldDescriptor* quux_field = corge_desc->field(0);
|
| + ASSERT_EQ(FieldDescriptor::TYPE_MESSAGE, quux_field->type());
|
| + ASSERT_EQ("Quux", quux_field->message_type()->name());
|
| + ASSERT_EQ("undeclared.Quux", quux_field->message_type()->full_name());
|
| + EXPECT_EQ("undeclared.Quux.placeholder.proto",
|
| + quux_field->message_type()->file()->name());
|
| + // The place holder type should not be findable.
|
| + ASSERT_TRUE(pool_->FindMessageTypeByName("undeclared.Quux") == NULL);
|
| +}
|
| +
|
| +INSTANTIATE_TEST_CASE_P(DatabaseSource,
|
| + AllowUnknownDependenciesTest,
|
| + testing::Values(NO_DATABASE, FALLBACK_DATABASE));
|
| +
|
| // ===================================================================
|
|
|
| TEST(CustomOptions, OptionLocations) {
|
| @@ -2451,6 +2628,15 @@
|
| "bar.proto: bar.proto: OTHER: Import \"foo.proto\" has not been loaded.\n");
|
| }
|
|
|
| +TEST_F(ValidationErrorTest, InvalidPublicDependencyIndex) {
|
| + BuildFile("name: \"foo.proto\"");
|
| + BuildFileWithErrors(
|
| + "name: \"bar.proto\" "
|
| + "dependency: \"foo.proto\" "
|
| + "public_dependency: 1",
|
| + "bar.proto: bar.proto: OTHER: Invalid public dependency index.\n");
|
| +}
|
| +
|
| TEST_F(ValidationErrorTest, ForeignUnimportedPackageNoCrash) {
|
| // Used to crash: If we depend on a non-existent file and then refer to a
|
| // package defined in a file that we didn't import, and that package is
|
| @@ -2829,6 +3015,164 @@
|
| "necessary import.\n");
|
| }
|
|
|
| +TEST_F(ValidationErrorTest, FieldTypeDefinedInIndirectDependency) {
|
| + // Test for hidden dependencies.
|
| + //
|
| + // // bar.proto
|
| + // message Bar{}
|
| + //
|
| + // // forward.proto
|
| + // import "bar.proto"
|
| + //
|
| + // // foo.proto
|
| + // import "forward.proto"
|
| + // message Foo {
|
| + // optional Bar foo = 1; // Error, needs to import bar.proto explicitly.
|
| + // }
|
| + //
|
| + BuildFile(
|
| + "name: \"bar.proto\" "
|
| + "message_type { name: \"Bar\" }");
|
| +
|
| + BuildFile(
|
| + "name: \"forward.proto\""
|
| + "dependency: \"bar.proto\"");
|
| +
|
| + BuildFileWithErrors(
|
| + "name: \"foo.proto\" "
|
| + "dependency: \"forward.proto\" "
|
| + "message_type {"
|
| + " name: \"Foo\""
|
| + " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type_name:\"Bar\" }"
|
| + "}",
|
| + "foo.proto: Foo.foo: TYPE: \"Bar\" seems to be defined in \"bar.proto\", "
|
| + "which is not imported by \"foo.proto\". To use it here, please add the "
|
| + "necessary import.\n");
|
| +}
|
| +
|
| +TEST_F(ValidationErrorTest, FieldTypeDefinedInPublicDependency) {
|
| + // Test for public dependencies.
|
| + //
|
| + // // bar.proto
|
| + // message Bar{}
|
| + //
|
| + // // forward.proto
|
| + // import public "bar.proto"
|
| + //
|
| + // // foo.proto
|
| + // import "forward.proto"
|
| + // message Foo {
|
| + // optional Bar foo = 1; // Correct. "bar.proto" is public imported into
|
| + // // forward.proto, so when "foo.proto" imports
|
| + // // "forward.proto", it imports "bar.proto" too.
|
| + // }
|
| + //
|
| + BuildFile(
|
| + "name: \"bar.proto\" "
|
| + "message_type { name: \"Bar\" }");
|
| +
|
| + BuildFile(
|
| + "name: \"forward.proto\""
|
| + "dependency: \"bar.proto\" "
|
| + "public_dependency: 0");
|
| +
|
| + BuildFile(
|
| + "name: \"foo.proto\" "
|
| + "dependency: \"forward.proto\" "
|
| + "message_type {"
|
| + " name: \"Foo\""
|
| + " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type_name:\"Bar\" }"
|
| + "}");
|
| +}
|
| +
|
| +TEST_F(ValidationErrorTest, FieldTypeDefinedInTransitivePublicDependency) {
|
| + // Test for public dependencies.
|
| + //
|
| + // // bar.proto
|
| + // message Bar{}
|
| + //
|
| + // // forward.proto
|
| + // import public "bar.proto"
|
| + //
|
| + // // forward2.proto
|
| + // import public "forward.proto"
|
| + //
|
| + // // foo.proto
|
| + // import "forward2.proto"
|
| + // message Foo {
|
| + // optional Bar foo = 1; // Correct, public imports are transitive.
|
| + // }
|
| + //
|
| + BuildFile(
|
| + "name: \"bar.proto\" "
|
| + "message_type { name: \"Bar\" }");
|
| +
|
| + BuildFile(
|
| + "name: \"forward.proto\""
|
| + "dependency: \"bar.proto\" "
|
| + "public_dependency: 0");
|
| +
|
| + BuildFile(
|
| + "name: \"forward2.proto\""
|
| + "dependency: \"forward.proto\" "
|
| + "public_dependency: 0");
|
| +
|
| + BuildFile(
|
| + "name: \"foo.proto\" "
|
| + "dependency: \"forward2.proto\" "
|
| + "message_type {"
|
| + " name: \"Foo\""
|
| + " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type_name:\"Bar\" }"
|
| + "}");
|
| +}
|
| +
|
| +TEST_F(ValidationErrorTest,
|
| + FieldTypeDefinedInPrivateDependencyOfPublicDependency) {
|
| + // Test for public dependencies.
|
| + //
|
| + // // bar.proto
|
| + // message Bar{}
|
| + //
|
| + // // forward.proto
|
| + // import "bar.proto"
|
| + //
|
| + // // forward2.proto
|
| + // import public "forward.proto"
|
| + //
|
| + // // foo.proto
|
| + // import "forward2.proto"
|
| + // message Foo {
|
| + // optional Bar foo = 1; // Error, the "bar.proto" is not public imported
|
| + // // into "forward.proto", so will not be imported
|
| + // // into either "forward2.proto" or "foo.proto".
|
| + // }
|
| + //
|
| + BuildFile(
|
| + "name: \"bar.proto\" "
|
| + "message_type { name: \"Bar\" }");
|
| +
|
| + BuildFile(
|
| + "name: \"forward.proto\""
|
| + "dependency: \"bar.proto\"");
|
| +
|
| + BuildFile(
|
| + "name: \"forward2.proto\""
|
| + "dependency: \"forward.proto\" "
|
| + "public_dependency: 0");
|
| +
|
| + BuildFileWithErrors(
|
| + "name: \"foo.proto\" "
|
| + "dependency: \"forward2.proto\" "
|
| + "message_type {"
|
| + " name: \"Foo\""
|
| + " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type_name:\"Bar\" }"
|
| + "}",
|
| + "foo.proto: Foo.foo: TYPE: \"Bar\" seems to be defined in \"bar.proto\", "
|
| + "which is not imported by \"foo.proto\". To use it here, please add the "
|
| + "necessary import.\n");
|
| +}
|
| +
|
| +
|
| TEST_F(ValidationErrorTest, SearchMostLocalFirst) {
|
| // The following should produce an error that Bar.Baz is not defined:
|
| // message Bar { message Baz {} }
|
| @@ -3031,7 +3375,8 @@
|
| " method { name: \"A\" input_type: \"Bar\" output_type: \"Foo\" }"
|
| "}",
|
|
|
| - "foo.proto: TestService.A: INPUT_TYPE: \"Bar\" is not defined.\n");
|
| + "foo.proto: TestService.A: INPUT_TYPE: \"Bar\" is not defined.\n"
|
| + );
|
| }
|
|
|
| TEST_F(ValidationErrorTest, InputTypeNotAMessage) {
|
| @@ -3044,7 +3389,8 @@
|
| " method { name: \"A\" input_type: \"Bar\" output_type: \"Foo\" }"
|
| "}",
|
|
|
| - "foo.proto: TestService.A: INPUT_TYPE: \"Bar\" is not a message type.\n");
|
| + "foo.proto: TestService.A: INPUT_TYPE: \"Bar\" is not a message type.\n"
|
| + );
|
| }
|
|
|
| TEST_F(ValidationErrorTest, OutputTypeNotDefined) {
|
| @@ -3056,7 +3402,8 @@
|
| " method { name: \"A\" input_type: \"Foo\" output_type: \"Bar\" }"
|
| "}",
|
|
|
| - "foo.proto: TestService.A: OUTPUT_TYPE: \"Bar\" is not defined.\n");
|
| + "foo.proto: TestService.A: OUTPUT_TYPE: \"Bar\" is not defined.\n"
|
| + );
|
| }
|
|
|
| TEST_F(ValidationErrorTest, OutputTypeNotAMessage) {
|
| @@ -3069,9 +3416,11 @@
|
| " method { name: \"A\" input_type: \"Foo\" output_type: \"Bar\" }"
|
| "}",
|
|
|
| - "foo.proto: TestService.A: OUTPUT_TYPE: \"Bar\" is not a message type.\n");
|
| + "foo.proto: TestService.A: OUTPUT_TYPE: \"Bar\" is not a message type.\n"
|
| + );
|
| }
|
|
|
| +
|
| TEST_F(ValidationErrorTest, IllegalPackedField) {
|
| BuildFileWithErrors(
|
| "name: \"foo.proto\" "
|
| @@ -3592,7 +3941,8 @@
|
| " }"
|
| "}",
|
|
|
| - "foo.proto: TestService.Baz: INPUT_TYPE: \"NoSuchType\" is not defined.\n");
|
| + "foo.proto: TestService.Baz: INPUT_TYPE: \"NoSuchType\" is not defined.\n"
|
| + );
|
|
|
| // Make sure that if we build the same file again with the error fixed,
|
| // it works. If the above rollback was incomplete, then some symbols will
|
| @@ -3641,6 +3991,19 @@
|
| EXPECT_EQ(" Foo: \"Foo\" is already defined.", errors[1]);
|
| }
|
|
|
| +TEST_F(ValidationErrorTest, DisallowEnumAlias) {
|
| + BuildFileWithErrors(
|
| + "name: \"foo.proto\" "
|
| + "enum_type {"
|
| + " name: \"Bar\""
|
| + " value { name:\"ENUM_A\" number:0 }"
|
| + " value { name:\"ENUM_B\" number:0 }"
|
| + " options { allow_alias: false }"
|
| + "}",
|
| + "foo.proto: Bar: NUMBER: "
|
| + "\"ENUM_B\" uses the same enum value as \"ENUM_A\"\n");
|
| +}
|
| +
|
| // ===================================================================
|
| // DescriptorDatabase
|
|
|
| @@ -3659,16 +4022,23 @@
|
|
|
| virtual void SetUp() {
|
| AddToDatabase(&database_,
|
| - "name: \"foo.proto\" "
|
| - "message_type { name:\"Foo\" extension_range { start: 1 end: 100 } } "
|
| - "enum_type { name:\"TestEnum\" value { name:\"DUMMY\" number:0 } } "
|
| - "service { name:\"TestService\" } ");
|
| + "name: 'foo.proto' "
|
| + "message_type { name:'Foo' extension_range { start: 1 end: 100 } } "
|
| + "enum_type { name:'TestEnum' value { name:'DUMMY' number:0 } } "
|
| + "service { name:'TestService' } ");
|
| AddToDatabase(&database_,
|
| - "name: \"bar.proto\" "
|
| - "dependency: \"foo.proto\" "
|
| - "message_type { name:\"Bar\" } "
|
| - "extension { name:\"foo_ext\" extendee: \".Foo\" number:5 "
|
| + "name: 'bar.proto' "
|
| + "dependency: 'foo.proto' "
|
| + "message_type { name:'Bar' } "
|
| + "extension { name:'foo_ext' extendee: '.Foo' number:5 "
|
| " label:LABEL_OPTIONAL type:TYPE_INT32 } ");
|
| + // Baz has an undeclared dependency on Foo.
|
| + AddToDatabase(&database_,
|
| + "name: 'baz.proto' "
|
| + "message_type { "
|
| + " name:'Baz' "
|
| + " field { name:'foo' number:1 label:LABEL_OPTIONAL type_name:'Foo' } "
|
| + "}");
|
| }
|
|
|
| // We can't inject a file containing errors into a DescriptorPool, so we
|
| @@ -3907,6 +4277,33 @@
|
| error_collector.text_);
|
| }
|
|
|
| +TEST_F(DatabaseBackedPoolTest, UndeclaredDependencyOnUnbuiltType) {
|
| + // Check that we find and report undeclared dependencies on types that exist
|
| + // in the descriptor database but that have not not been built yet.
|
| + MockErrorCollector error_collector;
|
| + DescriptorPool pool(&database_, &error_collector);
|
| + EXPECT_TRUE(pool.FindMessageTypeByName("Baz") == NULL);
|
| + EXPECT_EQ(
|
| + "baz.proto: Baz.foo: TYPE: \"Foo\" seems to be defined in \"foo.proto\", "
|
| + "which is not imported by \"baz.proto\". To use it here, please add "
|
| + "the necessary import.\n",
|
| + error_collector.text_);
|
| +}
|
| +
|
| +TEST_F(DatabaseBackedPoolTest, RollbackAfterError) {
|
| + // Make sure that all traces of bad types are removed from the pool. This used
|
| + // to be b/4529436, due to the fact that a symbol resolution failure could
|
| + // potentially cause another file to be recursively built, which would trigger
|
| + // a checkpoint _past_ possibly invalid symbols.
|
| + // Baz is defined in the database, but the file is invalid because it is
|
| + // missing a necessary import.
|
| + DescriptorPool pool(&database_);
|
| + EXPECT_TRUE(pool.FindMessageTypeByName("Baz") == NULL);
|
| + // Make sure that searching again for the file or the type fails.
|
| + EXPECT_TRUE(pool.FindFileByName("baz.proto") == NULL);
|
| + EXPECT_TRUE(pool.FindMessageTypeByName("Baz") == NULL);
|
| +}
|
| +
|
| TEST_F(DatabaseBackedPoolTest, UnittestProto) {
|
| // Try to load all of unittest.proto from a DescriptorDatabase. This should
|
| // thoroughly test all paths through DescriptorBuilder to insure that there
|
| @@ -3963,6 +4360,16 @@
|
| EXPECT_TRUE(file->FindEnumValueByName("NO_SUCH_VALUE") == NULL);
|
| EXPECT_TRUE(file->FindServiceByName("NO_SUCH_VALUE") == NULL);
|
| EXPECT_TRUE(file->FindExtensionByName("no_such_extension") == NULL);
|
| +
|
| + EXPECT_TRUE(pool.FindFileContainingSymbol("Foo.no.such.field") == NULL);
|
| + EXPECT_TRUE(pool.FindFileContainingSymbol("Foo.no_such_field") == NULL);
|
| + EXPECT_TRUE(pool.FindMessageTypeByName("Foo.NoSuchMessageType") == NULL);
|
| + EXPECT_TRUE(pool.FindFieldByName("Foo.no_such_field") == NULL);
|
| + EXPECT_TRUE(pool.FindExtensionByName("Foo.no_such_extension") == NULL);
|
| + EXPECT_TRUE(pool.FindEnumTypeByName("Foo.NoSuchEnumType") == NULL);
|
| + EXPECT_TRUE(pool.FindEnumValueByName("Foo.NO_SUCH_VALUE") == NULL);
|
| + EXPECT_TRUE(pool.FindMethodByName("TestService.NoSuchMethod") == NULL);
|
| +
|
| EXPECT_EQ(0, call_counter.call_count_);
|
| }
|
|
|
| @@ -4028,7 +4435,220 @@
|
|
|
| // ===================================================================
|
|
|
| +class AbortingErrorCollector : public DescriptorPool::ErrorCollector {
|
| + public:
|
| + AbortingErrorCollector() {}
|
|
|
| + virtual void AddError(
|
| + const string &filename,
|
| + const string &element_name,
|
| + const Message *message,
|
| + ErrorLocation location,
|
| + const string &error_message) {
|
| + GOOGLE_LOG(FATAL) << "AddError() called unexpectedly: " << filename << ": "
|
| + << error_message;
|
| + }
|
| + private:
|
| + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(AbortingErrorCollector);
|
| +};
|
| +
|
| +// A source tree containing only one file.
|
| +class SingletonSourceTree : public compiler::SourceTree {
|
| + public:
|
| + SingletonSourceTree(const string& filename, const string& contents)
|
| + : filename_(filename), contents_(contents) {}
|
| +
|
| + virtual io::ZeroCopyInputStream* Open(const string& filename) {
|
| + return filename == filename_ ?
|
| + new io::ArrayInputStream(contents_.data(), contents_.size()) : NULL;
|
| + }
|
| +
|
| + private:
|
| + const string filename_;
|
| + const string contents_;
|
| +
|
| + GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(SingletonSourceTree);
|
| +};
|
| +
|
| +const char *const kSourceLocationTestInput =
|
| + "syntax = \"proto2\";\n"
|
| + "message A {\n"
|
| + " optional int32 a = 1;\n"
|
| + " message B {\n"
|
| + " required double b = 1;\n"
|
| + " }\n"
|
| + "}\n"
|
| + "enum Indecision {\n"
|
| + " YES = 1;\n"
|
| + " NO = 2;\n"
|
| + " MAYBE = 3;\n"
|
| + "}\n"
|
| + "service S {\n"
|
| + " rpc Method(A) returns (A.B);\n"
|
| + // Put an empty line here to make the source location range match.
|
| + "\n"
|
| + "}\n";
|
| +
|
| +class SourceLocationTest : public testing::Test {
|
| + public:
|
| + SourceLocationTest()
|
| + : source_tree_("/test/test.proto", kSourceLocationTestInput),
|
| + db_(&source_tree_),
|
| + pool_(&db_, &collector_) {}
|
| +
|
| + static string PrintSourceLocation(const SourceLocation &loc) {
|
| + return strings::Substitute("$0:$1-$2:$3",
|
| + 1 + loc.start_line,
|
| + 1 + loc.start_column,
|
| + 1 + loc.end_line,
|
| + 1 + loc.end_column);
|
| + }
|
| +
|
| + private:
|
| + AbortingErrorCollector collector_;
|
| + SingletonSourceTree source_tree_;
|
| + compiler::SourceTreeDescriptorDatabase db_;
|
| +
|
| + protected:
|
| + DescriptorPool pool_;
|
| +};
|
| +
|
| +// TODO(adonovan): implement support for option fields and for
|
| +// subparts of declarations.
|
| +
|
| +TEST_F(SourceLocationTest, GetSourceLocation) {
|
| + SourceLocation loc;
|
| +
|
| + const FileDescriptor *file_desc =
|
| + GOOGLE_CHECK_NOTNULL(pool_.FindFileByName("/test/test.proto"));
|
| +
|
| + const Descriptor *a_desc = file_desc->FindMessageTypeByName("A");
|
| + EXPECT_TRUE(a_desc->GetSourceLocation(&loc));
|
| + EXPECT_EQ("2:1-7:2", PrintSourceLocation(loc));
|
| +
|
| + const Descriptor *a_b_desc = a_desc->FindNestedTypeByName("B");
|
| + EXPECT_TRUE(a_b_desc->GetSourceLocation(&loc));
|
| + EXPECT_EQ("4:3-6:4", PrintSourceLocation(loc));
|
| +
|
| + const EnumDescriptor *e_desc = file_desc->FindEnumTypeByName("Indecision");
|
| + EXPECT_TRUE(e_desc->GetSourceLocation(&loc));
|
| + EXPECT_EQ("8:1-12:2", PrintSourceLocation(loc));
|
| +
|
| + const EnumValueDescriptor *yes_desc = e_desc->FindValueByName("YES");
|
| + EXPECT_TRUE(yes_desc->GetSourceLocation(&loc));
|
| + EXPECT_EQ("9:3-9:13", PrintSourceLocation(loc));
|
| +
|
| + const ServiceDescriptor *s_desc = file_desc->FindServiceByName("S");
|
| + EXPECT_TRUE(s_desc->GetSourceLocation(&loc));
|
| + EXPECT_EQ("13:1-16:2", PrintSourceLocation(loc));
|
| +
|
| + const MethodDescriptor *m_desc = s_desc->FindMethodByName("Method");
|
| + EXPECT_TRUE(m_desc->GetSourceLocation(&loc));
|
| + EXPECT_EQ("14:3-14:31", PrintSourceLocation(loc));
|
| +
|
| +}
|
| +
|
| +// Missing SourceCodeInfo doesn't cause crash:
|
| +TEST_F(SourceLocationTest, GetSourceLocation_MissingSourceCodeInfo) {
|
| + SourceLocation loc;
|
| +
|
| + const FileDescriptor *file_desc =
|
| + GOOGLE_CHECK_NOTNULL(pool_.FindFileByName("/test/test.proto"));
|
| +
|
| + FileDescriptorProto proto;
|
| + file_desc->CopyTo(&proto); // Note, this discards the SourceCodeInfo.
|
| + EXPECT_FALSE(proto.has_source_code_info());
|
| +
|
| + DescriptorPool bad1_pool(&pool_);
|
| + const FileDescriptor* bad1_file_desc =
|
| + GOOGLE_CHECK_NOTNULL(bad1_pool.BuildFile(proto));
|
| + const Descriptor *bad1_a_desc = bad1_file_desc->FindMessageTypeByName("A");
|
| + EXPECT_FALSE(bad1_a_desc->GetSourceLocation(&loc));
|
| +}
|
| +
|
| +// Corrupt SourceCodeInfo doesn't cause crash:
|
| +TEST_F(SourceLocationTest, GetSourceLocation_BogusSourceCodeInfo) {
|
| + SourceLocation loc;
|
| +
|
| + const FileDescriptor *file_desc =
|
| + GOOGLE_CHECK_NOTNULL(pool_.FindFileByName("/test/test.proto"));
|
| +
|
| + FileDescriptorProto proto;
|
| + file_desc->CopyTo(&proto); // Note, this discards the SourceCodeInfo.
|
| + EXPECT_FALSE(proto.has_source_code_info());
|
| + SourceCodeInfo_Location *loc_msg =
|
| + proto.mutable_source_code_info()->add_location();
|
| + loc_msg->add_path(1);
|
| + loc_msg->add_path(2);
|
| + loc_msg->add_path(3);
|
| + loc_msg->add_span(4);
|
| + loc_msg->add_span(5);
|
| + loc_msg->add_span(6);
|
| +
|
| + DescriptorPool bad2_pool(&pool_);
|
| + const FileDescriptor* bad2_file_desc =
|
| + GOOGLE_CHECK_NOTNULL(bad2_pool.BuildFile(proto));
|
| + const Descriptor *bad2_a_desc = bad2_file_desc->FindMessageTypeByName("A");
|
| + EXPECT_FALSE(bad2_a_desc->GetSourceLocation(&loc));
|
| +}
|
| +
|
| +// ===================================================================
|
| +
|
| +const char* const kCopySourceCodeInfoToTestInput =
|
| + "syntax = \"proto2\";\n"
|
| + "message Foo {}\n";
|
| +
|
| +// Required since source code information is not preserved by
|
| +// FileDescriptorTest.
|
| +class CopySourceCodeInfoToTest : public testing::Test {
|
| + public:
|
| + CopySourceCodeInfoToTest()
|
| + : source_tree_("/test/test.proto", kCopySourceCodeInfoToTestInput),
|
| + db_(&source_tree_),
|
| + pool_(&db_, &collector_) {}
|
| +
|
| + private:
|
| + AbortingErrorCollector collector_;
|
| + SingletonSourceTree source_tree_;
|
| + compiler::SourceTreeDescriptorDatabase db_;
|
| +
|
| + protected:
|
| + DescriptorPool pool_;
|
| +};
|
| +
|
| +TEST_F(CopySourceCodeInfoToTest, CopyTo_DoesNotCopySourceCodeInfo) {
|
| + const FileDescriptor* file_desc =
|
| + GOOGLE_CHECK_NOTNULL(pool_.FindFileByName("/test/test.proto"));
|
| + FileDescriptorProto file_desc_proto;
|
| + ASSERT_FALSE(file_desc_proto.has_source_code_info());
|
| +
|
| + file_desc->CopyTo(&file_desc_proto);
|
| + EXPECT_FALSE(file_desc_proto.has_source_code_info());
|
| +}
|
| +
|
| +TEST_F(CopySourceCodeInfoToTest, CopySourceCodeInfoTo) {
|
| + const FileDescriptor* file_desc =
|
| + GOOGLE_CHECK_NOTNULL(pool_.FindFileByName("/test/test.proto"));
|
| + FileDescriptorProto file_desc_proto;
|
| + ASSERT_FALSE(file_desc_proto.has_source_code_info());
|
| +
|
| + file_desc->CopySourceCodeInfoTo(&file_desc_proto);
|
| + const SourceCodeInfo& info = file_desc_proto.source_code_info();
|
| + ASSERT_EQ(3, info.location_size());
|
| + // Get the Foo message location
|
| + const SourceCodeInfo_Location& foo_location = info.location(1);
|
| + ASSERT_EQ(2, foo_location.path_size());
|
| + EXPECT_EQ(FileDescriptorProto::kMessageTypeFieldNumber, foo_location.path(0));
|
| + EXPECT_EQ(0, foo_location.path(1)); // Foo is the first message defined
|
| + ASSERT_EQ(3, foo_location.span_size()); // Foo spans one line
|
| + EXPECT_EQ(1, foo_location.span(0)); // Foo is declared on line 1
|
| + EXPECT_EQ(0, foo_location.span(1)); // Foo starts at column 0
|
| + EXPECT_EQ(14, foo_location.span(2)); // Foo ends on column 14
|
| +}
|
| +
|
| +// ===================================================================
|
| +
|
| +
|
| } // namespace descriptor_unittest
|
| } // namespace protobuf
|
| } // namespace google
|
|
|