summaryrefslogtreecommitdiff
path: root/src/google/protobuf/descriptor_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/google/protobuf/descriptor_unittest.cc')
-rw-r--r--src/google/protobuf/descriptor_unittest.cc6615
1 files changed, 6615 insertions, 0 deletions
diff --git a/src/google/protobuf/descriptor_unittest.cc b/src/google/protobuf/descriptor_unittest.cc
new file mode 100644
index 00000000..4183138f
--- /dev/null
+++ b/src/google/protobuf/descriptor_unittest.cc
@@ -0,0 +1,6615 @@
+// Protocol Buffers - Google's data interchange format
+// Copyright 2008 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.
+
+// Author: kenton@google.com (Kenton Varda)
+// Based on original Protocol Buffers design by
+// Sanjay Ghemawat, Jeff Dean, and others.
+//
+// This file makes extensive use of RFC 3092. :)
+
+#include <memory>
+#ifndef _SHARED_PTR_H
+#include <google/protobuf/stubs/shared_ptr.h>
+#endif
+#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/text_format.h>
+#include <google/protobuf/stubs/strutil.h>
+#include <google/protobuf/stubs/substitute.h>
+
+#include <google/protobuf/stubs/common.h>
+#include <google/protobuf/stubs/logging.h>
+#include <google/protobuf/stubs/logging.h>
+#include <google/protobuf/testing/googletest.h>
+#include <gtest/gtest.h>
+
+namespace google {
+namespace protobuf {
+
+// Can't use an anonymous namespace here due to brokenness of Tru64 compiler.
+namespace descriptor_unittest {
+
+// Some helpers to make assembling descriptors faster.
+DescriptorProto* AddMessage(FileDescriptorProto* file, const string& name) {
+ DescriptorProto* result = file->add_message_type();
+ result->set_name(name);
+ return result;
+}
+
+DescriptorProto* AddNestedMessage(DescriptorProto* parent, const string& name) {
+ DescriptorProto* result = parent->add_nested_type();
+ result->set_name(name);
+ return result;
+}
+
+EnumDescriptorProto* AddEnum(FileDescriptorProto* file, const string& name) {
+ EnumDescriptorProto* result = file->add_enum_type();
+ result->set_name(name);
+ return result;
+}
+
+EnumDescriptorProto* AddNestedEnum(DescriptorProto* parent,
+ const string& name) {
+ EnumDescriptorProto* result = parent->add_enum_type();
+ result->set_name(name);
+ return result;
+}
+
+ServiceDescriptorProto* AddService(FileDescriptorProto* file,
+ const string& name) {
+ ServiceDescriptorProto* result = file->add_service();
+ result->set_name(name);
+ return result;
+}
+
+FieldDescriptorProto* AddField(DescriptorProto* parent,
+ const string& name, int number,
+ FieldDescriptorProto::Label label,
+ FieldDescriptorProto::Type type) {
+ FieldDescriptorProto* result = parent->add_field();
+ result->set_name(name);
+ result->set_number(number);
+ result->set_label(label);
+ result->set_type(type);
+ return result;
+}
+
+FieldDescriptorProto* AddExtension(FileDescriptorProto* file,
+ const string& extendee,
+ const string& name, int number,
+ FieldDescriptorProto::Label label,
+ FieldDescriptorProto::Type type) {
+ FieldDescriptorProto* result = file->add_extension();
+ result->set_name(name);
+ result->set_number(number);
+ result->set_label(label);
+ result->set_type(type);
+ result->set_extendee(extendee);
+ return result;
+}
+
+FieldDescriptorProto* AddNestedExtension(DescriptorProto* parent,
+ const string& extendee,
+ const string& name, int number,
+ FieldDescriptorProto::Label label,
+ FieldDescriptorProto::Type type) {
+ FieldDescriptorProto* result = parent->add_extension();
+ result->set_name(name);
+ result->set_number(number);
+ result->set_label(label);
+ result->set_type(type);
+ result->set_extendee(extendee);
+ return result;
+}
+
+DescriptorProto::ExtensionRange* AddExtensionRange(DescriptorProto* parent,
+ int start, int end) {
+ DescriptorProto::ExtensionRange* result = parent->add_extension_range();
+ result->set_start(start);
+ result->set_end(end);
+ return result;
+}
+
+DescriptorProto::ReservedRange* AddReservedRange(DescriptorProto* parent,
+ int start, int end) {
+ DescriptorProto::ReservedRange* result = parent->add_reserved_range();
+ result->set_start(start);
+ result->set_end(end);
+ return result;
+}
+
+EnumValueDescriptorProto* AddEnumValue(EnumDescriptorProto* enum_proto,
+ const string& name, int number) {
+ EnumValueDescriptorProto* result = enum_proto->add_value();
+ result->set_name(name);
+ result->set_number(number);
+ return result;
+}
+
+MethodDescriptorProto* AddMethod(ServiceDescriptorProto* service,
+ const string& name,
+ const string& input_type,
+ const string& output_type) {
+ MethodDescriptorProto* result = service->add_method();
+ result->set_name(name);
+ result->set_input_type(input_type);
+ result->set_output_type(output_type);
+ return result;
+}
+
+// Empty enums technically aren't allowed. We need to insert a dummy value
+// into them.
+void AddEmptyEnum(FileDescriptorProto* file, const string& name) {
+ AddEnumValue(AddEnum(file, name), name + "_DUMMY", 1);
+}
+
+class MockErrorCollector : public DescriptorPool::ErrorCollector {
+ public:
+ MockErrorCollector() {}
+ ~MockErrorCollector() {}
+
+ string text_;
+ string warning_text_;
+
+ // implements ErrorCollector ---------------------------------------
+ void AddError(const string& filename,
+ const string& element_name, const Message* descriptor,
+ ErrorLocation location, const string& message) {
+ const char* location_name = NULL;
+ switch (location) {
+ case NAME : location_name = "NAME" ; break;
+ case NUMBER : location_name = "NUMBER" ; break;
+ case TYPE : location_name = "TYPE" ; break;
+ case EXTENDEE : location_name = "EXTENDEE" ; break;
+ case DEFAULT_VALUE: location_name = "DEFAULT_VALUE"; break;
+ case OPTION_NAME : location_name = "OPTION_NAME" ; break;
+ case OPTION_VALUE : location_name = "OPTION_VALUE" ; break;
+ case INPUT_TYPE : location_name = "INPUT_TYPE" ; break;
+ case OUTPUT_TYPE : location_name = "OUTPUT_TYPE" ; break;
+ case OTHER : location_name = "OTHER" ; break;
+ }
+
+ strings::SubstituteAndAppend(
+ &text_, "$0: $1: $2: $3\n",
+ filename, element_name, location_name, message);
+ }
+
+ // implements ErrorCollector ---------------------------------------
+ void AddWarning(const string& filename, const string& element_name,
+ const Message* descriptor, ErrorLocation location,
+ const string& message) {
+ const char* location_name = NULL;
+ switch (location) {
+ case NAME : location_name = "NAME" ; break;
+ case NUMBER : location_name = "NUMBER" ; break;
+ case TYPE : location_name = "TYPE" ; break;
+ case EXTENDEE : location_name = "EXTENDEE" ; break;
+ case DEFAULT_VALUE: location_name = "DEFAULT_VALUE"; break;
+ case OPTION_NAME : location_name = "OPTION_NAME" ; break;
+ case OPTION_VALUE : location_name = "OPTION_VALUE" ; break;
+ case INPUT_TYPE : location_name = "INPUT_TYPE" ; break;
+ case OUTPUT_TYPE : location_name = "OUTPUT_TYPE" ; break;
+ case OTHER : location_name = "OTHER" ; break;
+ }
+
+ strings::SubstituteAndAppend(
+ &warning_text_, "$0: $1: $2: $3\n",
+ filename, element_name, location_name, message);
+ }
+};
+
+// ===================================================================
+
+// Test simple files.
+class FileDescriptorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Build descriptors for the following definitions:
+ //
+ // // in "foo.proto"
+ // message FooMessage { extensions 1; }
+ // enum FooEnum {FOO_ENUM_VALUE = 1;}
+ // service FooService {}
+ // extend FooMessage { optional int32 foo_extension = 1; }
+ //
+ // // in "bar.proto"
+ // package bar_package;
+ // message BarMessage { extensions 1; }
+ // enum BarEnum {BAR_ENUM_VALUE = 1;}
+ // service BarService {}
+ // extend BarMessage { optional int32 bar_extension = 1; }
+ //
+ // Also, we have an empty file "baz.proto". This file's purpose is to
+ // make sure that even though it has the same package as foo.proto,
+ // searching it for members of foo.proto won't work.
+
+ FileDescriptorProto foo_file;
+ foo_file.set_name("foo.proto");
+ AddExtensionRange(AddMessage(&foo_file, "FooMessage"), 1, 2);
+ AddEnumValue(AddEnum(&foo_file, "FooEnum"), "FOO_ENUM_VALUE", 1);
+ AddService(&foo_file, "FooService");
+ AddExtension(&foo_file, "FooMessage", "foo_extension", 1,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+
+ FileDescriptorProto bar_file;
+ bar_file.set_name("bar.proto");
+ bar_file.set_package("bar_package");
+ bar_file.add_dependency("foo.proto");
+ AddExtensionRange(AddMessage(&bar_file, "BarMessage"), 1, 2);
+ AddEnumValue(AddEnum(&bar_file, "BarEnum"), "BAR_ENUM_VALUE", 1);
+ AddService(&bar_file, "BarService");
+ AddExtension(&bar_file, "bar_package.BarMessage", "bar_extension", 1,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+
+ FileDescriptorProto baz_file;
+ baz_file.set_name("baz.proto");
+
+ // Build the descriptors and get the pointers.
+ foo_file_ = pool_.BuildFile(foo_file);
+ ASSERT_TRUE(foo_file_ != NULL);
+
+ bar_file_ = pool_.BuildFile(bar_file);
+ ASSERT_TRUE(bar_file_ != NULL);
+
+ baz_file_ = pool_.BuildFile(baz_file);
+ ASSERT_TRUE(baz_file_ != NULL);
+
+ ASSERT_EQ(1, foo_file_->message_type_count());
+ foo_message_ = foo_file_->message_type(0);
+ ASSERT_EQ(1, foo_file_->enum_type_count());
+ foo_enum_ = foo_file_->enum_type(0);
+ ASSERT_EQ(1, foo_enum_->value_count());
+ foo_enum_value_ = foo_enum_->value(0);
+ ASSERT_EQ(1, foo_file_->service_count());
+ foo_service_ = foo_file_->service(0);
+ ASSERT_EQ(1, foo_file_->extension_count());
+ foo_extension_ = foo_file_->extension(0);
+
+ ASSERT_EQ(1, bar_file_->message_type_count());
+ bar_message_ = bar_file_->message_type(0);
+ ASSERT_EQ(1, bar_file_->enum_type_count());
+ bar_enum_ = bar_file_->enum_type(0);
+ ASSERT_EQ(1, bar_enum_->value_count());
+ bar_enum_value_ = bar_enum_->value(0);
+ ASSERT_EQ(1, bar_file_->service_count());
+ bar_service_ = bar_file_->service(0);
+ ASSERT_EQ(1, bar_file_->extension_count());
+ bar_extension_ = bar_file_->extension(0);
+ }
+
+ DescriptorPool pool_;
+
+ const FileDescriptor* foo_file_;
+ const FileDescriptor* bar_file_;
+ const FileDescriptor* baz_file_;
+
+ const Descriptor* foo_message_;
+ const EnumDescriptor* foo_enum_;
+ const EnumValueDescriptor* foo_enum_value_;
+ const ServiceDescriptor* foo_service_;
+ const FieldDescriptor* foo_extension_;
+
+ const Descriptor* bar_message_;
+ const EnumDescriptor* bar_enum_;
+ const EnumValueDescriptor* bar_enum_value_;
+ const ServiceDescriptor* bar_service_;
+ const FieldDescriptor* bar_extension_;
+};
+
+TEST_F(FileDescriptorTest, Name) {
+ EXPECT_EQ("foo.proto", foo_file_->name());
+ EXPECT_EQ("bar.proto", bar_file_->name());
+ EXPECT_EQ("baz.proto", baz_file_->name());
+}
+
+TEST_F(FileDescriptorTest, Package) {
+ EXPECT_EQ("", foo_file_->package());
+ EXPECT_EQ("bar_package", bar_file_->package());
+}
+
+TEST_F(FileDescriptorTest, Dependencies) {
+ EXPECT_EQ(0, foo_file_->dependency_count());
+ EXPECT_EQ(1, bar_file_->dependency_count());
+ EXPECT_EQ(foo_file_, bar_file_->dependency(0));
+}
+
+TEST_F(FileDescriptorTest, FindMessageTypeByName) {
+ EXPECT_EQ(foo_message_, foo_file_->FindMessageTypeByName("FooMessage"));
+ EXPECT_EQ(bar_message_, bar_file_->FindMessageTypeByName("BarMessage"));
+
+ EXPECT_TRUE(foo_file_->FindMessageTypeByName("BarMessage") == NULL);
+ EXPECT_TRUE(bar_file_->FindMessageTypeByName("FooMessage") == NULL);
+ EXPECT_TRUE(baz_file_->FindMessageTypeByName("FooMessage") == NULL);
+
+ EXPECT_TRUE(foo_file_->FindMessageTypeByName("NoSuchMessage") == NULL);
+ EXPECT_TRUE(foo_file_->FindMessageTypeByName("FooEnum") == NULL);
+}
+
+TEST_F(FileDescriptorTest, FindEnumTypeByName) {
+ EXPECT_EQ(foo_enum_, foo_file_->FindEnumTypeByName("FooEnum"));
+ EXPECT_EQ(bar_enum_, bar_file_->FindEnumTypeByName("BarEnum"));
+
+ EXPECT_TRUE(foo_file_->FindEnumTypeByName("BarEnum") == NULL);
+ EXPECT_TRUE(bar_file_->FindEnumTypeByName("FooEnum") == NULL);
+ EXPECT_TRUE(baz_file_->FindEnumTypeByName("FooEnum") == NULL);
+
+ EXPECT_TRUE(foo_file_->FindEnumTypeByName("NoSuchEnum") == NULL);
+ EXPECT_TRUE(foo_file_->FindEnumTypeByName("FooMessage") == NULL);
+}
+
+TEST_F(FileDescriptorTest, FindEnumValueByName) {
+ EXPECT_EQ(foo_enum_value_, foo_file_->FindEnumValueByName("FOO_ENUM_VALUE"));
+ EXPECT_EQ(bar_enum_value_, bar_file_->FindEnumValueByName("BAR_ENUM_VALUE"));
+
+ EXPECT_TRUE(foo_file_->FindEnumValueByName("BAR_ENUM_VALUE") == NULL);
+ EXPECT_TRUE(bar_file_->FindEnumValueByName("FOO_ENUM_VALUE") == NULL);
+ EXPECT_TRUE(baz_file_->FindEnumValueByName("FOO_ENUM_VALUE") == NULL);
+
+ EXPECT_TRUE(foo_file_->FindEnumValueByName("NO_SUCH_VALUE") == NULL);
+ EXPECT_TRUE(foo_file_->FindEnumValueByName("FooMessage") == NULL);
+}
+
+TEST_F(FileDescriptorTest, FindServiceByName) {
+ EXPECT_EQ(foo_service_, foo_file_->FindServiceByName("FooService"));
+ EXPECT_EQ(bar_service_, bar_file_->FindServiceByName("BarService"));
+
+ EXPECT_TRUE(foo_file_->FindServiceByName("BarService") == NULL);
+ EXPECT_TRUE(bar_file_->FindServiceByName("FooService") == NULL);
+ EXPECT_TRUE(baz_file_->FindServiceByName("FooService") == NULL);
+
+ EXPECT_TRUE(foo_file_->FindServiceByName("NoSuchService") == NULL);
+ EXPECT_TRUE(foo_file_->FindServiceByName("FooMessage") == NULL);
+}
+
+TEST_F(FileDescriptorTest, FindExtensionByName) {
+ EXPECT_EQ(foo_extension_, foo_file_->FindExtensionByName("foo_extension"));
+ EXPECT_EQ(bar_extension_, bar_file_->FindExtensionByName("bar_extension"));
+
+ EXPECT_TRUE(foo_file_->FindExtensionByName("bar_extension") == NULL);
+ EXPECT_TRUE(bar_file_->FindExtensionByName("foo_extension") == NULL);
+ EXPECT_TRUE(baz_file_->FindExtensionByName("foo_extension") == NULL);
+
+ EXPECT_TRUE(foo_file_->FindExtensionByName("no_such_extension") == NULL);
+ EXPECT_TRUE(foo_file_->FindExtensionByName("FooMessage") == NULL);
+}
+
+TEST_F(FileDescriptorTest, FindExtensionByNumber) {
+ EXPECT_EQ(foo_extension_, pool_.FindExtensionByNumber(foo_message_, 1));
+ EXPECT_EQ(bar_extension_, pool_.FindExtensionByNumber(bar_message_, 1));
+
+ EXPECT_TRUE(pool_.FindExtensionByNumber(foo_message_, 2) == NULL);
+}
+
+TEST_F(FileDescriptorTest, BuildAgain) {
+ // Test that if te call BuildFile again on the same input we get the same
+ // FileDescriptor back.
+ FileDescriptorProto file;
+ foo_file_->CopyTo(&file);
+ EXPECT_EQ(foo_file_, pool_.BuildFile(file));
+
+ // But if we change the file then it won't work.
+ file.set_package("some.other.package");
+ EXPECT_TRUE(pool_.BuildFile(file) == NULL);
+}
+
+TEST_F(FileDescriptorTest, BuildAgainWithSyntax) {
+ // Test that if te call BuildFile again on the same input we get the same
+ // FileDescriptor back even if syntax param is specified.
+ FileDescriptorProto proto_syntax2;
+ proto_syntax2.set_name("foo_syntax2");
+ proto_syntax2.set_syntax("proto2");
+
+ const FileDescriptor* proto2_descriptor = pool_.BuildFile(proto_syntax2);
+ EXPECT_TRUE(proto2_descriptor != NULL);
+ EXPECT_EQ(proto2_descriptor, pool_.BuildFile(proto_syntax2));
+
+ FileDescriptorProto implicit_proto2;
+ implicit_proto2.set_name("foo_implicit_syntax2");
+
+ const FileDescriptor* implicit_proto2_descriptor =
+ pool_.BuildFile(implicit_proto2);
+ EXPECT_TRUE(implicit_proto2_descriptor != NULL);
+ // We get the same FileDescriptor back if syntax param is explicitly
+ // specified.
+ implicit_proto2.set_syntax("proto2");
+ EXPECT_EQ(implicit_proto2_descriptor, pool_.BuildFile(implicit_proto2));
+
+ FileDescriptorProto proto_syntax3;
+ proto_syntax3.set_name("foo_syntax3");
+ proto_syntax3.set_syntax("proto3");
+
+ const FileDescriptor* proto3_descriptor = pool_.BuildFile(proto_syntax3);
+ EXPECT_TRUE(proto3_descriptor != NULL);
+ EXPECT_EQ(proto3_descriptor, pool_.BuildFile(proto_syntax3));
+}
+
+TEST_F(FileDescriptorTest, Syntax) {
+ FileDescriptorProto proto;
+ proto.set_name("foo");
+ // Enable the test when we also populate the syntax for proto2.
+#if 0
+ {
+ proto.set_syntax("proto2");
+ DescriptorPool pool;
+ const FileDescriptor* file = pool.BuildFile(proto);
+ EXPECT_TRUE(file != NULL);
+ EXPECT_EQ(FileDescriptor::SYNTAX_PROTO2, file->syntax());
+ FileDescriptorProto other;
+ file->CopyTo(&other);
+ EXPECT_EQ("proto2", other.syntax());
+ }
+#endif
+ {
+ proto.set_syntax("proto3");
+ DescriptorPool pool;
+ const FileDescriptor* file = pool.BuildFile(proto);
+ EXPECT_TRUE(file != NULL);
+ EXPECT_EQ(FileDescriptor::SYNTAX_PROTO3, file->syntax());
+ FileDescriptorProto other;
+ file->CopyTo(&other);
+ EXPECT_EQ("proto3", other.syntax());
+ }
+}
+
+// ===================================================================
+
+// Test simple flat messages and fields.
+class DescriptorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Build descriptors for the following definitions:
+ //
+ // // in "foo.proto"
+ // message TestForeign {}
+ // enum TestEnum {}
+ //
+ // message TestMessage {
+ // required string foo = 1;
+ // optional TestEnum bar = 6;
+ // repeated TestForeign baz = 500000000;
+ // optional group qux = 15 {}
+ // }
+ //
+ // // in "bar.proto"
+ // package corge.grault;
+ // message TestMessage2 {
+ // required string foo = 1;
+ // required string bar = 2;
+ // required string quux = 6;
+ // }
+ //
+ // // in "map.proto"
+ // message TestMessage3 {
+ // map<int32, int32> map_int32_int32 = 1;
+ // }
+ //
+ // // in "json.proto"
+ // message TestMessage4 {
+ // optional int32 field_name1 = 1;
+ // optional int32 fieldName2 = 2;
+ // optional int32 FieldName3 = 3;
+ // optional int32 _field_name4 = 4;
+ // optional int32 FIELD_NAME5 = 5;
+ // optional int32 field_name6 = 6 [json_name = "@type"];
+ // }
+ //
+ // We cheat and use TestForeign as the type for qux rather than create
+ // an actual nested type.
+ //
+ // Since all primitive types (including string) use the same building
+ // code, there's no need to test each one individually.
+ //
+ // TestMessage2 is primarily here to test FindFieldByName and friends.
+ // All messages created from the same DescriptorPool share the same lookup
+ // table, so we need to insure that they don't interfere.
+
+ FileDescriptorProto foo_file;
+ foo_file.set_name("foo.proto");
+ AddMessage(&foo_file, "TestForeign");
+ AddEmptyEnum(&foo_file, "TestEnum");
+
+ DescriptorProto* message = AddMessage(&foo_file, "TestMessage");
+ AddField(message, "foo", 1,
+ FieldDescriptorProto::LABEL_REQUIRED,
+ FieldDescriptorProto::TYPE_STRING);
+ AddField(message, "bar", 6,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_ENUM)
+ ->set_type_name("TestEnum");
+ AddField(message, "baz", 500000000,
+ FieldDescriptorProto::LABEL_REPEATED,
+ FieldDescriptorProto::TYPE_MESSAGE)
+ ->set_type_name("TestForeign");
+ AddField(message, "qux", 15,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_GROUP)
+ ->set_type_name("TestForeign");
+
+ FileDescriptorProto bar_file;
+ bar_file.set_name("bar.proto");
+ bar_file.set_package("corge.grault");
+
+ DescriptorProto* message2 = AddMessage(&bar_file, "TestMessage2");
+ AddField(message2, "foo", 1,
+ FieldDescriptorProto::LABEL_REQUIRED,
+ FieldDescriptorProto::TYPE_STRING);
+ AddField(message2, "bar", 2,
+ FieldDescriptorProto::LABEL_REQUIRED,
+ FieldDescriptorProto::TYPE_STRING);
+ AddField(message2, "quux", 6,
+ FieldDescriptorProto::LABEL_REQUIRED,
+ FieldDescriptorProto::TYPE_STRING);
+
+ FileDescriptorProto map_file;
+ map_file.set_name("map.proto");
+ DescriptorProto* message3 = AddMessage(&map_file, "TestMessage3");
+
+ DescriptorProto* entry = AddNestedMessage(message3, "MapInt32Int32Entry");
+ AddField(entry, "key", 1,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(entry, "value", 2,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ entry->mutable_options()->set_map_entry(true);
+
+ AddField(message3, "map_int32_int32", 1,
+ FieldDescriptorProto::LABEL_REPEATED,
+ FieldDescriptorProto::TYPE_MESSAGE)
+ ->set_type_name("MapInt32Int32Entry");
+
+ FileDescriptorProto json_file;
+ json_file.set_name("json.proto");
+ json_file.set_syntax("proto3");
+ DescriptorProto* message4 = AddMessage(&json_file, "TestMessage4");
+ AddField(message4, "field_name1", 1,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message4, "fieldName2", 2,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message4, "FieldName3", 3,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message4, "_field_name4", 4,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message4, "FIELD_NAME5", 5,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message4, "field_name6", 6,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32)
+ ->set_json_name("@type");
+
+ // Build the descriptors and get the pointers.
+ foo_file_ = pool_.BuildFile(foo_file);
+ ASSERT_TRUE(foo_file_ != NULL);
+
+ bar_file_ = pool_.BuildFile(bar_file);
+ ASSERT_TRUE(bar_file_ != NULL);
+
+ map_file_ = pool_.BuildFile(map_file);
+ ASSERT_TRUE(map_file_ != NULL);
+
+ json_file_ = pool_.BuildFile(json_file);
+ ASSERT_TRUE(json_file_ != NULL);
+
+ ASSERT_EQ(1, foo_file_->enum_type_count());
+ enum_ = foo_file_->enum_type(0);
+
+ ASSERT_EQ(2, foo_file_->message_type_count());
+ foreign_ = foo_file_->message_type(0);
+ message_ = foo_file_->message_type(1);
+
+ ASSERT_EQ(4, message_->field_count());
+ foo_ = message_->field(0);
+ bar_ = message_->field(1);
+ baz_ = message_->field(2);
+ qux_ = message_->field(3);
+
+ ASSERT_EQ(1, bar_file_->message_type_count());
+ message2_ = bar_file_->message_type(0);
+
+ ASSERT_EQ(3, message2_->field_count());
+ foo2_ = message2_->field(0);
+ bar2_ = message2_->field(1);
+ quux2_ = message2_->field(2);
+
+ ASSERT_EQ(1, map_file_->message_type_count());
+ message3_ = map_file_->message_type(0);
+
+ ASSERT_EQ(1, message3_->field_count());
+ map_ = message3_->field(0);
+
+ ASSERT_EQ(1, json_file_->message_type_count());
+ message4_ = json_file_->message_type(0);
+ }
+
+ void CopyWithJsonName(const Descriptor* message, DescriptorProto* proto) {
+ message->CopyTo(proto);
+ message->CopyJsonNameTo(proto);
+ }
+
+ DescriptorPool pool_;
+
+ const FileDescriptor* foo_file_;
+ const FileDescriptor* bar_file_;
+ const FileDescriptor* map_file_;
+ const FileDescriptor* json_file_;
+
+ const Descriptor* message_;
+ const Descriptor* message2_;
+ const Descriptor* message3_;
+ const Descriptor* message4_;
+ const Descriptor* foreign_;
+ const EnumDescriptor* enum_;
+
+ const FieldDescriptor* foo_;
+ const FieldDescriptor* bar_;
+ const FieldDescriptor* baz_;
+ const FieldDescriptor* qux_;
+
+ const FieldDescriptor* foo2_;
+ const FieldDescriptor* bar2_;
+ const FieldDescriptor* quux2_;
+
+ const FieldDescriptor* map_;
+};
+
+TEST_F(DescriptorTest, Name) {
+ EXPECT_EQ("TestMessage", message_->name());
+ EXPECT_EQ("TestMessage", message_->full_name());
+ EXPECT_EQ(foo_file_, message_->file());
+
+ EXPECT_EQ("TestMessage2", message2_->name());
+ EXPECT_EQ("corge.grault.TestMessage2", message2_->full_name());
+ EXPECT_EQ(bar_file_, message2_->file());
+}
+
+TEST_F(DescriptorTest, ContainingType) {
+ EXPECT_TRUE(message_->containing_type() == NULL);
+ EXPECT_TRUE(message2_->containing_type() == NULL);
+}
+
+TEST_F(DescriptorTest, FieldsByIndex) {
+ ASSERT_EQ(4, message_->field_count());
+ EXPECT_EQ(foo_, message_->field(0));
+ EXPECT_EQ(bar_, message_->field(1));
+ EXPECT_EQ(baz_, message_->field(2));
+ EXPECT_EQ(qux_, message_->field(3));
+}
+
+TEST_F(DescriptorTest, FindFieldByName) {
+ // All messages in the same DescriptorPool share a single lookup table for
+ // fields. So, in addition to testing that FindFieldByName finds the fields
+ // of the message, we need to test that it does *not* find the fields of
+ // *other* messages.
+
+ EXPECT_EQ(foo_, message_->FindFieldByName("foo"));
+ EXPECT_EQ(bar_, message_->FindFieldByName("bar"));
+ EXPECT_EQ(baz_, message_->FindFieldByName("baz"));
+ EXPECT_EQ(qux_, message_->FindFieldByName("qux"));
+ EXPECT_TRUE(message_->FindFieldByName("no_such_field") == NULL);
+ EXPECT_TRUE(message_->FindFieldByName("quux") == NULL);
+
+ EXPECT_EQ(foo2_ , message2_->FindFieldByName("foo" ));
+ EXPECT_EQ(bar2_ , message2_->FindFieldByName("bar" ));
+ EXPECT_EQ(quux2_, message2_->FindFieldByName("quux"));
+ EXPECT_TRUE(message2_->FindFieldByName("baz") == NULL);
+ EXPECT_TRUE(message2_->FindFieldByName("qux") == NULL);
+}
+
+TEST_F(DescriptorTest, FindFieldByNumber) {
+ EXPECT_EQ(foo_, message_->FindFieldByNumber(1));
+ EXPECT_EQ(bar_, message_->FindFieldByNumber(6));
+ EXPECT_EQ(baz_, message_->FindFieldByNumber(500000000));
+ EXPECT_EQ(qux_, message_->FindFieldByNumber(15));
+ EXPECT_TRUE(message_->FindFieldByNumber(837592) == NULL);
+ EXPECT_TRUE(message_->FindFieldByNumber(2) == NULL);
+
+ EXPECT_EQ(foo2_ , message2_->FindFieldByNumber(1));
+ EXPECT_EQ(bar2_ , message2_->FindFieldByNumber(2));
+ EXPECT_EQ(quux2_, message2_->FindFieldByNumber(6));
+ EXPECT_TRUE(message2_->FindFieldByNumber(15) == NULL);
+ EXPECT_TRUE(message2_->FindFieldByNumber(500000000) == NULL);
+}
+
+TEST_F(DescriptorTest, FieldName) {
+ EXPECT_EQ("foo", foo_->name());
+ EXPECT_EQ("bar", bar_->name());
+ EXPECT_EQ("baz", baz_->name());
+ EXPECT_EQ("qux", qux_->name());
+}
+
+TEST_F(DescriptorTest, FieldFullName) {
+ EXPECT_EQ("TestMessage.foo", foo_->full_name());
+ EXPECT_EQ("TestMessage.bar", bar_->full_name());
+ EXPECT_EQ("TestMessage.baz", baz_->full_name());
+ EXPECT_EQ("TestMessage.qux", qux_->full_name());
+
+ EXPECT_EQ("corge.grault.TestMessage2.foo", foo2_->full_name());
+ EXPECT_EQ("corge.grault.TestMessage2.bar", bar2_->full_name());
+ EXPECT_EQ("corge.grault.TestMessage2.quux", quux2_->full_name());
+}
+
+TEST_F(DescriptorTest, FieldJsonName) {
+ EXPECT_EQ("fieldName1", message4_->field(0)->json_name());
+ EXPECT_EQ("fieldName2", message4_->field(1)->json_name());
+ EXPECT_EQ("fieldName3", message4_->field(2)->json_name());
+ EXPECT_EQ("fieldName4", message4_->field(3)->json_name());
+ EXPECT_EQ("fIELDNAME5", message4_->field(4)->json_name());
+ EXPECT_EQ("@type", message4_->field(5)->json_name());
+
+ DescriptorProto proto;
+ message4_->CopyTo(&proto);
+ ASSERT_EQ(6, proto.field_size());
+ EXPECT_FALSE(proto.field(0).has_json_name());
+ EXPECT_FALSE(proto.field(1).has_json_name());
+ EXPECT_FALSE(proto.field(2).has_json_name());
+ EXPECT_FALSE(proto.field(3).has_json_name());
+ EXPECT_FALSE(proto.field(4).has_json_name());
+ EXPECT_EQ("@type", proto.field(5).json_name());
+
+ proto.Clear();
+ CopyWithJsonName(message4_, &proto);
+ ASSERT_EQ(6, proto.field_size());
+ EXPECT_EQ("fieldName1", proto.field(0).json_name());
+ EXPECT_EQ("fieldName2", proto.field(1).json_name());
+ EXPECT_EQ("fieldName3", proto.field(2).json_name());
+ EXPECT_EQ("fieldName4", proto.field(3).json_name());
+ EXPECT_EQ("fIELDNAME5", proto.field(4).json_name());
+ EXPECT_EQ("@type", proto.field(5).json_name());
+}
+
+TEST_F(DescriptorTest, FieldFile) {
+ EXPECT_EQ(foo_file_, foo_->file());
+ EXPECT_EQ(foo_file_, bar_->file());
+ EXPECT_EQ(foo_file_, baz_->file());
+ EXPECT_EQ(foo_file_, qux_->file());
+
+ EXPECT_EQ(bar_file_, foo2_->file());
+ EXPECT_EQ(bar_file_, bar2_->file());
+ EXPECT_EQ(bar_file_, quux2_->file());
+}
+
+TEST_F(DescriptorTest, FieldIndex) {
+ EXPECT_EQ(0, foo_->index());
+ EXPECT_EQ(1, bar_->index());
+ EXPECT_EQ(2, baz_->index());
+ EXPECT_EQ(3, qux_->index());
+}
+
+TEST_F(DescriptorTest, FieldNumber) {
+ EXPECT_EQ( 1, foo_->number());
+ EXPECT_EQ( 6, bar_->number());
+ EXPECT_EQ(500000000, baz_->number());
+ EXPECT_EQ( 15, qux_->number());
+}
+
+TEST_F(DescriptorTest, FieldType) {
+ EXPECT_EQ(FieldDescriptor::TYPE_STRING , foo_->type());
+ EXPECT_EQ(FieldDescriptor::TYPE_ENUM , bar_->type());
+ EXPECT_EQ(FieldDescriptor::TYPE_MESSAGE, baz_->type());
+ EXPECT_EQ(FieldDescriptor::TYPE_GROUP , qux_->type());
+}
+
+TEST_F(DescriptorTest, FieldLabel) {
+ EXPECT_EQ(FieldDescriptor::LABEL_REQUIRED, foo_->label());
+ EXPECT_EQ(FieldDescriptor::LABEL_OPTIONAL, bar_->label());
+ EXPECT_EQ(FieldDescriptor::LABEL_REPEATED, baz_->label());
+ EXPECT_EQ(FieldDescriptor::LABEL_OPTIONAL, qux_->label());
+
+ EXPECT_TRUE (foo_->is_required());
+ EXPECT_FALSE(foo_->is_optional());
+ EXPECT_FALSE(foo_->is_repeated());
+
+ EXPECT_FALSE(bar_->is_required());
+ EXPECT_TRUE (bar_->is_optional());
+ EXPECT_FALSE(bar_->is_repeated());
+
+ EXPECT_FALSE(baz_->is_required());
+ EXPECT_FALSE(baz_->is_optional());
+ EXPECT_TRUE (baz_->is_repeated());
+}
+
+TEST_F(DescriptorTest, IsMap) {
+ EXPECT_TRUE(map_->is_map());
+ EXPECT_FALSE(baz_->is_map());
+ EXPECT_TRUE(map_->message_type()->options().map_entry());
+}
+
+TEST_F(DescriptorTest, FieldHasDefault) {
+ EXPECT_FALSE(foo_->has_default_value());
+ EXPECT_FALSE(bar_->has_default_value());
+ EXPECT_FALSE(baz_->has_default_value());
+ EXPECT_FALSE(qux_->has_default_value());
+}
+
+TEST_F(DescriptorTest, FieldContainingType) {
+ EXPECT_EQ(message_, foo_->containing_type());
+ EXPECT_EQ(message_, bar_->containing_type());
+ EXPECT_EQ(message_, baz_->containing_type());
+ EXPECT_EQ(message_, qux_->containing_type());
+
+ EXPECT_EQ(message2_, foo2_ ->containing_type());
+ EXPECT_EQ(message2_, bar2_ ->containing_type());
+ EXPECT_EQ(message2_, quux2_->containing_type());
+}
+
+TEST_F(DescriptorTest, FieldMessageType) {
+ EXPECT_TRUE(foo_->message_type() == NULL);
+ EXPECT_TRUE(bar_->message_type() == NULL);
+
+ EXPECT_EQ(foreign_, baz_->message_type());
+ EXPECT_EQ(foreign_, qux_->message_type());
+}
+
+TEST_F(DescriptorTest, FieldEnumType) {
+ EXPECT_TRUE(foo_->enum_type() == NULL);
+ EXPECT_TRUE(baz_->enum_type() == NULL);
+ EXPECT_TRUE(qux_->enum_type() == NULL);
+
+ EXPECT_EQ(enum_, bar_->enum_type());
+}
+
+// ===================================================================
+
+// Test simple flat messages and fields.
+class OneofDescriptorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Build descriptors for the following definitions:
+ //
+ // package garply;
+ // message TestOneof {
+ // optional int32 a = 1;
+ // oneof foo {
+ // string b = 2;
+ // TestOneof c = 3;
+ // }
+ // oneof bar {
+ // float d = 4;
+ // }
+ // }
+
+ FileDescriptorProto baz_file;
+ baz_file.set_name("baz.proto");
+ baz_file.set_package("garply");
+
+ DescriptorProto* oneof_message = AddMessage(&baz_file, "TestOneof");
+ oneof_message->add_oneof_decl()->set_name("foo");
+ oneof_message->add_oneof_decl()->set_name("bar");
+
+ AddField(oneof_message, "a", 1,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(oneof_message, "b", 2,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_STRING);
+ oneof_message->mutable_field(1)->set_oneof_index(0);
+ AddField(oneof_message, "c", 3,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_MESSAGE);
+ oneof_message->mutable_field(2)->set_oneof_index(0);
+ oneof_message->mutable_field(2)->set_type_name("TestOneof");
+
+ AddField(oneof_message, "d", 4,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_FLOAT);
+ oneof_message->mutable_field(3)->set_oneof_index(1);
+
+ // Build the descriptors and get the pointers.
+ baz_file_ = pool_.BuildFile(baz_file);
+ ASSERT_TRUE(baz_file_ != NULL);
+
+ ASSERT_EQ(1, baz_file_->message_type_count());
+ oneof_message_ = baz_file_->message_type(0);
+
+ ASSERT_EQ(2, oneof_message_->oneof_decl_count());
+ oneof_ = oneof_message_->oneof_decl(0);
+ oneof2_ = oneof_message_->oneof_decl(1);
+
+ ASSERT_EQ(4, oneof_message_->field_count());
+ a_ = oneof_message_->field(0);
+ b_ = oneof_message_->field(1);
+ c_ = oneof_message_->field(2);
+ d_ = oneof_message_->field(3);
+ }
+
+ DescriptorPool pool_;
+
+ const FileDescriptor* baz_file_;
+
+ const Descriptor* oneof_message_;
+
+ const OneofDescriptor* oneof_;
+ const OneofDescriptor* oneof2_;
+ const FieldDescriptor* a_;
+ const FieldDescriptor* b_;
+ const FieldDescriptor* c_;
+ const FieldDescriptor* d_;
+ const FieldDescriptor* e_;
+ const FieldDescriptor* f_;
+};
+
+TEST_F(OneofDescriptorTest, Normal) {
+ EXPECT_EQ("foo", oneof_->name());
+ EXPECT_EQ("garply.TestOneof.foo", oneof_->full_name());
+ EXPECT_EQ(0, oneof_->index());
+ ASSERT_EQ(2, oneof_->field_count());
+ EXPECT_EQ(b_, oneof_->field(0));
+ EXPECT_EQ(c_, oneof_->field(1));
+ EXPECT_TRUE(a_->containing_oneof() == NULL);
+ EXPECT_EQ(oneof_, b_->containing_oneof());
+ EXPECT_EQ(oneof_, c_->containing_oneof());
+}
+
+TEST_F(OneofDescriptorTest, FindByName) {
+ EXPECT_EQ(oneof_, oneof_message_->FindOneofByName("foo"));
+ EXPECT_EQ(oneof2_, oneof_message_->FindOneofByName("bar"));
+ EXPECT_TRUE(oneof_message_->FindOneofByName("no_such_oneof") == NULL);
+}
+
+// ===================================================================
+
+class StylizedFieldNamesTest : public testing::Test {
+ protected:
+ void SetUp() {
+ FileDescriptorProto file;
+ file.set_name("foo.proto");
+
+ AddExtensionRange(AddMessage(&file, "ExtendableMessage"), 1, 1000);
+
+ DescriptorProto* message = AddMessage(&file, "TestMessage");
+ AddField(message, "foo_foo", 1,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message, "FooBar", 2,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message, "fooBaz", 3,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message, "fooFoo", 4, // Camel-case conflict with foo_foo.
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddField(message, "foobar", 5, // Lower-case conflict with FooBar.
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+
+ AddNestedExtension(message, "ExtendableMessage", "bar_foo", 1,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddNestedExtension(message, "ExtendableMessage", "BarBar", 2,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddNestedExtension(message, "ExtendableMessage", "BarBaz", 3,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddNestedExtension(message, "ExtendableMessage", "barFoo", 4, // Conflict
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddNestedExtension(message, "ExtendableMessage", "barbar", 5, // Conflict
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+
+ AddExtension(&file, "ExtendableMessage", "baz_foo", 11,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddExtension(&file, "ExtendableMessage", "BazBar", 12,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddExtension(&file, "ExtendableMessage", "BazBaz", 13,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddExtension(&file, "ExtendableMessage", "bazFoo", 14, // Conflict
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddExtension(&file, "ExtendableMessage", "bazbar", 15, // Conflict
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+
+ file_ = pool_.BuildFile(file);
+ ASSERT_TRUE(file_ != NULL);
+ ASSERT_EQ(2, file_->message_type_count());
+ message_ = file_->message_type(1);
+ ASSERT_EQ("TestMessage", message_->name());
+ ASSERT_EQ(5, message_->field_count());
+ ASSERT_EQ(5, message_->extension_count());
+ ASSERT_EQ(5, file_->extension_count());
+ }
+
+ DescriptorPool pool_;
+ const FileDescriptor* file_;
+ const Descriptor* message_;
+};
+
+TEST_F(StylizedFieldNamesTest, LowercaseName) {
+ EXPECT_EQ("foo_foo", message_->field(0)->lowercase_name());
+ EXPECT_EQ("foobar" , message_->field(1)->lowercase_name());
+ EXPECT_EQ("foobaz" , message_->field(2)->lowercase_name());
+ EXPECT_EQ("foofoo" , message_->field(3)->lowercase_name());
+ EXPECT_EQ("foobar" , message_->field(4)->lowercase_name());
+
+ EXPECT_EQ("bar_foo", message_->extension(0)->lowercase_name());
+ EXPECT_EQ("barbar" , message_->extension(1)->lowercase_name());
+ EXPECT_EQ("barbaz" , message_->extension(2)->lowercase_name());
+ EXPECT_EQ("barfoo" , message_->extension(3)->lowercase_name());
+ EXPECT_EQ("barbar" , message_->extension(4)->lowercase_name());
+
+ EXPECT_EQ("baz_foo", file_->extension(0)->lowercase_name());
+ EXPECT_EQ("bazbar" , file_->extension(1)->lowercase_name());
+ EXPECT_EQ("bazbaz" , file_->extension(2)->lowercase_name());
+ EXPECT_EQ("bazfoo" , file_->extension(3)->lowercase_name());
+ EXPECT_EQ("bazbar" , file_->extension(4)->lowercase_name());
+}
+
+TEST_F(StylizedFieldNamesTest, CamelcaseName) {
+ EXPECT_EQ("fooFoo", message_->field(0)->camelcase_name());
+ EXPECT_EQ("fooBar", message_->field(1)->camelcase_name());
+ EXPECT_EQ("fooBaz", message_->field(2)->camelcase_name());
+ EXPECT_EQ("fooFoo", message_->field(3)->camelcase_name());
+ EXPECT_EQ("foobar", message_->field(4)->camelcase_name());
+
+ EXPECT_EQ("barFoo", message_->extension(0)->camelcase_name());
+ EXPECT_EQ("barBar", message_->extension(1)->camelcase_name());
+ EXPECT_EQ("barBaz", message_->extension(2)->camelcase_name());
+ EXPECT_EQ("barFoo", message_->extension(3)->camelcase_name());
+ EXPECT_EQ("barbar", message_->extension(4)->camelcase_name());
+
+ EXPECT_EQ("bazFoo", file_->extension(0)->camelcase_name());
+ EXPECT_EQ("bazBar", file_->extension(1)->camelcase_name());
+ EXPECT_EQ("bazBaz", file_->extension(2)->camelcase_name());
+ EXPECT_EQ("bazFoo", file_->extension(3)->camelcase_name());
+ EXPECT_EQ("bazbar", file_->extension(4)->camelcase_name());
+}
+
+TEST_F(StylizedFieldNamesTest, FindByLowercaseName) {
+ EXPECT_EQ(message_->field(0),
+ message_->FindFieldByLowercaseName("foo_foo"));
+ EXPECT_EQ(message_->field(1),
+ message_->FindFieldByLowercaseName("foobar"));
+ EXPECT_EQ(message_->field(2),
+ message_->FindFieldByLowercaseName("foobaz"));
+ EXPECT_TRUE(message_->FindFieldByLowercaseName("FooBar") == NULL);
+ EXPECT_TRUE(message_->FindFieldByLowercaseName("fooBaz") == NULL);
+ EXPECT_TRUE(message_->FindFieldByLowercaseName("bar_foo") == NULL);
+ EXPECT_TRUE(message_->FindFieldByLowercaseName("nosuchfield") == NULL);
+
+ EXPECT_EQ(message_->extension(0),
+ message_->FindExtensionByLowercaseName("bar_foo"));
+ EXPECT_EQ(message_->extension(1),
+ message_->FindExtensionByLowercaseName("barbar"));
+ EXPECT_EQ(message_->extension(2),
+ message_->FindExtensionByLowercaseName("barbaz"));
+ EXPECT_TRUE(message_->FindExtensionByLowercaseName("BarBar") == NULL);
+ EXPECT_TRUE(message_->FindExtensionByLowercaseName("barBaz") == NULL);
+ EXPECT_TRUE(message_->FindExtensionByLowercaseName("foo_foo") == NULL);
+ EXPECT_TRUE(message_->FindExtensionByLowercaseName("nosuchfield") == NULL);
+
+ EXPECT_EQ(file_->extension(0),
+ file_->FindExtensionByLowercaseName("baz_foo"));
+ EXPECT_EQ(file_->extension(1),
+ file_->FindExtensionByLowercaseName("bazbar"));
+ EXPECT_EQ(file_->extension(2),
+ file_->FindExtensionByLowercaseName("bazbaz"));
+ EXPECT_TRUE(file_->FindExtensionByLowercaseName("BazBar") == NULL);
+ EXPECT_TRUE(file_->FindExtensionByLowercaseName("bazBaz") == NULL);
+ EXPECT_TRUE(file_->FindExtensionByLowercaseName("nosuchfield") == NULL);
+}
+
+TEST_F(StylizedFieldNamesTest, FindByCamelcaseName) {
+ EXPECT_EQ(message_->field(0),
+ message_->FindFieldByCamelcaseName("fooFoo"));
+ EXPECT_EQ(message_->field(1),
+ message_->FindFieldByCamelcaseName("fooBar"));
+ EXPECT_EQ(message_->field(2),
+ message_->FindFieldByCamelcaseName("fooBaz"));
+ EXPECT_TRUE(message_->FindFieldByCamelcaseName("foo_foo") == NULL);
+ EXPECT_TRUE(message_->FindFieldByCamelcaseName("FooBar") == NULL);
+ EXPECT_TRUE(message_->FindFieldByCamelcaseName("barFoo") == NULL);
+ EXPECT_TRUE(message_->FindFieldByCamelcaseName("nosuchfield") == NULL);
+
+ EXPECT_EQ(message_->extension(0),
+ message_->FindExtensionByCamelcaseName("barFoo"));
+ EXPECT_EQ(message_->extension(1),
+ message_->FindExtensionByCamelcaseName("barBar"));
+ EXPECT_EQ(message_->extension(2),
+ message_->FindExtensionByCamelcaseName("barBaz"));
+ EXPECT_TRUE(message_->FindExtensionByCamelcaseName("bar_foo") == NULL);
+ EXPECT_TRUE(message_->FindExtensionByCamelcaseName("BarBar") == NULL);
+ EXPECT_TRUE(message_->FindExtensionByCamelcaseName("fooFoo") == NULL);
+ EXPECT_TRUE(message_->FindExtensionByCamelcaseName("nosuchfield") == NULL);
+
+ EXPECT_EQ(file_->extension(0),
+ file_->FindExtensionByCamelcaseName("bazFoo"));
+ EXPECT_EQ(file_->extension(1),
+ file_->FindExtensionByCamelcaseName("bazBar"));
+ EXPECT_EQ(file_->extension(2),
+ file_->FindExtensionByCamelcaseName("bazBaz"));
+ EXPECT_TRUE(file_->FindExtensionByCamelcaseName("baz_foo") == NULL);
+ EXPECT_TRUE(file_->FindExtensionByCamelcaseName("BazBar") == NULL);
+ EXPECT_TRUE(file_->FindExtensionByCamelcaseName("nosuchfield") == NULL);
+}
+
+// ===================================================================
+
+// Test enum descriptors.
+class EnumDescriptorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Build descriptors for the following definitions:
+ //
+ // // in "foo.proto"
+ // enum TestEnum {
+ // FOO = 1;
+ // BAR = 2;
+ // }
+ //
+ // // in "bar.proto"
+ // package corge.grault;
+ // enum TestEnum2 {
+ // FOO = 1;
+ // BAZ = 3;
+ // }
+ //
+ // TestEnum2 is primarily here to test FindValueByName and friends.
+ // All enums created from the same DescriptorPool share the same lookup
+ // table, so we need to insure that they don't interfere.
+
+ // TestEnum
+ FileDescriptorProto foo_file;
+ foo_file.set_name("foo.proto");
+
+ EnumDescriptorProto* enum_proto = AddEnum(&foo_file, "TestEnum");
+ AddEnumValue(enum_proto, "FOO", 1);
+ AddEnumValue(enum_proto, "BAR", 2);
+
+ // TestEnum2
+ FileDescriptorProto bar_file;
+ bar_file.set_name("bar.proto");
+ bar_file.set_package("corge.grault");
+
+ EnumDescriptorProto* enum2_proto = AddEnum(&bar_file, "TestEnum2");
+ AddEnumValue(enum2_proto, "FOO", 1);
+ AddEnumValue(enum2_proto, "BAZ", 3);
+
+ // Build the descriptors and get the pointers.
+ foo_file_ = pool_.BuildFile(foo_file);
+ ASSERT_TRUE(foo_file_ != NULL);
+
+ bar_file_ = pool_.BuildFile(bar_file);
+ ASSERT_TRUE(bar_file_ != NULL);
+
+ ASSERT_EQ(1, foo_file_->enum_type_count());
+ enum_ = foo_file_->enum_type(0);
+
+ ASSERT_EQ(2, enum_->value_count());
+ foo_ = enum_->value(0);
+ bar_ = enum_->value(1);
+
+ ASSERT_EQ(1, bar_file_->enum_type_count());
+ enum2_ = bar_file_->enum_type(0);
+
+ ASSERT_EQ(2, enum2_->value_count());
+ foo2_ = enum2_->value(0);
+ baz2_ = enum2_->value(1);
+ }
+
+ DescriptorPool pool_;
+
+ const FileDescriptor* foo_file_;
+ const FileDescriptor* bar_file_;
+
+ const EnumDescriptor* enum_;
+ const EnumDescriptor* enum2_;
+
+ const EnumValueDescriptor* foo_;
+ const EnumValueDescriptor* bar_;
+
+ const EnumValueDescriptor* foo2_;
+ const EnumValueDescriptor* baz2_;
+};
+
+TEST_F(EnumDescriptorTest, Name) {
+ EXPECT_EQ("TestEnum", enum_->name());
+ EXPECT_EQ("TestEnum", enum_->full_name());
+ EXPECT_EQ(foo_file_, enum_->file());
+
+ EXPECT_EQ("TestEnum2", enum2_->name());
+ EXPECT_EQ("corge.grault.TestEnum2", enum2_->full_name());
+ EXPECT_EQ(bar_file_, enum2_->file());
+}
+
+TEST_F(EnumDescriptorTest, ContainingType) {
+ EXPECT_TRUE(enum_->containing_type() == NULL);
+ EXPECT_TRUE(enum2_->containing_type() == NULL);
+}
+
+TEST_F(EnumDescriptorTest, ValuesByIndex) {
+ ASSERT_EQ(2, enum_->value_count());
+ EXPECT_EQ(foo_, enum_->value(0));
+ EXPECT_EQ(bar_, enum_->value(1));
+}
+
+TEST_F(EnumDescriptorTest, FindValueByName) {
+ EXPECT_EQ(foo_ , enum_ ->FindValueByName("FOO"));
+ EXPECT_EQ(bar_ , enum_ ->FindValueByName("BAR"));
+ EXPECT_EQ(foo2_, enum2_->FindValueByName("FOO"));
+ EXPECT_EQ(baz2_, enum2_->FindValueByName("BAZ"));
+
+ EXPECT_TRUE(enum_ ->FindValueByName("NO_SUCH_VALUE") == NULL);
+ EXPECT_TRUE(enum_ ->FindValueByName("BAZ" ) == NULL);
+ EXPECT_TRUE(enum2_->FindValueByName("BAR" ) == NULL);
+}
+
+TEST_F(EnumDescriptorTest, FindValueByNumber) {
+ EXPECT_EQ(foo_ , enum_ ->FindValueByNumber(1));
+ EXPECT_EQ(bar_ , enum_ ->FindValueByNumber(2));
+ EXPECT_EQ(foo2_, enum2_->FindValueByNumber(1));
+ EXPECT_EQ(baz2_, enum2_->FindValueByNumber(3));
+
+ EXPECT_TRUE(enum_ ->FindValueByNumber(416) == NULL);
+ EXPECT_TRUE(enum_ ->FindValueByNumber(3) == NULL);
+ EXPECT_TRUE(enum2_->FindValueByNumber(2) == NULL);
+}
+
+TEST_F(EnumDescriptorTest, ValueName) {
+ EXPECT_EQ("FOO", foo_->name());
+ EXPECT_EQ("BAR", bar_->name());
+}
+
+TEST_F(EnumDescriptorTest, ValueFullName) {
+ EXPECT_EQ("FOO", foo_->full_name());
+ EXPECT_EQ("BAR", bar_->full_name());
+ EXPECT_EQ("corge.grault.FOO", foo2_->full_name());
+ EXPECT_EQ("corge.grault.BAZ", baz2_->full_name());
+}
+
+TEST_F(EnumDescriptorTest, ValueIndex) {
+ EXPECT_EQ(0, foo_->index());
+ EXPECT_EQ(1, bar_->index());
+}
+
+TEST_F(EnumDescriptorTest, ValueNumber) {
+ EXPECT_EQ(1, foo_->number());
+ EXPECT_EQ(2, bar_->number());
+}
+
+TEST_F(EnumDescriptorTest, ValueType) {
+ EXPECT_EQ(enum_ , foo_ ->type());
+ EXPECT_EQ(enum_ , bar_ ->type());
+ EXPECT_EQ(enum2_, foo2_->type());
+ EXPECT_EQ(enum2_, baz2_->type());
+}
+
+// ===================================================================
+
+// Test service descriptors.
+class ServiceDescriptorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Build descriptors for the following messages and service:
+ // // in "foo.proto"
+ // message FooRequest {}
+ // message FooResponse {}
+ // message BarRequest {}
+ // message BarResponse {}
+ // message BazRequest {}
+ // message BazResponse {}
+ //
+ // service TestService {
+ // rpc Foo(FooRequest) returns (FooResponse);
+ // rpc Bar(BarRequest) returns (BarResponse);
+ // }
+ //
+ // // in "bar.proto"
+ // package corge.grault
+ // service TestService2 {
+ // rpc Foo(FooRequest) returns (FooResponse);
+ // rpc Baz(BazRequest) returns (BazResponse);
+ // }
+
+ FileDescriptorProto foo_file;
+ foo_file.set_name("foo.proto");
+
+ AddMessage(&foo_file, "FooRequest");
+ AddMessage(&foo_file, "FooResponse");
+ AddMessage(&foo_file, "BarRequest");
+ AddMessage(&foo_file, "BarResponse");
+ AddMessage(&foo_file, "BazRequest");
+ AddMessage(&foo_file, "BazResponse");
+
+ ServiceDescriptorProto* service = AddService(&foo_file, "TestService");
+ AddMethod(service, "Foo", "FooRequest", "FooResponse");
+ AddMethod(service, "Bar", "BarRequest", "BarResponse");
+
+ FileDescriptorProto bar_file;
+ bar_file.set_name("bar.proto");
+ bar_file.set_package("corge.grault");
+ bar_file.add_dependency("foo.proto");
+
+ ServiceDescriptorProto* service2 = AddService(&bar_file, "TestService2");
+ AddMethod(service2, "Foo", "FooRequest", "FooResponse");
+ AddMethod(service2, "Baz", "BazRequest", "BazResponse");
+
+ // Build the descriptors and get the pointers.
+ foo_file_ = pool_.BuildFile(foo_file);
+ ASSERT_TRUE(foo_file_ != NULL);
+
+ bar_file_ = pool_.BuildFile(bar_file);
+ ASSERT_TRUE(bar_file_ != NULL);
+
+ ASSERT_EQ(6, foo_file_->message_type_count());
+ foo_request_ = foo_file_->message_type(0);
+ foo_response_ = foo_file_->message_type(1);
+ bar_request_ = foo_file_->message_type(2);
+ bar_response_ = foo_file_->message_type(3);
+ baz_request_ = foo_file_->message_type(4);
+ baz_response_ = foo_file_->message_type(5);
+
+ ASSERT_EQ(1, foo_file_->service_count());
+ service_ = foo_file_->service(0);
+
+ ASSERT_EQ(2, service_->method_count());
+ foo_ = service_->method(0);
+ bar_ = service_->method(1);
+
+ ASSERT_EQ(1, bar_file_->service_count());
+ service2_ = bar_file_->service(0);
+
+ ASSERT_EQ(2, service2_->method_count());
+ foo2_ = service2_->method(0);
+ baz2_ = service2_->method(1);
+ }
+
+ DescriptorPool pool_;
+
+ const FileDescriptor* foo_file_;
+ const FileDescriptor* bar_file_;
+
+ const Descriptor* foo_request_;
+ const Descriptor* foo_response_;
+ const Descriptor* bar_request_;
+ const Descriptor* bar_response_;
+ const Descriptor* baz_request_;
+ const Descriptor* baz_response_;
+
+ const ServiceDescriptor* service_;
+ const ServiceDescriptor* service2_;
+
+ const MethodDescriptor* foo_;
+ const MethodDescriptor* bar_;
+
+ const MethodDescriptor* foo2_;
+ const MethodDescriptor* baz2_;
+};
+
+TEST_F(ServiceDescriptorTest, Name) {
+ EXPECT_EQ("TestService", service_->name());
+ EXPECT_EQ("TestService", service_->full_name());
+ EXPECT_EQ(foo_file_, service_->file());
+
+ EXPECT_EQ("TestService2", service2_->name());
+ EXPECT_EQ("corge.grault.TestService2", service2_->full_name());
+ EXPECT_EQ(bar_file_, service2_->file());
+}
+
+TEST_F(ServiceDescriptorTest, MethodsByIndex) {
+ ASSERT_EQ(2, service_->method_count());
+ EXPECT_EQ(foo_, service_->method(0));
+ EXPECT_EQ(bar_, service_->method(1));
+}
+
+TEST_F(ServiceDescriptorTest, FindMethodByName) {
+ EXPECT_EQ(foo_ , service_ ->FindMethodByName("Foo"));
+ EXPECT_EQ(bar_ , service_ ->FindMethodByName("Bar"));
+ EXPECT_EQ(foo2_, service2_->FindMethodByName("Foo"));
+ EXPECT_EQ(baz2_, service2_->FindMethodByName("Baz"));
+
+ EXPECT_TRUE(service_ ->FindMethodByName("NoSuchMethod") == NULL);
+ EXPECT_TRUE(service_ ->FindMethodByName("Baz" ) == NULL);
+ EXPECT_TRUE(service2_->FindMethodByName("Bar" ) == NULL);
+}
+
+TEST_F(ServiceDescriptorTest, MethodName) {
+ EXPECT_EQ("Foo", foo_->name());
+ EXPECT_EQ("Bar", bar_->name());
+}
+
+TEST_F(ServiceDescriptorTest, MethodFullName) {
+ EXPECT_EQ("TestService.Foo", foo_->full_name());
+ EXPECT_EQ("TestService.Bar", bar_->full_name());
+ EXPECT_EQ("corge.grault.TestService2.Foo", foo2_->full_name());
+ EXPECT_EQ("corge.grault.TestService2.Baz", baz2_->full_name());
+}
+
+TEST_F(ServiceDescriptorTest, MethodIndex) {
+ EXPECT_EQ(0, foo_->index());
+ EXPECT_EQ(1, bar_->index());
+}
+
+TEST_F(ServiceDescriptorTest, MethodParent) {
+ EXPECT_EQ(service_, foo_->service());
+ EXPECT_EQ(service_, bar_->service());
+}
+
+TEST_F(ServiceDescriptorTest, MethodInputType) {
+ EXPECT_EQ(foo_request_, foo_->input_type());
+ EXPECT_EQ(bar_request_, bar_->input_type());
+}
+
+TEST_F(ServiceDescriptorTest, MethodOutputType) {
+ EXPECT_EQ(foo_response_, foo_->output_type());
+ EXPECT_EQ(bar_response_, bar_->output_type());
+}
+
+// ===================================================================
+
+// Test nested types.
+class NestedDescriptorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Build descriptors for the following definitions:
+ //
+ // // in "foo.proto"
+ // message TestMessage {
+ // message Foo {}
+ // message Bar {}
+ // enum Baz { A = 1; }
+ // enum Qux { B = 1; }
+ // }
+ //
+ // // in "bar.proto"
+ // package corge.grault;
+ // message TestMessage2 {
+ // message Foo {}
+ // message Baz {}
+ // enum Qux { A = 1; }
+ // enum Quux { C = 1; }
+ // }
+ //
+ // TestMessage2 is primarily here to test FindNestedTypeByName and friends.
+ // All messages created from the same DescriptorPool share the same lookup
+ // table, so we need to insure that they don't interfere.
+ //
+ // We add enum values to the enums in order to test searching for enum
+ // values across a message's scope.
+
+ FileDescriptorProto foo_file;
+ foo_file.set_name("foo.proto");
+
+ DescriptorProto* message = AddMessage(&foo_file, "TestMessage");
+ AddNestedMessage(message, "Foo");
+ AddNestedMessage(message, "Bar");
+ EnumDescriptorProto* baz = AddNestedEnum(message, "Baz");
+ AddEnumValue(baz, "A", 1);
+ EnumDescriptorProto* qux = AddNestedEnum(message, "Qux");
+ AddEnumValue(qux, "B", 1);
+
+ FileDescriptorProto bar_file;
+ bar_file.set_name("bar.proto");
+ bar_file.set_package("corge.grault");
+
+ DescriptorProto* message2 = AddMessage(&bar_file, "TestMessage2");
+ AddNestedMessage(message2, "Foo");
+ AddNestedMessage(message2, "Baz");
+ EnumDescriptorProto* qux2 = AddNestedEnum(message2, "Qux");
+ AddEnumValue(qux2, "A", 1);
+ EnumDescriptorProto* quux2 = AddNestedEnum(message2, "Quux");
+ AddEnumValue(quux2, "C", 1);
+
+ // Build the descriptors and get the pointers.
+ foo_file_ = pool_.BuildFile(foo_file);
+ ASSERT_TRUE(foo_file_ != NULL);
+
+ bar_file_ = pool_.BuildFile(bar_file);
+ ASSERT_TRUE(bar_file_ != NULL);
+
+ ASSERT_EQ(1, foo_file_->message_type_count());
+ message_ = foo_file_->message_type(0);
+
+ ASSERT_EQ(2, message_->nested_type_count());
+ foo_ = message_->nested_type(0);
+ bar_ = message_->nested_type(1);
+
+ ASSERT_EQ(2, message_->enum_type_count());
+ baz_ = message_->enum_type(0);
+ qux_ = message_->enum_type(1);
+
+ ASSERT_EQ(1, baz_->value_count());
+ a_ = baz_->value(0);
+ ASSERT_EQ(1, qux_->value_count());
+ b_ = qux_->value(0);
+
+ ASSERT_EQ(1, bar_file_->message_type_count());
+ message2_ = bar_file_->message_type(0);
+
+ ASSERT_EQ(2, message2_->nested_type_count());
+ foo2_ = message2_->nested_type(0);
+ baz2_ = message2_->nested_type(1);
+
+ ASSERT_EQ(2, message2_->enum_type_count());
+ qux2_ = message2_->enum_type(0);
+ quux2_ = message2_->enum_type(1);
+
+ ASSERT_EQ(1, qux2_->value_count());
+ a2_ = qux2_->value(0);
+ ASSERT_EQ(1, quux2_->value_count());
+ c2_ = quux2_->value(0);
+ }
+
+ DescriptorPool pool_;
+
+ const FileDescriptor* foo_file_;
+ const FileDescriptor* bar_file_;
+
+ const Descriptor* message_;
+ const Descriptor* message2_;
+
+ const Descriptor* foo_;
+ const Descriptor* bar_;
+ const EnumDescriptor* baz_;
+ const EnumDescriptor* qux_;
+ const EnumValueDescriptor* a_;
+ const EnumValueDescriptor* b_;
+
+ const Descriptor* foo2_;
+ const Descriptor* baz2_;
+ const EnumDescriptor* qux2_;
+ const EnumDescriptor* quux2_;
+ const EnumValueDescriptor* a2_;
+ const EnumValueDescriptor* c2_;
+};
+
+TEST_F(NestedDescriptorTest, MessageName) {
+ EXPECT_EQ("Foo", foo_ ->name());
+ EXPECT_EQ("Bar", bar_ ->name());
+ EXPECT_EQ("Foo", foo2_->name());
+ EXPECT_EQ("Baz", baz2_->name());
+
+ EXPECT_EQ("TestMessage.Foo", foo_->full_name());
+ EXPECT_EQ("TestMessage.Bar", bar_->full_name());
+ EXPECT_EQ("corge.grault.TestMessage2.Foo", foo2_->full_name());
+ EXPECT_EQ("corge.grault.TestMessage2.Baz", baz2_->full_name());
+}
+
+TEST_F(NestedDescriptorTest, MessageContainingType) {
+ EXPECT_EQ(message_ , foo_ ->containing_type());
+ EXPECT_EQ(message_ , bar_ ->containing_type());
+ EXPECT_EQ(message2_, foo2_->containing_type());
+ EXPECT_EQ(message2_, baz2_->containing_type());
+}
+
+TEST_F(NestedDescriptorTest, NestedMessagesByIndex) {
+ ASSERT_EQ(2, message_->nested_type_count());
+ EXPECT_EQ(foo_, message_->nested_type(0));
+ EXPECT_EQ(bar_, message_->nested_type(1));
+}
+
+TEST_F(NestedDescriptorTest, FindFieldByNameDoesntFindNestedTypes) {
+ EXPECT_TRUE(message_->FindFieldByName("Foo") == NULL);
+ EXPECT_TRUE(message_->FindFieldByName("Qux") == NULL);
+ EXPECT_TRUE(message_->FindExtensionByName("Foo") == NULL);
+ EXPECT_TRUE(message_->FindExtensionByName("Qux") == NULL);
+}
+
+TEST_F(NestedDescriptorTest, FindNestedTypeByName) {
+ EXPECT_EQ(foo_ , message_ ->FindNestedTypeByName("Foo"));
+ EXPECT_EQ(bar_ , message_ ->FindNestedTypeByName("Bar"));
+ EXPECT_EQ(foo2_, message2_->FindNestedTypeByName("Foo"));
+ EXPECT_EQ(baz2_, message2_->FindNestedTypeByName("Baz"));
+
+ EXPECT_TRUE(message_ ->FindNestedTypeByName("NoSuchType") == NULL);
+ EXPECT_TRUE(message_ ->FindNestedTypeByName("Baz" ) == NULL);
+ EXPECT_TRUE(message2_->FindNestedTypeByName("Bar" ) == NULL);
+
+ EXPECT_TRUE(message_->FindNestedTypeByName("Qux") == NULL);
+}
+
+TEST_F(NestedDescriptorTest, EnumName) {
+ EXPECT_EQ("Baz" , baz_ ->name());
+ EXPECT_EQ("Qux" , qux_ ->name());
+ EXPECT_EQ("Qux" , qux2_->name());
+ EXPECT_EQ("Quux", quux2_->name());
+
+ EXPECT_EQ("TestMessage.Baz", baz_->full_name());
+ EXPECT_EQ("TestMessage.Qux", qux_->full_name());
+ EXPECT_EQ("corge.grault.TestMessage2.Qux" , qux2_ ->full_name());
+ EXPECT_EQ("corge.grault.TestMessage2.Quux", quux2_->full_name());
+}
+
+TEST_F(NestedDescriptorTest, EnumContainingType) {
+ EXPECT_EQ(message_ , baz_ ->containing_type());
+ EXPECT_EQ(message_ , qux_ ->containing_type());
+ EXPECT_EQ(message2_, qux2_ ->containing_type());
+ EXPECT_EQ(message2_, quux2_->containing_type());
+}
+
+TEST_F(NestedDescriptorTest, NestedEnumsByIndex) {
+ ASSERT_EQ(2, message_->nested_type_count());
+ EXPECT_EQ(foo_, message_->nested_type(0));
+ EXPECT_EQ(bar_, message_->nested_type(1));
+}
+
+TEST_F(NestedDescriptorTest, FindEnumTypeByName) {
+ EXPECT_EQ(baz_ , message_ ->FindEnumTypeByName("Baz" ));
+ EXPECT_EQ(qux_ , message_ ->FindEnumTypeByName("Qux" ));
+ EXPECT_EQ(qux2_ , message2_->FindEnumTypeByName("Qux" ));
+ EXPECT_EQ(quux2_, message2_->FindEnumTypeByName("Quux"));
+
+ EXPECT_TRUE(message_ ->FindEnumTypeByName("NoSuchType") == NULL);
+ EXPECT_TRUE(message_ ->FindEnumTypeByName("Quux" ) == NULL);
+ EXPECT_TRUE(message2_->FindEnumTypeByName("Baz" ) == NULL);
+
+ EXPECT_TRUE(message_->FindEnumTypeByName("Foo") == NULL);
+}
+
+TEST_F(NestedDescriptorTest, FindEnumValueByName) {
+ EXPECT_EQ(a_ , message_ ->FindEnumValueByName("A"));
+ EXPECT_EQ(b_ , message_ ->FindEnumValueByName("B"));
+ EXPECT_EQ(a2_, message2_->FindEnumValueByName("A"));
+ EXPECT_EQ(c2_, message2_->FindEnumValueByName("C"));
+
+ EXPECT_TRUE(message_ ->FindEnumValueByName("NO_SUCH_VALUE") == NULL);
+ EXPECT_TRUE(message_ ->FindEnumValueByName("C" ) == NULL);
+ EXPECT_TRUE(message2_->FindEnumValueByName("B" ) == NULL);
+
+ EXPECT_TRUE(message_->FindEnumValueByName("Foo") == NULL);
+}
+
+// ===================================================================
+
+// Test extensions.
+class ExtensionDescriptorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Build descriptors for the following definitions:
+ //
+ // enum Baz {}
+ // message Qux {}
+ //
+ // message Foo {
+ // extensions 10 to 19;
+ // extensions 30 to 39;
+ // }
+ // extends Foo with optional int32 foo_int32 = 10;
+ // extends Foo with repeated TestEnum foo_enum = 19;
+ // message Bar {
+ // extends Foo with optional Qux foo_message = 30;
+ // // (using Qux as the group type)
+ // extends Foo with repeated group foo_group = 39;
+ // }
+
+ FileDescriptorProto foo_file;
+ foo_file.set_name("foo.proto");
+
+ AddEmptyEnum(&foo_file, "Baz");
+ AddMessage(&foo_file, "Qux");
+
+ DescriptorProto* foo = AddMessage(&foo_file, "Foo");
+ AddExtensionRange(foo, 10, 20);
+ AddExtensionRange(foo, 30, 40);
+
+ AddExtension(&foo_file, "Foo", "foo_int32", 10,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ AddExtension(&foo_file, "Foo", "foo_enum", 19,
+ FieldDescriptorProto::LABEL_REPEATED,
+ FieldDescriptorProto::TYPE_ENUM)
+ ->set_type_name("Baz");
+
+ DescriptorProto* bar = AddMessage(&foo_file, "Bar");
+ AddNestedExtension(bar, "Foo", "foo_message", 30,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_MESSAGE)
+ ->set_type_name("Qux");
+ AddNestedExtension(bar, "Foo", "foo_group", 39,
+ FieldDescriptorProto::LABEL_REPEATED,
+ FieldDescriptorProto::TYPE_GROUP)
+ ->set_type_name("Qux");
+
+ // Build the descriptors and get the pointers.
+ foo_file_ = pool_.BuildFile(foo_file);
+ ASSERT_TRUE(foo_file_ != NULL);
+
+ ASSERT_EQ(1, foo_file_->enum_type_count());
+ baz_ = foo_file_->enum_type(0);
+
+ ASSERT_EQ(3, foo_file_->message_type_count());
+ qux_ = foo_file_->message_type(0);
+ foo_ = foo_file_->message_type(1);
+ bar_ = foo_file_->message_type(2);
+ }
+
+ DescriptorPool pool_;
+
+ const FileDescriptor* foo_file_;
+
+ const Descriptor* foo_;
+ const Descriptor* bar_;
+ const EnumDescriptor* baz_;
+ const Descriptor* qux_;
+};
+
+TEST_F(ExtensionDescriptorTest, ExtensionRanges) {
+ EXPECT_EQ(0, bar_->extension_range_count());
+ ASSERT_EQ(2, foo_->extension_range_count());
+
+ EXPECT_EQ(10, foo_->extension_range(0)->start);
+ EXPECT_EQ(30, foo_->extension_range(1)->start);
+
+ EXPECT_EQ(20, foo_->extension_range(0)->end);
+ EXPECT_EQ(40, foo_->extension_range(1)->end);
+};
+
+TEST_F(ExtensionDescriptorTest, Extensions) {
+ EXPECT_EQ(0, foo_->extension_count());
+ ASSERT_EQ(2, foo_file_->extension_count());
+ ASSERT_EQ(2, bar_->extension_count());
+
+ EXPECT_TRUE(foo_file_->extension(0)->is_extension());
+ EXPECT_TRUE(foo_file_->extension(1)->is_extension());
+ EXPECT_TRUE(bar_->extension(0)->is_extension());
+ EXPECT_TRUE(bar_->extension(1)->is_extension());
+
+ EXPECT_EQ("foo_int32" , foo_file_->extension(0)->name());
+ EXPECT_EQ("foo_enum" , foo_file_->extension(1)->name());
+ EXPECT_EQ("foo_message", bar_->extension(0)->name());
+ EXPECT_EQ("foo_group" , bar_->extension(1)->name());
+
+ EXPECT_EQ(10, foo_file_->extension(0)->number());
+ EXPECT_EQ(19, foo_file_->extension(1)->number());
+ EXPECT_EQ(30, bar_->extension(0)->number());
+ EXPECT_EQ(39, bar_->extension(1)->number());
+
+ EXPECT_EQ(FieldDescriptor::TYPE_INT32 , foo_file_->extension(0)->type());
+ EXPECT_EQ(FieldDescriptor::TYPE_ENUM , foo_file_->extension(1)->type());
+ EXPECT_EQ(FieldDescriptor::TYPE_MESSAGE, bar_->extension(0)->type());
+ EXPECT_EQ(FieldDescriptor::TYPE_GROUP , bar_->extension(1)->type());
+
+ EXPECT_EQ(baz_, foo_file_->extension(1)->enum_type());
+ EXPECT_EQ(qux_, bar_->extension(0)->message_type());
+ EXPECT_EQ(qux_, bar_->extension(1)->message_type());
+
+ EXPECT_EQ(FieldDescriptor::LABEL_OPTIONAL, foo_file_->extension(0)->label());
+ EXPECT_EQ(FieldDescriptor::LABEL_REPEATED, foo_file_->extension(1)->label());
+ EXPECT_EQ(FieldDescriptor::LABEL_OPTIONAL, bar_->extension(0)->label());
+ EXPECT_EQ(FieldDescriptor::LABEL_REPEATED, bar_->extension(1)->label());
+
+ EXPECT_EQ(foo_, foo_file_->extension(0)->containing_type());
+ EXPECT_EQ(foo_, foo_file_->extension(1)->containing_type());
+ EXPECT_EQ(foo_, bar_->extension(0)->containing_type());
+ EXPECT_EQ(foo_, bar_->extension(1)->containing_type());
+
+ EXPECT_TRUE(foo_file_->extension(0)->extension_scope() == NULL);
+ EXPECT_TRUE(foo_file_->extension(1)->extension_scope() == NULL);
+ EXPECT_EQ(bar_, bar_->extension(0)->extension_scope());
+ EXPECT_EQ(bar_, bar_->extension(1)->extension_scope());
+};
+
+TEST_F(ExtensionDescriptorTest, IsExtensionNumber) {
+ EXPECT_FALSE(foo_->IsExtensionNumber( 9));
+ EXPECT_TRUE (foo_->IsExtensionNumber(10));
+ EXPECT_TRUE (foo_->IsExtensionNumber(19));
+ EXPECT_FALSE(foo_->IsExtensionNumber(20));
+ EXPECT_FALSE(foo_->IsExtensionNumber(29));
+ EXPECT_TRUE (foo_->IsExtensionNumber(30));
+ EXPECT_TRUE (foo_->IsExtensionNumber(39));
+ EXPECT_FALSE(foo_->IsExtensionNumber(40));
+}
+
+TEST_F(ExtensionDescriptorTest, FindExtensionByName) {
+ // Note that FileDescriptor::FindExtensionByName() is tested by
+ // FileDescriptorTest.
+ ASSERT_EQ(2, bar_->extension_count());
+
+ EXPECT_EQ(bar_->extension(0), bar_->FindExtensionByName("foo_message"));
+ EXPECT_EQ(bar_->extension(1), bar_->FindExtensionByName("foo_group" ));
+
+ EXPECT_TRUE(bar_->FindExtensionByName("no_such_extension") == NULL);
+ EXPECT_TRUE(foo_->FindExtensionByName("foo_int32") == NULL);
+ EXPECT_TRUE(foo_->FindExtensionByName("foo_message") == NULL);
+}
+
+TEST_F(ExtensionDescriptorTest, FindAllExtensions) {
+ vector<const FieldDescriptor*> extensions;
+ pool_.FindAllExtensions(foo_, &extensions);
+ ASSERT_EQ(4, extensions.size());
+ EXPECT_EQ(10, extensions[0]->number());
+ EXPECT_EQ(19, extensions[1]->number());
+ EXPECT_EQ(30, extensions[2]->number());
+ EXPECT_EQ(39, extensions[3]->number());
+}
+
+TEST_F(ExtensionDescriptorTest, DuplicateFieldNumber) {
+ DescriptorPool pool;
+ FileDescriptorProto file_proto;
+ // Add "google/protobuf/descriptor.proto".
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+ // Add "foo.proto":
+ // import "google/protobuf/descriptor.proto";
+ // extend google.protobuf.FieldOptions {
+ // optional int32 option1 = 1000;
+ // }
+ file_proto.Clear();
+ file_proto.set_name("foo.proto");
+ file_proto.add_dependency("google/protobuf/descriptor.proto");
+ AddExtension(&file_proto, "google.protobuf.FieldOptions", "option1", 1000,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+ // Add "bar.proto":
+ // import "google/protobuf/descriptor.proto";
+ // extend google.protobuf.FieldOptions {
+ // optional int32 option2 = 1000;
+ // }
+ file_proto.Clear();
+ file_proto.set_name("bar.proto");
+ file_proto.add_dependency("google/protobuf/descriptor.proto");
+ AddExtension(&file_proto, "google.protobuf.FieldOptions", "option2", 1000,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ // Currently we only generate a warning for conflicting extension numbers.
+ // TODO(xiaofeng): Change it to an error.
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+}
+
+// ===================================================================
+
+// Test reserved fields.
+class ReservedDescriptorTest : public testing::Test {
+ protected:
+ virtual void SetUp() {
+ // Build descriptors for the following definitions:
+ //
+ // message Foo {
+ // reserved 2, 9 to 11, 15;
+ // reserved "foo", "bar";
+ // }
+
+ FileDescriptorProto foo_file;
+ foo_file.set_name("foo.proto");
+
+ DescriptorProto* foo = AddMessage(&foo_file, "Foo");
+ AddReservedRange(foo, 2, 3);
+ AddReservedRange(foo, 9, 12);
+ AddReservedRange(foo, 15, 16);
+
+ foo->add_reserved_name("foo");
+ foo->add_reserved_name("bar");
+
+ // Build the descriptors and get the pointers.
+ foo_file_ = pool_.BuildFile(foo_file);
+ ASSERT_TRUE(foo_file_ != NULL);
+
+ ASSERT_EQ(1, foo_file_->message_type_count());
+ foo_ = foo_file_->message_type(0);
+ }
+
+ DescriptorPool pool_;
+ const FileDescriptor* foo_file_;
+ const Descriptor* foo_;
+};
+
+TEST_F(ReservedDescriptorTest, ReservedRanges) {
+ ASSERT_EQ(3, foo_->reserved_range_count());
+
+ EXPECT_EQ(2, foo_->reserved_range(0)->start);
+ EXPECT_EQ(3, foo_->reserved_range(0)->end);
+
+ EXPECT_EQ(9, foo_->reserved_range(1)->start);
+ EXPECT_EQ(12, foo_->reserved_range(1)->end);
+
+ EXPECT_EQ(15, foo_->reserved_range(2)->start);
+ EXPECT_EQ(16, foo_->reserved_range(2)->end);
+};
+
+TEST_F(ReservedDescriptorTest, IsReservedNumber) {
+ EXPECT_FALSE(foo_->IsReservedNumber(1));
+ EXPECT_TRUE (foo_->IsReservedNumber(2));
+ EXPECT_FALSE(foo_->IsReservedNumber(3));
+ EXPECT_FALSE(foo_->IsReservedNumber(8));
+ EXPECT_TRUE (foo_->IsReservedNumber(9));
+ EXPECT_TRUE (foo_->IsReservedNumber(10));
+ EXPECT_TRUE (foo_->IsReservedNumber(11));
+ EXPECT_FALSE(foo_->IsReservedNumber(12));
+ EXPECT_FALSE(foo_->IsReservedNumber(13));
+ EXPECT_FALSE(foo_->IsReservedNumber(14));
+ EXPECT_TRUE (foo_->IsReservedNumber(15));
+ EXPECT_FALSE(foo_->IsReservedNumber(16));
+};
+
+TEST_F(ReservedDescriptorTest, ReservedNames) {
+ ASSERT_EQ(2, foo_->reserved_name_count());
+
+ EXPECT_EQ("foo", foo_->reserved_name(0));
+ EXPECT_EQ("bar", foo_->reserved_name(1));
+};
+
+TEST_F(ReservedDescriptorTest, IsReservedName) {
+ EXPECT_TRUE (foo_->IsReservedName("foo"));
+ EXPECT_TRUE (foo_->IsReservedName("bar"));
+ EXPECT_FALSE(foo_->IsReservedName("baz"));
+};
+
+// ===================================================================
+
+class MiscTest : public testing::Test {
+ protected:
+ // 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");
+
+ DescriptorProto* message = AddMessage(&file_proto, "TestMessage");
+ FieldDescriptorProto* field =
+ AddField(message, "foo", 1, FieldDescriptorProto::LABEL_OPTIONAL,
+ static_cast<FieldDescriptorProto::Type>(static_cast<int>(type)));
+
+ if (type == FieldDescriptor::TYPE_MESSAGE ||
+ type == FieldDescriptor::TYPE_GROUP) {
+ field->set_type_name("TestMessage");
+ } else if (type == FieldDescriptor::TYPE_ENUM) {
+ field->set_type_name("DummyEnum");
+ }
+
+ // Build the descriptors and get the pointers.
+ 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);
+ } else {
+ 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() : "";
+ }
+
+ const Descriptor* GetMessageDescriptorForFieldType(
+ FieldDescriptor::Type type) {
+ const FieldDescriptor* field = GetFieldDescriptorOfType(type);
+ return field != NULL ? field->message_type() : NULL;
+ }
+
+ const EnumDescriptor* GetEnumDescriptorForFieldType(
+ FieldDescriptor::Type type) {
+ const FieldDescriptor* field = GetFieldDescriptorOfType(type);
+ return field != NULL ? field->enum_type() : NULL;
+ }
+
+ google::protobuf::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, StaticTypeNames) {
+ // Test that correct type names are returned.
+
+ typedef FieldDescriptor FD; // avoid ugly line wrapping
+
+ EXPECT_STREQ("double" , FD::TypeName(FD::TYPE_DOUBLE ));
+ EXPECT_STREQ("float" , FD::TypeName(FD::TYPE_FLOAT ));
+ EXPECT_STREQ("int64" , FD::TypeName(FD::TYPE_INT64 ));
+ EXPECT_STREQ("uint64" , FD::TypeName(FD::TYPE_UINT64 ));
+ EXPECT_STREQ("int32" , FD::TypeName(FD::TYPE_INT32 ));
+ EXPECT_STREQ("fixed64" , FD::TypeName(FD::TYPE_FIXED64 ));
+ EXPECT_STREQ("fixed32" , FD::TypeName(FD::TYPE_FIXED32 ));
+ EXPECT_STREQ("bool" , FD::TypeName(FD::TYPE_BOOL ));
+ EXPECT_STREQ("string" , FD::TypeName(FD::TYPE_STRING ));
+ EXPECT_STREQ("group" , FD::TypeName(FD::TYPE_GROUP ));
+ EXPECT_STREQ("message" , FD::TypeName(FD::TYPE_MESSAGE ));
+ EXPECT_STREQ("bytes" , FD::TypeName(FD::TYPE_BYTES ));
+ EXPECT_STREQ("uint32" , FD::TypeName(FD::TYPE_UINT32 ));
+ EXPECT_STREQ("enum" , FD::TypeName(FD::TYPE_ENUM ));
+ EXPECT_STREQ("sfixed32", FD::TypeName(FD::TYPE_SFIXED32));
+ EXPECT_STREQ("sfixed64", FD::TypeName(FD::TYPE_SFIXED64));
+ EXPECT_STREQ("sint32" , FD::TypeName(FD::TYPE_SINT32 ));
+ EXPECT_STREQ("sint64" , FD::TypeName(FD::TYPE_SINT64 ));
+}
+
+TEST_F(MiscTest, CppTypes) {
+ // Test that CPP types are assigned correctly.
+
+ typedef FieldDescriptor FD; // avoid ugly line wrapping
+
+ EXPECT_EQ(FD::CPPTYPE_DOUBLE , GetCppTypeForFieldType(FD::TYPE_DOUBLE ));
+ EXPECT_EQ(FD::CPPTYPE_FLOAT , GetCppTypeForFieldType(FD::TYPE_FLOAT ));
+ EXPECT_EQ(FD::CPPTYPE_INT64 , GetCppTypeForFieldType(FD::TYPE_INT64 ));
+ EXPECT_EQ(FD::CPPTYPE_UINT64 , GetCppTypeForFieldType(FD::TYPE_UINT64 ));
+ EXPECT_EQ(FD::CPPTYPE_INT32 , GetCppTypeForFieldType(FD::TYPE_INT32 ));
+ EXPECT_EQ(FD::CPPTYPE_UINT64 , GetCppTypeForFieldType(FD::TYPE_FIXED64 ));
+ EXPECT_EQ(FD::CPPTYPE_UINT32 , GetCppTypeForFieldType(FD::TYPE_FIXED32 ));
+ EXPECT_EQ(FD::CPPTYPE_BOOL , GetCppTypeForFieldType(FD::TYPE_BOOL ));
+ EXPECT_EQ(FD::CPPTYPE_STRING , GetCppTypeForFieldType(FD::TYPE_STRING ));
+ EXPECT_EQ(FD::CPPTYPE_MESSAGE, GetCppTypeForFieldType(FD::TYPE_GROUP ));
+ EXPECT_EQ(FD::CPPTYPE_MESSAGE, GetCppTypeForFieldType(FD::TYPE_MESSAGE ));
+ EXPECT_EQ(FD::CPPTYPE_STRING , GetCppTypeForFieldType(FD::TYPE_BYTES ));
+ EXPECT_EQ(FD::CPPTYPE_UINT32 , GetCppTypeForFieldType(FD::TYPE_UINT32 ));
+ EXPECT_EQ(FD::CPPTYPE_ENUM , GetCppTypeForFieldType(FD::TYPE_ENUM ));
+ EXPECT_EQ(FD::CPPTYPE_INT32 , GetCppTypeForFieldType(FD::TYPE_SFIXED32));
+ EXPECT_EQ(FD::CPPTYPE_INT64 , GetCppTypeForFieldType(FD::TYPE_SFIXED64));
+ EXPECT_EQ(FD::CPPTYPE_INT32 , GetCppTypeForFieldType(FD::TYPE_SINT32 ));
+ 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, StaticCppTypeNames) {
+ // Test that correct CPP type names are returned.
+
+ typedef FieldDescriptor FD; // avoid ugly line wrapping
+
+ EXPECT_STREQ("int32" , FD::CppTypeName(FD::CPPTYPE_INT32 ));
+ EXPECT_STREQ("int64" , FD::CppTypeName(FD::CPPTYPE_INT64 ));
+ EXPECT_STREQ("uint32" , FD::CppTypeName(FD::CPPTYPE_UINT32 ));
+ EXPECT_STREQ("uint64" , FD::CppTypeName(FD::CPPTYPE_UINT64 ));
+ EXPECT_STREQ("double" , FD::CppTypeName(FD::CPPTYPE_DOUBLE ));
+ EXPECT_STREQ("float" , FD::CppTypeName(FD::CPPTYPE_FLOAT ));
+ EXPECT_STREQ("bool" , FD::CppTypeName(FD::CPPTYPE_BOOL ));
+ EXPECT_STREQ("enum" , FD::CppTypeName(FD::CPPTYPE_ENUM ));
+ EXPECT_STREQ("string" , FD::CppTypeName(FD::CPPTYPE_STRING ));
+ EXPECT_STREQ("message", FD::CppTypeName(FD::CPPTYPE_MESSAGE));
+}
+
+TEST_F(MiscTest, MessageType) {
+ // Test that message_type() is NULL for non-aggregate fields
+
+ typedef FieldDescriptor FD; // avoid ugly line wrapping
+
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_DOUBLE ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_FLOAT ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_INT64 ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_UINT64 ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_INT32 ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_FIXED64 ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_FIXED32 ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_BOOL ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_STRING ));
+ EXPECT_TRUE(NULL != GetMessageDescriptorForFieldType(FD::TYPE_GROUP ));
+ EXPECT_TRUE(NULL != GetMessageDescriptorForFieldType(FD::TYPE_MESSAGE ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_BYTES ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_UINT32 ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_ENUM ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_SFIXED32));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_SFIXED64));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_SINT32 ));
+ EXPECT_TRUE(NULL == GetMessageDescriptorForFieldType(FD::TYPE_SINT64 ));
+}
+
+TEST_F(MiscTest, EnumType) {
+ // Test that enum_type() is NULL for non-enum fields
+
+ typedef FieldDescriptor FD; // avoid ugly line wrapping
+
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_DOUBLE ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_FLOAT ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_INT64 ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_UINT64 ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_INT32 ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_FIXED64 ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_FIXED32 ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_BOOL ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_STRING ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_GROUP ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_MESSAGE ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_BYTES ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_UINT32 ));
+ EXPECT_TRUE(NULL != GetEnumDescriptorForFieldType(FD::TYPE_ENUM ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_SFIXED32));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_SFIXED64));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_SINT32 ));
+ EXPECT_TRUE(NULL == GetEnumDescriptorForFieldType(FD::TYPE_SINT64 ));
+}
+
+
+TEST_F(MiscTest, DefaultValues) {
+ // Test that setting default values works.
+ FileDescriptorProto file_proto;
+ file_proto.set_name("foo.proto");
+
+ EnumDescriptorProto* enum_type_proto = AddEnum(&file_proto, "DummyEnum");
+ AddEnumValue(enum_type_proto, "A", 1);
+ AddEnumValue(enum_type_proto, "B", 2);
+
+ DescriptorProto* message_proto = AddMessage(&file_proto, "TestMessage");
+
+ typedef FieldDescriptorProto FD; // avoid ugly line wrapping
+ const FD::Label label = FD::LABEL_OPTIONAL;
+
+ // Create fields of every CPP type with default values.
+ AddField(message_proto, "int32" , 1, label, FD::TYPE_INT32 )
+ ->set_default_value("-1");
+ AddField(message_proto, "int64" , 2, label, FD::TYPE_INT64 )
+ ->set_default_value("-1000000000000");
+ AddField(message_proto, "uint32", 3, label, FD::TYPE_UINT32)
+ ->set_default_value("42");
+ AddField(message_proto, "uint64", 4, label, FD::TYPE_UINT64)
+ ->set_default_value("2000000000000");
+ AddField(message_proto, "float" , 5, label, FD::TYPE_FLOAT )
+ ->set_default_value("4.5");
+ AddField(message_proto, "double", 6, label, FD::TYPE_DOUBLE)
+ ->set_default_value("10e100");
+ AddField(message_proto, "bool" , 7, label, FD::TYPE_BOOL )
+ ->set_default_value("true");
+ AddField(message_proto, "string", 8, label, FD::TYPE_STRING)
+ ->set_default_value("hello");
+ AddField(message_proto, "data" , 9, label, FD::TYPE_BYTES )
+ ->set_default_value("\\001\\002\\003");
+
+ FieldDescriptorProto* enum_field =
+ AddField(message_proto, "enum", 10, label, FD::TYPE_ENUM);
+ enum_field->set_type_name("DummyEnum");
+ enum_field->set_default_value("B");
+
+ // Strings are allowed to have empty defaults. (At one point, due to
+ // a bug, empty defaults for strings were rejected. Oops.)
+ AddField(message_proto, "empty_string", 11, label, FD::TYPE_STRING)
+ ->set_default_value("");
+
+ // Add a second set of fields with implicit defalut values.
+ AddField(message_proto, "implicit_int32" , 21, label, FD::TYPE_INT32 );
+ AddField(message_proto, "implicit_int64" , 22, label, FD::TYPE_INT64 );
+ AddField(message_proto, "implicit_uint32", 23, label, FD::TYPE_UINT32);
+ AddField(message_proto, "implicit_uint64", 24, label, FD::TYPE_UINT64);
+ AddField(message_proto, "implicit_float" , 25, label, FD::TYPE_FLOAT );
+ AddField(message_proto, "implicit_double", 26, label, FD::TYPE_DOUBLE);
+ AddField(message_proto, "implicit_bool" , 27, label, FD::TYPE_BOOL );
+ AddField(message_proto, "implicit_string", 28, label, FD::TYPE_STRING);
+ AddField(message_proto, "implicit_data" , 29, label, FD::TYPE_BYTES );
+ AddField(message_proto, "implicit_enum" , 30, label, FD::TYPE_ENUM)
+ ->set_type_name("DummyEnum");
+
+ // Build it.
+ DescriptorPool pool;
+ const FileDescriptor* file = pool.BuildFile(file_proto);
+ ASSERT_TRUE(file != NULL);
+
+ ASSERT_EQ(1, file->enum_type_count());
+ const EnumDescriptor* enum_type = file->enum_type(0);
+ ASSERT_EQ(2, enum_type->value_count());
+ const EnumValueDescriptor* enum_value_a = enum_type->value(0);
+ const EnumValueDescriptor* enum_value_b = enum_type->value(1);
+
+ ASSERT_EQ(1, file->message_type_count());
+ const Descriptor* message = file->message_type(0);
+
+ ASSERT_EQ(21, message->field_count());
+
+ // Check the default values.
+ ASSERT_TRUE(message->field(0)->has_default_value());
+ ASSERT_TRUE(message->field(1)->has_default_value());
+ ASSERT_TRUE(message->field(2)->has_default_value());
+ ASSERT_TRUE(message->field(3)->has_default_value());
+ ASSERT_TRUE(message->field(4)->has_default_value());
+ ASSERT_TRUE(message->field(5)->has_default_value());
+ ASSERT_TRUE(message->field(6)->has_default_value());
+ ASSERT_TRUE(message->field(7)->has_default_value());
+ ASSERT_TRUE(message->field(8)->has_default_value());
+ ASSERT_TRUE(message->field(9)->has_default_value());
+ ASSERT_TRUE(message->field(10)->has_default_value());
+
+ EXPECT_EQ(-1 , message->field(0)->default_value_int32 ());
+ EXPECT_EQ(-GOOGLE_ULONGLONG(1000000000000),
+ message->field(1)->default_value_int64 ());
+ EXPECT_EQ(42 , message->field(2)->default_value_uint32());
+ EXPECT_EQ(GOOGLE_ULONGLONG(2000000000000),
+ message->field(3)->default_value_uint64());
+ EXPECT_EQ(4.5 , message->field(4)->default_value_float ());
+ EXPECT_EQ(10e100 , message->field(5)->default_value_double());
+ EXPECT_TRUE( message->field(6)->default_value_bool ());
+ EXPECT_EQ("hello" , message->field(7)->default_value_string());
+ EXPECT_EQ("\001\002\003" , message->field(8)->default_value_string());
+ EXPECT_EQ(enum_value_b , message->field(9)->default_value_enum ());
+ EXPECT_EQ("" , message->field(10)->default_value_string());
+
+ ASSERT_FALSE(message->field(11)->has_default_value());
+ ASSERT_FALSE(message->field(12)->has_default_value());
+ ASSERT_FALSE(message->field(13)->has_default_value());
+ ASSERT_FALSE(message->field(14)->has_default_value());
+ ASSERT_FALSE(message->field(15)->has_default_value());
+ ASSERT_FALSE(message->field(16)->has_default_value());
+ ASSERT_FALSE(message->field(17)->has_default_value());
+ ASSERT_FALSE(message->field(18)->has_default_value());
+ ASSERT_FALSE(message->field(19)->has_default_value());
+ ASSERT_FALSE(message->field(20)->has_default_value());
+
+ EXPECT_EQ(0 , message->field(11)->default_value_int32 ());
+ EXPECT_EQ(0 , message->field(12)->default_value_int64 ());
+ EXPECT_EQ(0 , message->field(13)->default_value_uint32());
+ EXPECT_EQ(0 , message->field(14)->default_value_uint64());
+ EXPECT_EQ(0.0f , message->field(15)->default_value_float ());
+ EXPECT_EQ(0.0 , message->field(16)->default_value_double());
+ EXPECT_FALSE( message->field(17)->default_value_bool ());
+ EXPECT_EQ("" , message->field(18)->default_value_string());
+ EXPECT_EQ("" , message->field(19)->default_value_string());
+ EXPECT_EQ(enum_value_a, message->field(20)->default_value_enum());
+}
+
+TEST_F(MiscTest, FieldOptions) {
+ // Try setting field options.
+
+ FileDescriptorProto file_proto;
+ file_proto.set_name("foo.proto");
+
+ DescriptorProto* message_proto = AddMessage(&file_proto, "TestMessage");
+ AddField(message_proto, "foo", 1,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ FieldDescriptorProto* bar_proto =
+ AddField(message_proto, "bar", 2,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+
+ FieldOptions* options = bar_proto->mutable_options();
+ options->set_ctype(FieldOptions::CORD);
+
+ // Build the descriptors and get the pointers.
+ DescriptorPool pool;
+ const FileDescriptor* file = pool.BuildFile(file_proto);
+ ASSERT_TRUE(file != NULL);
+
+ ASSERT_EQ(1, file->message_type_count());
+ const Descriptor* message = file->message_type(0);
+
+ ASSERT_EQ(2, message->field_count());
+ const FieldDescriptor* foo = message->field(0);
+ const FieldDescriptor* bar = message->field(1);
+
+ // "foo" had no options set, so it should return the default options.
+ EXPECT_EQ(&FieldOptions::default_instance(), &foo->options());
+
+ // "bar" had options set.
+ EXPECT_NE(&FieldOptions::default_instance(), options);
+ EXPECT_TRUE(bar->options().has_ctype());
+ EXPECT_EQ(FieldOptions::CORD, bar->options().ctype());
+}
+
+// ===================================================================
+enum DescriptorPoolMode {
+ NO_DATABASE,
+ FALLBACK_DATABASE
+};
+
+class AllowUnknownDependenciesTest
+ : public testing::TestWithParam<DescriptorPoolMode> {
+ protected:
+ DescriptorPoolMode mode() {
+ return GetParam();
+ }
+
+ virtual void SetUp() {
+ FileDescriptorProto foo_proto, bar_proto;
+
+ 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'"
+ "dependency: 'baz.proto'"
+ "message_type {"
+ " name: 'Foo'"
+ " field { name:'bar' number:1 label:LABEL_OPTIONAL type_name:'Bar' }"
+ " field { name:'baz' number:2 label:LABEL_OPTIONAL type_name:'Baz' }"
+ " field { name:'qux' number:3 label:LABEL_OPTIONAL"
+ " type_name: '.corge.Qux'"
+ " type: TYPE_ENUM"
+ " options {"
+ " uninterpreted_option {"
+ " name {"
+ " name_part: 'grault'"
+ " is_extension: true"
+ " }"
+ " positive_int_value: 1234"
+ " }"
+ " }"
+ " }"
+ "}",
+ &foo_proto));
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: 'bar.proto'"
+ "message_type { name: 'Bar' }",
+ &bar_proto));
+
+ // Collect pointers to stuff.
+ 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_ = BuildFile(foo_proto);
+ ASSERT_TRUE(foo_file_ != NULL);
+
+ ASSERT_EQ(1, foo_file_->message_type_count());
+ foo_type_ = foo_file_->message_type(0);
+
+ ASSERT_EQ(3, foo_type_->field_count());
+ bar_field_ = foo_type_->field(0);
+ baz_field_ = foo_type_->field(1);
+ 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_;
+ const Descriptor* foo_type_;
+ const FieldDescriptor* bar_field_;
+ const FieldDescriptor* baz_field_;
+ const FieldDescriptor* qux_field_;
+
+ SimpleDescriptorDatabase db_; // used if in FALLBACK_DATABASE mode.
+ google::protobuf::scoped_ptr<DescriptorPool> pool_;
+};
+
+TEST_P(AllowUnknownDependenciesTest, PlaceholderFile) {
+ ASSERT_EQ(2, foo_file_->dependency_count());
+ EXPECT_EQ(bar_file_, foo_file_->dependency(0));
+ EXPECT_FALSE(bar_file_->is_placeholder());
+
+ const FileDescriptor* baz_file = foo_file_->dependency(1);
+ EXPECT_EQ("baz.proto", baz_file->name());
+ EXPECT_EQ(0, baz_file->message_type_count());
+ EXPECT_TRUE(baz_file->is_placeholder());
+
+ // Placeholder files should not be findable.
+ EXPECT_EQ(bar_file_, pool_->FindFileByName(bar_file_->name()));
+ EXPECT_TRUE(pool_->FindFileByName(baz_file->name()) == NULL);
+
+ // Copy*To should not crash for placeholder files.
+ FileDescriptorProto baz_file_proto;
+ baz_file->CopyTo(&baz_file_proto);
+ baz_file->CopySourceCodeInfoTo(&baz_file_proto);
+ EXPECT_FALSE(baz_file_proto.has_source_code_info());
+}
+
+TEST_P(AllowUnknownDependenciesTest, PlaceholderTypes) {
+ ASSERT_EQ(FieldDescriptor::TYPE_MESSAGE, bar_field_->type());
+ EXPECT_EQ(bar_type_, bar_field_->message_type());
+ EXPECT_FALSE(bar_type_->is_placeholder());
+
+ ASSERT_EQ(FieldDescriptor::TYPE_MESSAGE, baz_field_->type());
+ const Descriptor* baz_type = baz_field_->message_type();
+ EXPECT_EQ("Baz", baz_type->name());
+ EXPECT_EQ("Baz", baz_type->full_name());
+ EXPECT_EQ(0, baz_type->extension_range_count());
+ EXPECT_TRUE(baz_type->is_placeholder());
+
+ ASSERT_EQ(FieldDescriptor::TYPE_ENUM, qux_field_->type());
+ const EnumDescriptor* qux_type = qux_field_->enum_type();
+ EXPECT_EQ("Qux", qux_type->name());
+ EXPECT_EQ("corge.Qux", qux_type->full_name());
+ EXPECT_TRUE(qux_type->is_placeholder());
+
+ // 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);
+}
+
+TEST_P(AllowUnknownDependenciesTest, CopyTo) {
+ // FieldDescriptor::CopyTo() should write non-fully-qualified type names
+ // for placeholder types which were not originally fully-qualified.
+ FieldDescriptorProto proto;
+
+ // Bar is not a placeholder, so it is fully-qualified.
+ bar_field_->CopyTo(&proto);
+ EXPECT_EQ(".Bar", proto.type_name());
+ EXPECT_EQ(FieldDescriptorProto::TYPE_MESSAGE, proto.type());
+
+ // Baz is an unqualified placeholder.
+ proto.Clear();
+ baz_field_->CopyTo(&proto);
+ EXPECT_EQ("Baz", proto.type_name());
+ EXPECT_FALSE(proto.has_type());
+
+ // Qux is a fully-qualified placeholder.
+ proto.Clear();
+ qux_field_->CopyTo(&proto);
+ EXPECT_EQ(".corge.Qux", proto.type_name());
+ EXPECT_EQ(FieldDescriptorProto::TYPE_ENUM, proto.type());
+}
+
+TEST_P(AllowUnknownDependenciesTest, CustomOptions) {
+ // Qux should still have the uninterpreted option attached.
+ ASSERT_EQ(1, qux_field_->options().uninterpreted_option_size());
+ const UninterpretedOption& option =
+ qux_field_->options().uninterpreted_option(0);
+ ASSERT_EQ(1, option.name_size());
+ EXPECT_EQ("grault", option.name(0).name_part());
+}
+
+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.
+
+ FileDescriptorProto extension_proto;
+
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: 'extension.proto'"
+ "extension { extendee: 'UnknownType' name:'some_extension' number:123"
+ " label:LABEL_OPTIONAL type:TYPE_INT32 }",
+ &extension_proto));
+ const FileDescriptor* file = BuildFile(extension_proto);
+
+ ASSERT_TRUE(file != NULL);
+
+ ASSERT_EQ(1, file->extension_count());
+ const Descriptor* extendee = file->extension(0)->containing_type();
+ EXPECT_EQ("UnknownType", extendee->name());
+ EXPECT_TRUE(extendee->is_placeholder());
+ ASSERT_EQ(1, extendee->extension_range_count());
+ EXPECT_EQ(1, extendee->extension_range(0)->start);
+ EXPECT_EQ(FieldDescriptor::kMaxNumber + 1, extendee->extension_range(0)->end);
+}
+
+TEST_P(AllowUnknownDependenciesTest, CustomOption) {
+ // Test that we can use a custom option without having parsed
+ // descriptor.proto.
+
+ FileDescriptorProto option_proto;
+
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: \"unknown_custom_options.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { "
+ " extendee: \"google.protobuf.FileOptions\" "
+ " name: \"some_option\" "
+ " number: 123456 "
+ " label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 "
+ "} "
+ "options { "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"some_option\" "
+ " is_extension: true "
+ " } "
+ " positive_int_value: 1234 "
+ " } "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"unknown_option\" "
+ " is_extension: true "
+ " } "
+ " positive_int_value: 1234 "
+ " } "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"optimize_for\" "
+ " is_extension: false "
+ " } "
+ " identifier_value: \"SPEED\" "
+ " } "
+ "}",
+ &option_proto));
+
+ const FileDescriptor* file = BuildFile(option_proto);
+ ASSERT_TRUE(file != NULL);
+
+ // Verify that no extension options were set, but they were left as
+ // uninterpreted_options.
+ vector<const FieldDescriptor*> fields;
+ file->options().GetReflection()->ListFields(file->options(), &fields);
+ ASSERT_EQ(2, fields.size());
+ EXPECT_TRUE(file->options().has_optimize_for());
+ 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());
+ EXPECT_FALSE(corge_desc->is_placeholder());
+
+ 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_TRUE(quux_field->message_type()->is_placeholder());
+ // 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) {
+ const Descriptor* message =
+ protobuf_unittest::TestMessageWithCustomOptions::descriptor();
+ const FileDescriptor* file = message->file();
+ const FieldDescriptor* field = message->FindFieldByName("field1");
+ const OneofDescriptor* oneof = message->FindOneofByName("AnOneof");
+ const EnumDescriptor* enm = message->FindEnumTypeByName("AnEnum");
+ // TODO(benjy): Support EnumValue options, once the compiler does.
+ const ServiceDescriptor* service =
+ file->FindServiceByName("TestServiceWithCustomOptions");
+ const MethodDescriptor* method = service->FindMethodByName("Foo");
+
+ EXPECT_EQ(GOOGLE_LONGLONG(9876543210),
+ file->options().GetExtension(protobuf_unittest::file_opt1));
+ EXPECT_EQ(-56,
+ message->options().GetExtension(protobuf_unittest::message_opt1));
+ EXPECT_EQ(GOOGLE_LONGLONG(8765432109),
+ field->options().GetExtension(protobuf_unittest::field_opt1));
+ EXPECT_EQ(42, // Check that we get the default for an option we don't set.
+ field->options().GetExtension(protobuf_unittest::field_opt2));
+ EXPECT_EQ(-99,
+ oneof->options().GetExtension(protobuf_unittest::oneof_opt1));
+ EXPECT_EQ(-789,
+ enm->options().GetExtension(protobuf_unittest::enum_opt1));
+ EXPECT_EQ(123,
+ enm->value(1)->options().GetExtension(
+ protobuf_unittest::enum_value_opt1));
+ EXPECT_EQ(GOOGLE_LONGLONG(-9876543210),
+ service->options().GetExtension(protobuf_unittest::service_opt1));
+ EXPECT_EQ(protobuf_unittest::METHODOPT1_VAL2,
+ method->options().GetExtension(protobuf_unittest::method_opt1));
+
+ // See that the regular options went through unscathed.
+ EXPECT_TRUE(message->options().has_message_set_wire_format());
+ EXPECT_EQ(FieldOptions::CORD, field->options().ctype());
+}
+
+TEST(CustomOptions, OptionTypes) {
+ const MessageOptions* options = NULL;
+
+ options =
+ &protobuf_unittest::CustomOptionMinIntegerValues::descriptor()->options();
+ EXPECT_EQ(false , options->GetExtension(protobuf_unittest::bool_opt));
+ EXPECT_EQ(kint32min, options->GetExtension(protobuf_unittest::int32_opt));
+ EXPECT_EQ(kint64min, options->GetExtension(protobuf_unittest::int64_opt));
+ EXPECT_EQ(0 , options->GetExtension(protobuf_unittest::uint32_opt));
+ EXPECT_EQ(0 , options->GetExtension(protobuf_unittest::uint64_opt));
+ EXPECT_EQ(kint32min, options->GetExtension(protobuf_unittest::sint32_opt));
+ EXPECT_EQ(kint64min, options->GetExtension(protobuf_unittest::sint64_opt));
+ EXPECT_EQ(0 , options->GetExtension(protobuf_unittest::fixed32_opt));
+ EXPECT_EQ(0 , options->GetExtension(protobuf_unittest::fixed64_opt));
+ EXPECT_EQ(kint32min, options->GetExtension(protobuf_unittest::sfixed32_opt));
+ EXPECT_EQ(kint64min, options->GetExtension(protobuf_unittest::sfixed64_opt));
+
+ options =
+ &protobuf_unittest::CustomOptionMaxIntegerValues::descriptor()->options();
+ EXPECT_EQ(true , options->GetExtension(protobuf_unittest::bool_opt));
+ EXPECT_EQ(kint32max , options->GetExtension(protobuf_unittest::int32_opt));
+ EXPECT_EQ(kint64max , options->GetExtension(protobuf_unittest::int64_opt));
+ EXPECT_EQ(kuint32max, options->GetExtension(protobuf_unittest::uint32_opt));
+ EXPECT_EQ(kuint64max, options->GetExtension(protobuf_unittest::uint64_opt));
+ EXPECT_EQ(kint32max , options->GetExtension(protobuf_unittest::sint32_opt));
+ EXPECT_EQ(kint64max , options->GetExtension(protobuf_unittest::sint64_opt));
+ EXPECT_EQ(kuint32max, options->GetExtension(protobuf_unittest::fixed32_opt));
+ EXPECT_EQ(kuint64max, options->GetExtension(protobuf_unittest::fixed64_opt));
+ EXPECT_EQ(kint32max , options->GetExtension(protobuf_unittest::sfixed32_opt));
+ EXPECT_EQ(kint64max , options->GetExtension(protobuf_unittest::sfixed64_opt));
+
+ options =
+ &protobuf_unittest::CustomOptionOtherValues::descriptor()->options();
+ EXPECT_EQ(-100, options->GetExtension(protobuf_unittest::int32_opt));
+ EXPECT_FLOAT_EQ(12.3456789,
+ options->GetExtension(protobuf_unittest::float_opt));
+ EXPECT_DOUBLE_EQ(1.234567890123456789,
+ options->GetExtension(protobuf_unittest::double_opt));
+ EXPECT_EQ("Hello, \"World\"",
+ options->GetExtension(protobuf_unittest::string_opt));
+
+ EXPECT_EQ(string("Hello\0World", 11),
+ options->GetExtension(protobuf_unittest::bytes_opt));
+
+ EXPECT_EQ(protobuf_unittest::DummyMessageContainingEnum::TEST_OPTION_ENUM_TYPE2,
+ options->GetExtension(protobuf_unittest::enum_opt));
+
+ options =
+ &protobuf_unittest::SettingRealsFromPositiveInts::descriptor()->options();
+ EXPECT_FLOAT_EQ(12, options->GetExtension(protobuf_unittest::float_opt));
+ EXPECT_DOUBLE_EQ(154, options->GetExtension(protobuf_unittest::double_opt));
+
+ options =
+ &protobuf_unittest::SettingRealsFromNegativeInts::descriptor()->options();
+ EXPECT_FLOAT_EQ(-12, options->GetExtension(protobuf_unittest::float_opt));
+ EXPECT_DOUBLE_EQ(-154, options->GetExtension(protobuf_unittest::double_opt));
+}
+
+TEST(CustomOptions, ComplexExtensionOptions) {
+ const MessageOptions* options =
+ &protobuf_unittest::VariousComplexOptions::descriptor()->options();
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt1).foo(), 42);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt1).
+ GetExtension(protobuf_unittest::quux), 324);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt1).
+ GetExtension(protobuf_unittest::corge).qux(), 876);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).baz(), 987);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).
+ GetExtension(protobuf_unittest::grault), 654);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).bar().foo(),
+ 743);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).bar().
+ GetExtension(protobuf_unittest::quux), 1999);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).bar().
+ GetExtension(protobuf_unittest::corge).qux(), 2008);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).
+ GetExtension(protobuf_unittest::garply).foo(), 741);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).
+ GetExtension(protobuf_unittest::garply).
+ GetExtension(protobuf_unittest::quux), 1998);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).
+ GetExtension(protobuf_unittest::garply).
+ GetExtension(protobuf_unittest::corge).qux(), 2121);
+ EXPECT_EQ(options->GetExtension(
+ protobuf_unittest::ComplexOptionType2::ComplexOptionType4::complex_opt4).
+ waldo(), 1971);
+ EXPECT_EQ(options->GetExtension(protobuf_unittest::complex_opt2).
+ fred().waldo(), 321);
+ EXPECT_EQ(9, options->GetExtension(protobuf_unittest::complex_opt3).qux());
+ EXPECT_EQ(22, options->GetExtension(protobuf_unittest::complex_opt3).
+ complexoptiontype5().plugh());
+ EXPECT_EQ(24, options->GetExtension(protobuf_unittest::complexopt6).xyzzy());
+}
+
+TEST(CustomOptions, OptionsFromOtherFile) {
+ // Test that to use a custom option, we only need to import the file
+ // defining the option; we do not also have to import descriptor.proto.
+ DescriptorPool pool;
+
+ FileDescriptorProto file_proto;
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ protobuf_unittest::TestMessageWithCustomOptions::descriptor()
+ ->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: \"custom_options_import.proto\" "
+ "package: \"protobuf_unittest\" "
+ "dependency: \"google/protobuf/unittest_custom_options.proto\" "
+ "options { "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"file_opt1\" "
+ " is_extension: true "
+ " } "
+ " positive_int_value: 1234 "
+ " } "
+ // Test a non-extension option too. (At one point this failed due to a
+ // bug.)
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"java_package\" "
+ " is_extension: false "
+ " } "
+ " string_value: \"foo\" "
+ " } "
+ // Test that enum-typed options still work too. (At one point this also
+ // failed due to a bug.)
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"optimize_for\" "
+ " is_extension: false "
+ " } "
+ " identifier_value: \"SPEED\" "
+ " } "
+ "}"
+ ,
+ &file_proto));
+
+ const FileDescriptor* file = pool.BuildFile(file_proto);
+ ASSERT_TRUE(file != NULL);
+ EXPECT_EQ(1234, file->options().GetExtension(protobuf_unittest::file_opt1));
+ EXPECT_TRUE(file->options().has_java_package());
+ EXPECT_EQ("foo", file->options().java_package());
+ EXPECT_TRUE(file->options().has_optimize_for());
+ EXPECT_EQ(FileOptions::SPEED, file->options().optimize_for());
+}
+
+TEST(CustomOptions, MessageOptionThreeFieldsSet) {
+ // This tests a bug which previously existed in custom options parsing. The
+ // bug occurred when you defined a custom option with message type and then
+ // set three fields of that option on a single definition (see the example
+ // below). The bug is a bit hard to explain, so check the change history if
+ // you want to know more.
+ DescriptorPool pool;
+
+ FileDescriptorProto file_proto;
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ protobuf_unittest::TestMessageWithCustomOptions::descriptor()
+ ->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ // The following represents the definition:
+ //
+ // import "google/protobuf/unittest_custom_options.proto"
+ // package protobuf_unittest;
+ // message Foo {
+ // option (complex_opt1).foo = 1234;
+ // option (complex_opt1).foo2 = 1234;
+ // option (complex_opt1).foo3 = 1234;
+ // }
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: \"custom_options_import.proto\" "
+ "package: \"protobuf_unittest\" "
+ "dependency: \"google/protobuf/unittest_custom_options.proto\" "
+ "message_type { "
+ " name: \"Foo\" "
+ " options { "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt1\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"foo\" "
+ " is_extension: false "
+ " } "
+ " positive_int_value: 1234 "
+ " } "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt1\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"foo2\" "
+ " is_extension: false "
+ " } "
+ " positive_int_value: 1234 "
+ " } "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt1\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"foo3\" "
+ " is_extension: false "
+ " } "
+ " positive_int_value: 1234 "
+ " } "
+ " } "
+ "}",
+ &file_proto));
+
+ const FileDescriptor* file = pool.BuildFile(file_proto);
+ ASSERT_TRUE(file != NULL);
+ ASSERT_EQ(1, file->message_type_count());
+
+ const MessageOptions& options = file->message_type(0)->options();
+ EXPECT_EQ(1234, options.GetExtension(protobuf_unittest::complex_opt1).foo());
+}
+
+TEST(CustomOptions, MessageOptionRepeatedLeafFieldSet) {
+ // This test verifies that repeated fields in custom options can be
+ // given multiple values by repeating the option with a different value.
+ // This test checks repeated leaf values. Each repeated custom value
+ // appears in a different uninterpreted_option, which will be concatenated
+ // when they are merged into the final option value.
+ DescriptorPool pool;
+
+ FileDescriptorProto file_proto;
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ protobuf_unittest::TestMessageWithCustomOptions::descriptor()
+ ->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ // The following represents the definition:
+ //
+ // import "google/protobuf/unittest_custom_options.proto"
+ // package protobuf_unittest;
+ // message Foo {
+ // option (complex_opt1).foo4 = 12;
+ // option (complex_opt1).foo4 = 34;
+ // option (complex_opt1).foo4 = 56;
+ // }
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: \"custom_options_import.proto\" "
+ "package: \"protobuf_unittest\" "
+ "dependency: \"google/protobuf/unittest_custom_options.proto\" "
+ "message_type { "
+ " name: \"Foo\" "
+ " options { "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt1\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"foo4\" "
+ " is_extension: false "
+ " } "
+ " positive_int_value: 12 "
+ " } "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt1\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"foo4\" "
+ " is_extension: false "
+ " } "
+ " positive_int_value: 34 "
+ " } "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt1\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"foo4\" "
+ " is_extension: false "
+ " } "
+ " positive_int_value: 56 "
+ " } "
+ " } "
+ "}",
+ &file_proto));
+
+ const FileDescriptor* file = pool.BuildFile(file_proto);
+ ASSERT_TRUE(file != NULL);
+ ASSERT_EQ(1, file->message_type_count());
+
+ const MessageOptions& options = file->message_type(0)->options();
+ EXPECT_EQ(3, options.GetExtension(protobuf_unittest::complex_opt1).foo4_size());
+ EXPECT_EQ(12, options.GetExtension(protobuf_unittest::complex_opt1).foo4(0));
+ EXPECT_EQ(34, options.GetExtension(protobuf_unittest::complex_opt1).foo4(1));
+ EXPECT_EQ(56, options.GetExtension(protobuf_unittest::complex_opt1).foo4(2));
+}
+
+TEST(CustomOptions, MessageOptionRepeatedMsgFieldSet) {
+ // This test verifies that repeated fields in custom options can be
+ // given multiple values by repeating the option with a different value.
+ // This test checks repeated message values. Each repeated custom value
+ // appears in a different uninterpreted_option, which will be concatenated
+ // when they are merged into the final option value.
+ DescriptorPool pool;
+
+ FileDescriptorProto file_proto;
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ protobuf_unittest::TestMessageWithCustomOptions::descriptor()
+ ->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ // The following represents the definition:
+ //
+ // import "google/protobuf/unittest_custom_options.proto"
+ // package protobuf_unittest;
+ // message Foo {
+ // option (complex_opt2).barney = {waldo: 1};
+ // option (complex_opt2).barney = {waldo: 10};
+ // option (complex_opt2).barney = {waldo: 100};
+ // }
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: \"custom_options_import.proto\" "
+ "package: \"protobuf_unittest\" "
+ "dependency: \"google/protobuf/unittest_custom_options.proto\" "
+ "message_type { "
+ " name: \"Foo\" "
+ " options { "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt2\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"barney\" "
+ " is_extension: false "
+ " } "
+ " aggregate_value: \"waldo: 1\" "
+ " } "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt2\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"barney\" "
+ " is_extension: false "
+ " } "
+ " aggregate_value: \"waldo: 10\" "
+ " } "
+ " uninterpreted_option { "
+ " name { "
+ " name_part: \"complex_opt2\" "
+ " is_extension: true "
+ " } "
+ " name { "
+ " name_part: \"barney\" "
+ " is_extension: false "
+ " } "
+ " aggregate_value: \"waldo: 100\" "
+ " } "
+ " } "
+ "}",
+ &file_proto));
+
+ const FileDescriptor* file = pool.BuildFile(file_proto);
+ ASSERT_TRUE(file != NULL);
+ ASSERT_EQ(1, file->message_type_count());
+
+ const MessageOptions& options = file->message_type(0)->options();
+ EXPECT_EQ(3, options.GetExtension(
+ protobuf_unittest::complex_opt2).barney_size());
+ EXPECT_EQ(1,options.GetExtension(
+ protobuf_unittest::complex_opt2).barney(0).waldo());
+ EXPECT_EQ(10, options.GetExtension(
+ protobuf_unittest::complex_opt2).barney(1).waldo());
+ EXPECT_EQ(100, options.GetExtension(
+ protobuf_unittest::complex_opt2).barney(2).waldo());
+}
+
+// Check that aggregate options were parsed and saved correctly in
+// the appropriate descriptors.
+TEST(CustomOptions, AggregateOptions) {
+ const Descriptor* msg = protobuf_unittest::AggregateMessage::descriptor();
+ const FileDescriptor* file = msg->file();
+ const FieldDescriptor* field = msg->FindFieldByName("fieldname");
+ const EnumDescriptor* enumd = file->FindEnumTypeByName("AggregateEnum");
+ const EnumValueDescriptor* enumv = enumd->FindValueByName("VALUE");
+ const ServiceDescriptor* service = file->FindServiceByName(
+ "AggregateService");
+ const MethodDescriptor* method = service->FindMethodByName("Method");
+
+ // Tests for the different types of data embedded in fileopt
+ const protobuf_unittest::Aggregate& file_options =
+ file->options().GetExtension(protobuf_unittest::fileopt);
+ EXPECT_EQ(100, file_options.i());
+ EXPECT_EQ("FileAnnotation", file_options.s());
+ EXPECT_EQ("NestedFileAnnotation", file_options.sub().s());
+ EXPECT_EQ("FileExtensionAnnotation",
+ file_options.file().GetExtension(protobuf_unittest::fileopt).s());
+ EXPECT_EQ("EmbeddedMessageSetElement",
+ file_options.mset().GetExtension(
+ protobuf_unittest::AggregateMessageSetElement
+ ::message_set_extension).s());
+
+ // Simple tests for all the other types of annotations
+ EXPECT_EQ("MessageAnnotation",
+ msg->options().GetExtension(protobuf_unittest::msgopt).s());
+ EXPECT_EQ("FieldAnnotation",
+ field->options().GetExtension(protobuf_unittest::fieldopt).s());
+ EXPECT_EQ("EnumAnnotation",
+ enumd->options().GetExtension(protobuf_unittest::enumopt).s());
+ EXPECT_EQ("EnumValueAnnotation",
+ enumv->options().GetExtension(protobuf_unittest::enumvalopt).s());
+ EXPECT_EQ("ServiceAnnotation",
+ service->options().GetExtension(protobuf_unittest::serviceopt).s());
+ EXPECT_EQ("MethodAnnotation",
+ method->options().GetExtension(protobuf_unittest::methodopt).s());
+}
+
+TEST(CustomOptions, UnusedImportWarning) {
+ DescriptorPool pool;
+
+ FileDescriptorProto file_proto;
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ protobuf_unittest::TestMessageWithCustomOptions::descriptor()
+ ->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ pool.AddUnusedImportTrackFile("custom_options_import.proto");
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: \"custom_options_import.proto\" "
+ "package: \"protobuf_unittest\" "
+ "dependency: \"google/protobuf/unittest_custom_options.proto\" ",
+ &file_proto));
+
+ MockErrorCollector error_collector;
+ EXPECT_TRUE(pool.BuildFileCollectingErrors(file_proto, &error_collector));
+ EXPECT_EQ("", error_collector.warning_text_);
+}
+
+// Verifies that proto files can correctly be parsed, even if the
+// custom options defined in the file are incompatible with those
+// compiled in the binary. See http://b/19276250.
+TEST(CustomOptions, OptionsWithRequiredEnums) {
+ DescriptorPool pool;
+
+ FileDescriptorProto file_proto;
+ MessageOptions::descriptor()->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ // Create a new file descriptor proto containing a subset of the
+ // messages defined in google/protobuf/unittest_custom_options.proto.
+ file_proto.Clear();
+ file_proto.set_name("unittest_custom_options.proto");
+ file_proto.set_package("protobuf_unittest");
+ file_proto.add_dependency("google/protobuf/descriptor.proto");
+
+ // Add the "required_enum_opt" extension.
+ FieldDescriptorProto* extension = file_proto.add_extension();
+ protobuf_unittest::OldOptionType::descriptor()->file()
+ ->FindExtensionByName("required_enum_opt")->CopyTo(extension);
+
+ // Add a test message that uses the "required_enum_opt" option.
+ DescriptorProto* test_message_type = file_proto.add_message_type();
+ protobuf_unittest::TestMessageWithRequiredEnumOption::descriptor()
+ ->CopyTo(test_message_type);
+
+ // Instruct the extension to use NewOptionType instead of
+ // OldOptionType, and add the descriptor of NewOptionType.
+ extension->set_type_name(".protobuf_unittest.NewOptionType");
+ DescriptorProto* new_option_type = file_proto.add_message_type();
+ protobuf_unittest::NewOptionType::descriptor()
+ ->CopyTo(new_option_type);
+
+ // Replace the value of the "required_enum_opt" option used in the
+ // test message with an enum value that only exists in NewOptionType.
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "uninterpreted_option { "
+ " name { "
+ " name_part: 'required_enum_opt' "
+ " is_extension: true "
+ " } "
+ " aggregate_value: 'value: NEW_VALUE' "
+ "}",
+ test_message_type->mutable_options()));
+
+ // Add the file descriptor to the pool.
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ // Find the test message.
+ const Descriptor* test_message = pool.FindMessageTypeByName(
+ "protobuf_unittest.TestMessageWithRequiredEnumOption");
+ ASSERT_TRUE(test_message != NULL);
+
+ const MessageOptions& options = test_message->options();
+ // Extract the "required_enum_opt" option. Since the binary does not
+ // know that the extension was updated, this will still return an
+ // OldOptionType message.
+ ASSERT_TRUE(
+ options.HasExtension(protobuf_unittest::required_enum_opt));
+ const protobuf_unittest::OldOptionType& old_enum_opt =
+ options.GetExtension(protobuf_unittest::required_enum_opt);
+
+ // Confirm that the required enum field is missing.
+ EXPECT_FALSE(old_enum_opt.IsInitialized());
+ EXPECT_FALSE(old_enum_opt.has_value());
+
+ string buf;
+ // Verify that the required enum field does show up when the option
+ // is re-parsed as a NewOptionType message;
+ protobuf_unittest::NewOptionType new_enum_opt;
+ EXPECT_TRUE(old_enum_opt.AppendPartialToString(&buf));
+ EXPECT_TRUE(new_enum_opt.ParseFromString(buf));
+ EXPECT_EQ(protobuf_unittest::NewOptionType::NEW_VALUE, new_enum_opt.value());
+}
+
+// ===================================================================
+
+class ValidationErrorTest : public testing::Test {
+ protected:
+ // Parse file_text as a FileDescriptorProto in text format and add it
+ // to the DescriptorPool. Expect no errors.
+ const FileDescriptor* BuildFile(const string& file_text) {
+ FileDescriptorProto file_proto;
+ EXPECT_TRUE(TextFormat::ParseFromString(file_text, &file_proto));
+ return GOOGLE_CHECK_NOTNULL(pool_.BuildFile(file_proto));
+ }
+
+ // Parse file_text as a FileDescriptorProto in text format and add it
+ // to the DescriptorPool. Expect errors to be produced which match the
+ // given error text.
+ void BuildFileWithErrors(const string& file_text,
+ const string& expected_errors) {
+ FileDescriptorProto file_proto;
+ ASSERT_TRUE(TextFormat::ParseFromString(file_text, &file_proto));
+
+ MockErrorCollector error_collector;
+ EXPECT_TRUE(
+ pool_.BuildFileCollectingErrors(file_proto, &error_collector) == NULL);
+ EXPECT_EQ(expected_errors, error_collector.text_);
+ }
+
+ // Parse file_text as a FileDescriptorProto in text format and add it
+ // to the DescriptorPool. Expect errors to be produced which match the
+ // given warning text.
+ void BuildFileWithWarnings(const string& file_text,
+ const string& expected_warnings) {
+ FileDescriptorProto file_proto;
+ ASSERT_TRUE(TextFormat::ParseFromString(file_text, &file_proto));
+
+ MockErrorCollector error_collector;
+ EXPECT_TRUE(pool_.BuildFileCollectingErrors(file_proto, &error_collector));
+ EXPECT_EQ(expected_warnings, error_collector.warning_text_);
+ }
+
+ // Builds some already-parsed file in our test pool.
+ void BuildFileInTestPool(const FileDescriptor* file) {
+ FileDescriptorProto file_proto;
+ file->CopyTo(&file_proto);
+ ASSERT_TRUE(pool_.BuildFile(file_proto) != NULL);
+ }
+
+ // Build descriptor.proto in our test pool. This allows us to extend it in
+ // the test pool, so we can test custom options.
+ void BuildDescriptorMessagesInTestPool() {
+ BuildFileInTestPool(DescriptorProto::descriptor()->file());
+ }
+
+ DescriptorPool pool_;
+};
+
+TEST_F(ValidationErrorTest, AlreadyDefined) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" }"
+ "message_type { name: \"Foo\" }",
+
+ "foo.proto: Foo: NAME: \"Foo\" is already defined.\n");
+}
+
+TEST_F(ValidationErrorTest, AlreadyDefinedInPackage) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "package: \"foo.bar\" "
+ "message_type { name: \"Foo\" }"
+ "message_type { name: \"Foo\" }",
+
+ "foo.proto: foo.bar.Foo: NAME: \"Foo\" is already defined in "
+ "\"foo.bar\".\n");
+}
+
+TEST_F(ValidationErrorTest, AlreadyDefinedInOtherFile) {
+ BuildFile(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" }");
+
+ BuildFileWithErrors(
+ "name: \"bar.proto\" "
+ "message_type { name: \"Foo\" }",
+
+ "bar.proto: Foo: NAME: \"Foo\" is already defined in file "
+ "\"foo.proto\".\n");
+}
+
+TEST_F(ValidationErrorTest, PackageAlreadyDefined) {
+ BuildFile(
+ "name: \"foo.proto\" "
+ "message_type { name: \"foo\" }");
+ BuildFileWithErrors(
+ "name: \"bar.proto\" "
+ "package: \"foo.bar\"",
+
+ "bar.proto: foo: NAME: \"foo\" is already defined (as something other "
+ "than a package) in file \"foo.proto\".\n");
+}
+
+TEST_F(ValidationErrorTest, EnumValueAlreadyDefinedInParent) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "enum_type { name: \"Foo\" value { name: \"FOO\" number: 1 } } "
+ "enum_type { name: \"Bar\" value { name: \"FOO\" number: 1 } } ",
+
+ "foo.proto: FOO: NAME: \"FOO\" is already defined.\n"
+ "foo.proto: FOO: NAME: Note that enum values use C++ scoping rules, "
+ "meaning that enum values are siblings of their type, not children of "
+ "it. Therefore, \"FOO\" must be unique within the global scope, not "
+ "just within \"Bar\".\n");
+}
+
+TEST_F(ValidationErrorTest, EnumValueAlreadyDefinedInParentNonGlobal) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "package: \"pkg\" "
+ "enum_type { name: \"Foo\" value { name: \"FOO\" number: 1 } } "
+ "enum_type { name: \"Bar\" value { name: \"FOO\" number: 1 } } ",
+
+ "foo.proto: pkg.FOO: NAME: \"FOO\" is already defined in \"pkg\".\n"
+ "foo.proto: pkg.FOO: NAME: Note that enum values use C++ scoping rules, "
+ "meaning that enum values are siblings of their type, not children of "
+ "it. Therefore, \"FOO\" must be unique within \"pkg\", not just within "
+ "\"Bar\".\n");
+}
+
+TEST_F(ValidationErrorTest, MissingName) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { }",
+
+ "foo.proto: : NAME: Missing name.\n");
+}
+
+TEST_F(ValidationErrorTest, InvalidName) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { name: \"$\" }",
+
+ "foo.proto: $: NAME: \"$\" is not a valid identifier.\n");
+}
+
+TEST_F(ValidationErrorTest, InvalidPackageName) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "package: \"foo.$\"",
+
+ "foo.proto: foo.$: NAME: \"$\" is not a valid identifier.\n");
+}
+
+TEST_F(ValidationErrorTest, MissingFileName) {
+ BuildFileWithErrors(
+ "",
+
+ ": : OTHER: Missing field: FileDescriptorProto.name.\n");
+}
+
+TEST_F(ValidationErrorTest, DupeDependency) {
+ BuildFile("name: \"foo.proto\"");
+ BuildFileWithErrors(
+ "name: \"bar.proto\" "
+ "dependency: \"foo.proto\" "
+ "dependency: \"foo.proto\" ",
+
+ "bar.proto: bar.proto: OTHER: Import \"foo.proto\" was listed twice.\n");
+}
+
+TEST_F(ValidationErrorTest, UnknownDependency) {
+ BuildFileWithErrors(
+ "name: \"bar.proto\" "
+ "dependency: \"foo.proto\" ",
+
+ "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
+ // nested within a parent package which this file is also in, and we don't
+ // include that parent package in the name (i.e. we do a relative lookup)...
+ // Yes, really.
+ BuildFile(
+ "name: 'foo.proto' "
+ "package: 'outer.foo' ");
+ BuildFileWithErrors(
+ "name: 'bar.proto' "
+ "dependency: 'baz.proto' "
+ "package: 'outer.bar' "
+ "message_type { "
+ " name: 'Bar' "
+ " field { name:'bar' number:1 label:LABEL_OPTIONAL type_name:'foo.Foo' }"
+ "}",
+
+ "bar.proto: bar.proto: OTHER: Import \"baz.proto\" has not been loaded.\n"
+ "bar.proto: outer.bar.Bar.bar: TYPE: \"outer.foo\" seems to be defined in "
+ "\"foo.proto\", which is not imported by \"bar.proto\". To use it here, "
+ "please add the necessary import.\n");
+}
+
+TEST_F(ValidationErrorTest, DupeFile) {
+ BuildFile(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" }");
+ // Note: We should *not* get redundant errors about "Foo" already being
+ // defined.
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" } "
+ // Add another type so that the files aren't identical (in which case there
+ // would be no error).
+ "enum_type { name: \"Bar\" }",
+
+ "foo.proto: foo.proto: OTHER: A file with this name is already in the "
+ "pool.\n");
+}
+
+TEST_F(ValidationErrorTest, FieldInExtensionRange) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name: \"foo\" number: 9 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name: \"bar\" number: 10 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name: \"baz\" number: 19 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name: \"qux\" number: 20 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " extension_range { start: 10 end: 20 }"
+ "}",
+
+ "foo.proto: Foo.bar: NUMBER: Extension range 10 to 19 includes field "
+ "\"bar\" (10).\n"
+ "foo.proto: Foo.baz: NUMBER: Extension range 10 to 19 includes field "
+ "\"baz\" (19).\n");
+}
+
+TEST_F(ValidationErrorTest, OverlappingExtensionRanges) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " extension_range { start: 10 end: 20 }"
+ " extension_range { start: 20 end: 30 }"
+ " extension_range { start: 19 end: 21 }"
+ "}",
+
+ "foo.proto: Foo: NUMBER: Extension range 19 to 20 overlaps with "
+ "already-defined range 10 to 19.\n"
+ "foo.proto: Foo: NUMBER: Extension range 19 to 20 overlaps with "
+ "already-defined range 20 to 29.\n");
+}
+
+TEST_F(ValidationErrorTest, ReservedFieldError) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name: \"foo\" number: 15 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " reserved_range { start: 10 end: 20 }"
+ "}",
+
+ "foo.proto: Foo.foo: NUMBER: Field \"foo\" uses reserved number 15.\n");
+}
+
+TEST_F(ValidationErrorTest, ReservedExtensionRangeError) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " extension_range { start: 10 end: 20 }"
+ " reserved_range { start: 5 end: 15 }"
+ "}",
+
+ "foo.proto: Foo: NUMBER: Extension range 10 to 19"
+ " overlaps with reserved range 5 to 14.\n");
+}
+
+TEST_F(ValidationErrorTest, ReservedExtensionRangeAdjacent) {
+ BuildFile(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " extension_range { start: 10 end: 20 }"
+ " reserved_range { start: 5 end: 10 }"
+ "}");
+}
+
+TEST_F(ValidationErrorTest, ReservedRangeOverlap) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " reserved_range { start: 10 end: 20 }"
+ " reserved_range { start: 5 end: 15 }"
+ "}",
+
+ "foo.proto: Foo: NUMBER: Reserved range 5 to 14"
+ " overlaps with already-defined range 10 to 19.\n");
+}
+
+TEST_F(ValidationErrorTest, ReservedNameError) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name: \"foo\" number: 15 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name: \"bar\" number: 16 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name: \"baz\" number: 17 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " reserved_name: \"foo\""
+ " reserved_name: \"bar\""
+ "}",
+
+ "foo.proto: Foo.foo: NAME: Field name \"foo\" is reserved.\n"
+ "foo.proto: Foo.bar: NAME: Field name \"bar\" is reserved.\n");
+}
+
+TEST_F(ValidationErrorTest, ReservedNameRedundant) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " reserved_name: \"foo\""
+ " reserved_name: \"foo\""
+ "}",
+
+ "foo.proto: foo: NAME: Field name \"foo\" is reserved multiple times.\n");
+}
+
+TEST_F(ValidationErrorTest, ReservedFieldsDebugString) {
+ const FileDescriptor* file = BuildFile(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " reserved_name: \"foo\""
+ " reserved_name: \"bar\""
+ " reserved_range { start: 5 end: 6 }"
+ " reserved_range { start: 10 end: 20 }"
+ "}");
+
+ ASSERT_EQ(
+ "syntax = \"proto2\";\n\n"
+ "message Foo {\n"
+ " reserved 5, 10 to 19;\n"
+ " reserved \"foo\", \"bar\";\n"
+ "}\n\n",
+ file->DebugString());
+}
+
+TEST_F(ValidationErrorTest, InvalidDefaults) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+
+ // Invalid number.
+ " field { name: \"foo\" number: 1 label: LABEL_OPTIONAL type: TYPE_INT32"
+ " default_value: \"abc\" }"
+
+ // Empty default value.
+ " field { name: \"bar\" number: 2 label: LABEL_OPTIONAL type: TYPE_INT32"
+ " default_value: \"\" }"
+
+ // Invalid boolean.
+ " field { name: \"baz\" number: 3 label: LABEL_OPTIONAL type: TYPE_BOOL"
+ " default_value: \"abc\" }"
+
+ // Messages can't have defaults.
+ " field { name: \"qux\" number: 4 label: LABEL_OPTIONAL type: TYPE_MESSAGE"
+ " default_value: \"abc\" type_name: \"Foo\" }"
+
+ // Same thing, but we don't know that this field has message type until
+ // we look up the type name.
+ " field { name: \"quux\" number: 5 label: LABEL_OPTIONAL"
+ " default_value: \"abc\" type_name: \"Foo\" }"
+
+ // Repeateds can't have defaults.
+ " field { name: \"corge\" number: 6 label: LABEL_REPEATED type: TYPE_INT32"
+ " default_value: \"1\" }"
+ "}",
+
+ "foo.proto: Foo.foo: DEFAULT_VALUE: Couldn't parse default value \"abc\".\n"
+ "foo.proto: Foo.bar: DEFAULT_VALUE: Couldn't parse default value \"\".\n"
+ "foo.proto: Foo.baz: DEFAULT_VALUE: Boolean default must be true or "
+ "false.\n"
+ "foo.proto: Foo.qux: DEFAULT_VALUE: Messages can't have default values.\n"
+ "foo.proto: Foo.corge: DEFAULT_VALUE: Repeated fields can't have default "
+ "values.\n"
+ // This ends up being reported later because the error is detected at
+ // cross-linking time.
+ "foo.proto: Foo.quux: DEFAULT_VALUE: Messages can't have default "
+ "values.\n");
+}
+
+TEST_F(ValidationErrorTest, NegativeFieldNumber) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name: \"foo\" number: -1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ "}",
+
+ "foo.proto: Foo.foo: NUMBER: Field numbers must be positive integers.\n");
+}
+
+TEST_F(ValidationErrorTest, HugeFieldNumber) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name: \"foo\" number: 0x70000000 "
+ " label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ "}",
+
+ "foo.proto: Foo.foo: NUMBER: Field numbers cannot be greater than "
+ "536870911.\n");
+}
+
+TEST_F(ValidationErrorTest, ReservedFieldNumber) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field {name:\"foo\" number: 18999 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field {name:\"bar\" number: 19000 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field {name:\"baz\" number: 19999 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field {name:\"qux\" number: 20000 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ "}",
+
+ "foo.proto: Foo.bar: NUMBER: Field numbers 19000 through 19999 are "
+ "reserved for the protocol buffer library implementation.\n"
+ "foo.proto: Foo.baz: NUMBER: Field numbers 19000 through 19999 are "
+ "reserved for the protocol buffer library implementation.\n");
+}
+
+TEST_F(ValidationErrorTest, ExtensionMissingExtendee) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " extension { name: \"foo\" number: 1 label: LABEL_OPTIONAL"
+ " type_name: \"Foo\" }"
+ "}",
+
+ "foo.proto: Foo.foo: EXTENDEE: FieldDescriptorProto.extendee not set for "
+ "extension field.\n");
+}
+
+TEST_F(ValidationErrorTest, NonExtensionWithExtendee) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Bar\""
+ " extension_range { start: 1 end: 2 }"
+ "}"
+ "message_type {"
+ " name: \"Foo\""
+ " field { name: \"foo\" number: 1 label: LABEL_OPTIONAL"
+ " type_name: \"Foo\" extendee: \"Bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: EXTENDEE: FieldDescriptorProto.extendee set for "
+ "non-extension field.\n");
+}
+
+TEST_F(ValidationErrorTest, FieldOneofIndexTooLarge) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 1 }"
+ " field { name:\"dummy\" number:2 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 0 }"
+ " oneof_decl { name:\"bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: OTHER: FieldDescriptorProto.oneof_index 1 is out of "
+ "range for type \"Foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, FieldOneofIndexNegative) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: -1 }"
+ " field { name:\"dummy\" number:2 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 0 }"
+ " oneof_decl { name:\"bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: OTHER: FieldDescriptorProto.oneof_index -1 is out of "
+ "range for type \"Foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, OneofFieldsConsecutiveDefinition) {
+ // Fields belonging to the same oneof must be defined consecutively.
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo1\" number: 1 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 0 }"
+ " field { name:\"bar\" number: 2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name:\"foo2\" number: 3 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 0 }"
+ " oneof_decl { name:\"foos\" }"
+ "}",
+
+ "foo.proto: Foo.bar: OTHER: Fields in the same oneof must be defined "
+ "consecutively. \"bar\" cannot be defined before the completion of the "
+ "\"foos\" oneof definition.\n");
+
+ // Prevent interleaved fields, which belong to different oneofs.
+ BuildFileWithErrors(
+ "name: \"foo2.proto\" "
+ "message_type {"
+ " name: \"Foo2\""
+ " field { name:\"foo1\" number: 1 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 0 }"
+ " field { name:\"bar1\" number: 2 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 1 }"
+ " field { name:\"foo2\" number: 3 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 0 }"
+ " field { name:\"bar2\" number: 4 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 1 }"
+ " oneof_decl { name:\"foos\" }"
+ " oneof_decl { name:\"bars\" }"
+ "}",
+ "foo2.proto: Foo2.bar1: OTHER: Fields in the same oneof must be defined "
+ "consecutively. \"bar1\" cannot be defined before the completion of the "
+ "\"foos\" oneof definition.\n"
+ "foo2.proto: Foo2.foo2: OTHER: Fields in the same oneof must be defined "
+ "consecutively. \"foo2\" cannot be defined before the completion of the "
+ "\"bars\" oneof definition.\n");
+
+ // Another case for normal fields and different oneof fields interleave.
+ BuildFileWithErrors(
+ "name: \"foo3.proto\" "
+ "message_type {"
+ " name: \"Foo3\""
+ " field { name:\"foo1\" number: 1 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 0 }"
+ " field { name:\"bar1\" number: 2 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 1 }"
+ " field { name:\"baz\" number: 3 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name:\"foo2\" number: 4 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " oneof_index: 0 }"
+ " oneof_decl { name:\"foos\" }"
+ " oneof_decl { name:\"bars\" }"
+ "}",
+ "foo3.proto: Foo3.baz: OTHER: Fields in the same oneof must be defined "
+ "consecutively. \"baz\" cannot be defined before the completion of the "
+ "\"foos\" oneof definition.\n");
+}
+
+TEST_F(ValidationErrorTest, FieldNumberConflict) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name: \"foo\" number: 1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name: \"bar\" number: 1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ "}",
+
+ "foo.proto: Foo.bar: NUMBER: Field number 1 has already been used in "
+ "\"Foo\" by field \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, BadMessageSetExtensionType) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"MessageSet\""
+ " options { message_set_wire_format: true }"
+ " extension_range { start: 4 end: 5 }"
+ "}"
+ "message_type {"
+ " name: \"Foo\""
+ " extension { name:\"foo\" number:4 label:LABEL_OPTIONAL type:TYPE_INT32"
+ " extendee: \"MessageSet\" }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: Extensions of MessageSets must be optional "
+ "messages.\n");
+}
+
+TEST_F(ValidationErrorTest, BadMessageSetExtensionLabel) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"MessageSet\""
+ " options { message_set_wire_format: true }"
+ " extension_range { start: 4 end: 5 }"
+ "}"
+ "message_type {"
+ " name: \"Foo\""
+ " extension { name:\"foo\" number:4 label:LABEL_REPEATED type:TYPE_MESSAGE"
+ " type_name: \"Foo\" extendee: \"MessageSet\" }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: Extensions of MessageSets must be optional "
+ "messages.\n");
+}
+
+TEST_F(ValidationErrorTest, FieldInMessageSet) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " options { message_set_wire_format: true }"
+ " field { name: \"foo\" number: 1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ "}",
+
+ "foo.proto: Foo.foo: NAME: MessageSets cannot have fields, only "
+ "extensions.\n");
+}
+
+TEST_F(ValidationErrorTest, NegativeExtensionRangeNumber) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " extension_range { start: -10 end: -1 }"
+ "}",
+
+ "foo.proto: Foo: NUMBER: Extension numbers must be positive integers.\n");
+}
+
+TEST_F(ValidationErrorTest, HugeExtensionRangeNumber) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " extension_range { start: 1 end: 0x70000000 }"
+ "}",
+
+ "foo.proto: Foo: NUMBER: Extension numbers cannot be greater than "
+ "536870911.\n");
+}
+
+TEST_F(ValidationErrorTest, ExtensionRangeEndBeforeStart) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " extension_range { start: 10 end: 10 }"
+ " extension_range { start: 10 end: 5 }"
+ "}",
+
+ "foo.proto: Foo: NUMBER: Extension range end number must be greater than "
+ "start number.\n"
+ "foo.proto: Foo: NUMBER: Extension range end number must be greater than "
+ "start number.\n");
+}
+
+TEST_F(ValidationErrorTest, EmptyEnum) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "enum_type { name: \"Foo\" }"
+ // Also use the empty enum in a message to make sure there are no crashes
+ // during validation (possible if the code attempts to derive a default
+ // value for the field).
+ "message_type {"
+ " name: \"Bar\""
+ " field { name: \"foo\" number: 1 label:LABEL_OPTIONAL type_name:\"Foo\" }"
+ " field { name: \"bar\" number: 2 label:LABEL_OPTIONAL type_name:\"Foo\" "
+ " default_value: \"NO_SUCH_VALUE\" }"
+ "}",
+
+ "foo.proto: Foo: NAME: Enums must contain at least one value.\n"
+ "foo.proto: Bar.bar: DEFAULT_VALUE: Enum type \"Foo\" has no value named "
+ "\"NO_SUCH_VALUE\".\n");
+}
+
+TEST_F(ValidationErrorTest, UndefinedExtendee) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " extension { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_INT32"
+ " extendee: \"Bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: EXTENDEE: \"Bar\" is not defined.\n");
+}
+
+TEST_F(ValidationErrorTest, NonMessageExtendee) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "enum_type { name: \"Bar\" value { name:\"DUMMY\" number:0 } }"
+ "message_type {"
+ " name: \"Foo\""
+ " extension { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_INT32"
+ " extendee: \"Bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: EXTENDEE: \"Bar\" is not a message type.\n");
+}
+
+TEST_F(ValidationErrorTest, NotAnExtensionNumber) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Bar\""
+ "}"
+ "message_type {"
+ " name: \"Foo\""
+ " extension { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_INT32"
+ " extendee: \"Bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: NUMBER: \"Bar\" does not declare 1 as an extension "
+ "number.\n");
+}
+
+TEST_F(ValidationErrorTest, RequiredExtension) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Bar\""
+ " extension_range { start: 1000 end: 10000 }"
+ "}"
+ "message_type {"
+ " name: \"Foo\""
+ " extension {"
+ " name:\"foo\""
+ " number:1000"
+ " label:LABEL_REQUIRED"
+ " type:TYPE_INT32"
+ " extendee: \"Bar\""
+ " }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: Message extensions cannot have required "
+ "fields.\n");
+}
+
+TEST_F(ValidationErrorTest, UndefinedFieldType) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type_name:\"Bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: \"Bar\" is not defined.\n");
+}
+
+TEST_F(ValidationErrorTest, UndefinedFieldTypeWithDefault) {
+ // See b/12533582. Previously this failed because the default value was not
+ // accepted by the parser, which assumed an enum type, leading to an unclear
+ // error message. We want this input to yield a validation error instead,
+ // since the unknown type is the primary problem.
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type_name:\"int\" "
+ " default_value:\"1\" }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: \"int\" is not defined.\n");
+}
+
+TEST_F(ValidationErrorTest, UndefinedNestedFieldType) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " nested_type { name:\"Baz\" }"
+ " field { name:\"foo\" number:1"
+ " label:LABEL_OPTIONAL"
+ " type_name:\"Foo.Baz.Bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: \"Foo.Baz.Bar\" is not defined.\n");
+}
+
+TEST_F(ValidationErrorTest, FieldTypeDefinedInUndeclaredDependency) {
+ BuildFile(
+ "name: \"bar.proto\" "
+ "message_type { name: \"Bar\" } ");
+
+ BuildFileWithErrors(
+ "name: \"foo.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, 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 resolved but
+ // not defined:
+ // message Bar { message Baz {} }
+ // message Foo {
+ // message Bar {
+ // // Placing "message Baz{}" here, or removing Foo.Bar altogether,
+ // // would fix the error.
+ // }
+ // optional Bar.Baz baz = 1;
+ // }
+ // An one point the lookup code incorrectly did not produce an error in this
+ // case, because when looking for Bar.Baz, it would try "Foo.Bar.Baz" first,
+ // fail, and ten try "Bar.Baz" and succeed, even though "Bar" should actually
+ // refer to the inner Bar, not the outer one.
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Bar\""
+ " nested_type { name: \"Baz\" }"
+ "}"
+ "message_type {"
+ " name: \"Foo\""
+ " nested_type { name: \"Bar\" }"
+ " field { name:\"baz\" number:1 label:LABEL_OPTIONAL"
+ " type_name:\"Bar.Baz\" }"
+ "}",
+
+ "foo.proto: Foo.baz: TYPE: \"Bar.Baz\" is resolved to \"Foo.Bar.Baz\","
+ " which is not defined. The innermost scope is searched first in name "
+ "resolution. Consider using a leading '.'(i.e., \".Bar.Baz\") to start "
+ "from the outermost scope.\n");
+}
+
+TEST_F(ValidationErrorTest, SearchMostLocalFirst2) {
+ // This test would find the most local "Bar" first, and does, but
+ // proceeds to find the outer one because the inner one's not an
+ // aggregate.
+ BuildFile(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Bar\""
+ " nested_type { name: \"Baz\" }"
+ "}"
+ "message_type {"
+ " name: \"Foo\""
+ " field { name: \"Bar\" number:1 type:TYPE_BYTES } "
+ " field { name:\"baz\" number:2 label:LABEL_OPTIONAL"
+ " type_name:\"Bar.Baz\" }"
+ "}");
+}
+
+TEST_F(ValidationErrorTest, PackageOriginallyDeclaredInTransitiveDependent) {
+ // Imagine we have the following:
+ //
+ // foo.proto:
+ // package foo.bar;
+ // bar.proto:
+ // package foo.bar;
+ // import "foo.proto";
+ // message Bar {}
+ // baz.proto:
+ // package foo;
+ // import "bar.proto"
+ // message Baz { optional bar.Bar qux = 1; }
+ //
+ // When validating baz.proto, we will look up "bar.Bar". As part of this
+ // lookup, we first lookup "bar" then try to find "Bar" within it. "bar"
+ // should resolve to "foo.bar". Note, though, that "foo.bar" was originally
+ // defined in foo.proto, which is not a direct dependency of baz.proto. The
+ // implementation of FindSymbol() normally only returns symbols in direct
+ // dependencies, not indirect ones. This test insures that this does not
+ // prevent it from finding "foo.bar".
+
+ BuildFile(
+ "name: \"foo.proto\" "
+ "package: \"foo.bar\" ");
+ BuildFile(
+ "name: \"bar.proto\" "
+ "package: \"foo.bar\" "
+ "dependency: \"foo.proto\" "
+ "message_type { name: \"Bar\" }");
+ BuildFile(
+ "name: \"baz.proto\" "
+ "package: \"foo\" "
+ "dependency: \"bar.proto\" "
+ "message_type { "
+ " name: \"Baz\" "
+ " field { name:\"qux\" number:1 label:LABEL_OPTIONAL "
+ " type_name:\"bar.Bar\" }"
+ "}");
+}
+
+TEST_F(ValidationErrorTest, FieldTypeNotAType) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL "
+ " type_name:\".Foo.bar\" }"
+ " field { name:\"bar\" number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: \".Foo.bar\" is not a type.\n");
+}
+
+TEST_F(ValidationErrorTest, RelativeFieldTypeNotAType) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " nested_type {"
+ " name: \"Bar\""
+ " field { name:\"Baz\" number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " }"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL "
+ " type_name:\"Bar.Baz\" }"
+ "}",
+ "foo.proto: Foo.foo: TYPE: \"Bar.Baz\" is not a type.\n");
+}
+
+TEST_F(ValidationErrorTest, FieldTypeMayBeItsName) {
+ BuildFile(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Bar\""
+ "}"
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"Bar\" number:1 label:LABEL_OPTIONAL type_name:\"Bar\" }"
+ "}");
+}
+
+TEST_F(ValidationErrorTest, EnumFieldTypeIsMessage) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Bar\" } "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_ENUM"
+ " type_name:\"Bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: \"Bar\" is not an enum type.\n");
+}
+
+TEST_F(ValidationErrorTest, MessageFieldTypeIsEnum) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "enum_type { name: \"Bar\" value { name:\"DUMMY\" number:0 } } "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE"
+ " type_name:\"Bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: \"Bar\" is not a message type.\n");
+}
+
+TEST_F(ValidationErrorTest, BadEnumDefaultValue) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "enum_type { name: \"Bar\" value { name:\"DUMMY\" number:0 } } "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type_name:\"Bar\""
+ " default_value:\"NO_SUCH_VALUE\" }"
+ "}",
+
+ "foo.proto: Foo.foo: DEFAULT_VALUE: Enum type \"Bar\" has no value named "
+ "\"NO_SUCH_VALUE\".\n");
+}
+
+TEST_F(ValidationErrorTest, EnumDefaultValueIsInteger) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "enum_type { name: \"Bar\" value { name:\"DUMMY\" number:0 } } "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type_name:\"Bar\""
+ " default_value:\"0\" }"
+ "}",
+
+ "foo.proto: Foo.foo: DEFAULT_VALUE: Default value for an enum field must "
+ "be an identifier.\n");
+}
+
+TEST_F(ValidationErrorTest, PrimitiveWithTypeName) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_INT32"
+ " type_name:\"Foo\" }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: Field with primitive type has type_name.\n");
+}
+
+TEST_F(ValidationErrorTest, NonPrimitiveWithoutTypeName) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_MESSAGE }"
+ "}",
+
+ "foo.proto: Foo.foo: TYPE: Field with message or enum type missing "
+ "type_name.\n");
+}
+
+TEST_F(ValidationErrorTest, OneofWithNoFields) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " oneof_decl { name:\"bar\" }"
+ "}",
+
+ "foo.proto: Foo.bar: NAME: Oneof must have at least one field.\n");
+}
+
+TEST_F(ValidationErrorTest, OneofLabelMismatch) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"Foo\""
+ " field { name:\"foo\" number:1 label:LABEL_REPEATED type:TYPE_INT32 "
+ " oneof_index:0 }"
+ " oneof_decl { name:\"bar\" }"
+ "}",
+
+ "foo.proto: Foo.foo: NAME: Fields of oneofs must themselves have label "
+ "LABEL_OPTIONAL.\n");
+}
+
+TEST_F(ValidationErrorTest, InputTypeNotDefined) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" } "
+ "service {"
+ " name: \"TestService\""
+ " method { name: \"A\" input_type: \"Bar\" output_type: \"Foo\" }"
+ "}",
+
+ "foo.proto: TestService.A: INPUT_TYPE: \"Bar\" is not defined.\n"
+ );
+}
+
+TEST_F(ValidationErrorTest, InputTypeNotAMessage) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" } "
+ "enum_type { name: \"Bar\" value { name:\"DUMMY\" number:0 } } "
+ "service {"
+ " name: \"TestService\""
+ " method { name: \"A\" input_type: \"Bar\" output_type: \"Foo\" }"
+ "}",
+
+ "foo.proto: TestService.A: INPUT_TYPE: \"Bar\" is not a message type.\n"
+ );
+}
+
+TEST_F(ValidationErrorTest, OutputTypeNotDefined) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" } "
+ "service {"
+ " name: \"TestService\""
+ " method { name: \"A\" input_type: \"Foo\" output_type: \"Bar\" }"
+ "}",
+
+ "foo.proto: TestService.A: OUTPUT_TYPE: \"Bar\" is not defined.\n"
+ );
+}
+
+TEST_F(ValidationErrorTest, OutputTypeNotAMessage) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" } "
+ "enum_type { name: \"Bar\" value { name:\"DUMMY\" number:0 } } "
+ "service {"
+ " name: \"TestService\""
+ " method { name: \"A\" input_type: \"Foo\" output_type: \"Bar\" }"
+ "}",
+
+ "foo.proto: TestService.A: OUTPUT_TYPE: \"Bar\" is not a message type.\n"
+ );
+}
+
+
+TEST_F(ValidationErrorTest, IllegalPackedField) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {\n"
+ " name: \"Foo\""
+ " field { name:\"packed_string\" number:1 label:LABEL_REPEATED "
+ " type:TYPE_STRING "
+ " options { uninterpreted_option {"
+ " name { name_part: \"packed\" is_extension: false }"
+ " identifier_value: \"true\" }}}\n"
+ " field { name:\"packed_message\" number:3 label:LABEL_REPEATED "
+ " type_name: \"Foo\""
+ " options { uninterpreted_option {"
+ " name { name_part: \"packed\" is_extension: false }"
+ " identifier_value: \"true\" }}}\n"
+ " field { name:\"optional_int32\" number: 4 label: LABEL_OPTIONAL "
+ " type:TYPE_INT32 "
+ " options { uninterpreted_option {"
+ " name { name_part: \"packed\" is_extension: false }"
+ " identifier_value: \"true\" }}}\n"
+ "}",
+
+ "foo.proto: Foo.packed_string: TYPE: [packed = true] can only be "
+ "specified for repeated primitive fields.\n"
+ "foo.proto: Foo.packed_message: TYPE: [packed = true] can only be "
+ "specified for repeated primitive fields.\n"
+ "foo.proto: Foo.optional_int32: TYPE: [packed = true] can only be "
+ "specified for repeated primitive fields.\n"
+ );
+}
+
+TEST_F(ValidationErrorTest, OptionWrongType) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { "
+ " name: \"TestMessage\" "
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_STRING "
+ " options { uninterpreted_option { name { name_part: \"ctype\" "
+ " is_extension: false }"
+ " positive_int_value: 1 }"
+ " }"
+ " }"
+ "}\n",
+
+ "foo.proto: TestMessage.foo: OPTION_VALUE: Value must be identifier for "
+ "enum-valued option \"google.protobuf.FieldOptions.ctype\".\n");
+}
+
+TEST_F(ValidationErrorTest, OptionExtendsAtomicType) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { "
+ " name: \"TestMessage\" "
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_STRING "
+ " options { uninterpreted_option { name { name_part: \"ctype\" "
+ " is_extension: false }"
+ " name { name_part: \"foo\" "
+ " is_extension: true }"
+ " positive_int_value: 1 }"
+ " }"
+ " }"
+ "}\n",
+
+ "foo.proto: TestMessage.foo: OPTION_NAME: Option \"ctype\" is an "
+ "atomic type, not a message.\n");
+}
+
+TEST_F(ValidationErrorTest, DupOption) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { "
+ " name: \"TestMessage\" "
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_UINT32 "
+ " options { uninterpreted_option { name { name_part: \"ctype\" "
+ " is_extension: false }"
+ " identifier_value: \"CORD\" }"
+ " uninterpreted_option { name { name_part: \"ctype\" "
+ " is_extension: false }"
+ " identifier_value: \"CORD\" }"
+ " }"
+ " }"
+ "}\n",
+
+ "foo.proto: TestMessage.foo: OPTION_NAME: Option \"ctype\" was "
+ "already set.\n");
+}
+
+TEST_F(ValidationErrorTest, InvalidOptionName) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type { "
+ " name: \"TestMessage\" "
+ " field { name:\"foo\" number:1 label:LABEL_OPTIONAL type:TYPE_BOOL "
+ " options { uninterpreted_option { "
+ " name { name_part: \"uninterpreted_option\" "
+ " is_extension: false }"
+ " positive_int_value: 1 "
+ " }"
+ " }"
+ " }"
+ "}\n",
+
+ "foo.proto: TestMessage.foo: OPTION_NAME: Option must not use "
+ "reserved name \"uninterpreted_option\".\n");
+}
+
+TEST_F(ValidationErrorTest, RepeatedMessageOption) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "message_type: { name: \"Bar\" field: { "
+ " name: \"foo\" number: 1 label: LABEL_OPTIONAL type: TYPE_INT32 } "
+ "} "
+ "extension { name: \"bar\" number: 7672757 label: LABEL_REPEATED "
+ " type: TYPE_MESSAGE type_name: \"Bar\" "
+ " extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"bar\" "
+ " is_extension: true } "
+ " name { name_part: \"foo\" "
+ " is_extension: false } "
+ " positive_int_value: 1 } }",
+
+ "foo.proto: foo.proto: OPTION_NAME: Option field \"(bar)\" is a "
+ "repeated message. Repeated message options must be initialized "
+ "using an aggregate value.\n");
+}
+
+TEST_F(ValidationErrorTest, ResolveUndefinedOption) {
+ // The following should produce an eror that baz.bar is resolved but not
+ // defined.
+ // foo.proto:
+ // package baz
+ // import google/protobuf/descriptor.proto
+ // message Bar { optional int32 foo = 1; }
+ // extend FileOptions { optional Bar bar = 7672757; }
+ //
+ // qux.proto:
+ // package qux.baz
+ // option (baz.bar).foo = 1;
+ //
+ // Although "baz.bar" is already defined, the lookup code will try
+ // "qux.baz.bar", since it's the match from the innermost scope, which will
+ // cause a symbol not defined error.
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFile(
+ "name: \"foo.proto\" "
+ "package: \"baz\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "message_type: { name: \"Bar\" field: { "
+ " name: \"foo\" number: 1 label: LABEL_OPTIONAL type: TYPE_INT32 } "
+ "} "
+ "extension { name: \"bar\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_MESSAGE type_name: \"Bar\" "
+ " extendee: \"google.protobuf.FileOptions\" }");
+
+ BuildFileWithErrors(
+ "name: \"qux.proto\" "
+ "package: \"qux.baz\" "
+ "options { uninterpreted_option { name { name_part: \"baz.bar\" "
+ " is_extension: true } "
+ " name { name_part: \"foo\" "
+ " is_extension: false } "
+ " positive_int_value: 1 } }",
+
+ "qux.proto: qux.proto: OPTION_NAME: Option \"(baz.bar)\" is resolved to "
+ "\"(qux.baz.bar)\","
+ " which is not defined. The innermost scope is searched first in name "
+ "resolution. Consider using a leading '.'(i.e., \"(.baz.bar)\") to start "
+ "from the outermost scope.\n");
+}
+
+TEST_F(ValidationErrorTest, UnknownOption) {
+ BuildFileWithErrors(
+ "name: \"qux.proto\" "
+ "package: \"qux.baz\" "
+ "options { uninterpreted_option { name { name_part: \"baaz.bar\" "
+ " is_extension: true } "
+ " name { name_part: \"foo\" "
+ " is_extension: false } "
+ " positive_int_value: 1 } }",
+
+ "qux.proto: qux.proto: OPTION_NAME: Option \"(baaz.bar)\" unknown.\n");
+}
+
+TEST_F(ValidationErrorTest, CustomOptionConflictingFieldNumber) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo1\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 extendee: \"google.protobuf.FieldOptions\" }"
+ "extension { name: \"foo2\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 extendee: \"google.protobuf.FieldOptions\" }",
+
+ "foo.proto: foo2: NUMBER: Extension number 7672757 has already been used "
+ "in \"google.protobuf.FieldOptions\" by extension \"foo1\".\n");
+}
+
+TEST_F(ValidationErrorTest, Int32OptionValueOutOfPositiveRange) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " positive_int_value: 0x80000000 } "
+ "}",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value out of range "
+ "for int32 option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, Int32OptionValueOutOfNegativeRange) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " negative_int_value: -0x80000001 } "
+ "}",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value out of range "
+ "for int32 option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, Int32OptionValueIsNotPositiveInt) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " string_value: \"5\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be integer "
+ "for int32 option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, Int64OptionValueOutOfRange) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_INT64 extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " positive_int_value: 0x8000000000000000 } "
+ "}",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value out of range "
+ "for int64 option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, Int64OptionValueIsNotPositiveInt) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_INT64 extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " identifier_value: \"5\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be integer "
+ "for int64 option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, UInt32OptionValueOutOfRange) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_UINT32 extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " positive_int_value: 0x100000000 } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value out of range "
+ "for uint32 option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, UInt32OptionValueIsNotPositiveInt) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_UINT32 extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " double_value: -5.6 } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be non-negative integer "
+ "for uint32 option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, UInt64OptionValueIsNotPositiveInt) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_UINT64 extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " negative_int_value: -5 } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be non-negative integer "
+ "for uint64 option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, FloatOptionValueIsNotNumber) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_FLOAT extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " string_value: \"bar\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be number "
+ "for float option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, DoubleOptionValueIsNotNumber) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_DOUBLE extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " string_value: \"bar\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be number "
+ "for double option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, BoolOptionValueIsNotTrueOrFalse) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_BOOL extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " identifier_value: \"bar\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be \"true\" or \"false\" "
+ "for boolean option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, EnumOptionValueIsNotIdentifier) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "enum_type { name: \"FooEnum\" value { name: \"BAR\" number: 1 } "
+ " value { name: \"BAZ\" number: 2 } }"
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_ENUM type_name: \"FooEnum\" "
+ " extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " string_value: \"QUUX\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be identifier for "
+ "enum-valued option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, EnumOptionValueIsNotEnumValueName) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "enum_type { name: \"FooEnum\" value { name: \"BAR\" number: 1 } "
+ " value { name: \"BAZ\" number: 2 } }"
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_ENUM type_name: \"FooEnum\" "
+ " extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " identifier_value: \"QUUX\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Enum type \"FooEnum\" has no value "
+ "named \"QUUX\" for option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, EnumOptionValueIsSiblingEnumValueName) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "enum_type { name: \"FooEnum1\" value { name: \"BAR\" number: 1 } "
+ " value { name: \"BAZ\" number: 2 } }"
+ "enum_type { name: \"FooEnum2\" value { name: \"QUX\" number: 1 } "
+ " value { name: \"QUUX\" number: 2 } }"
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_ENUM type_name: \"FooEnum1\" "
+ " extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " identifier_value: \"QUUX\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Enum type \"FooEnum1\" has no value "
+ "named \"QUUX\" for option \"foo\". This appears to be a value from a "
+ "sibling type.\n");
+}
+
+TEST_F(ValidationErrorTest, StringOptionValueIsNotString) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_STRING extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " identifier_value: \"QUUX\" } }",
+
+ "foo.proto: foo.proto: OPTION_VALUE: Value must be quoted string for "
+ "string option \"foo\".\n");
+}
+
+TEST_F(ValidationErrorTest, DuplicateExtensionFieldNumber) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFile(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"option1\" number: 1000 label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 extendee: \"google.protobuf.FileOptions\" }");
+
+ BuildFileWithWarnings(
+ "name: \"bar.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "extension { name: \"option2\" number: 1000 label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 extendee: \"google.protobuf.FileOptions\" }",
+ "bar.proto: option2: NUMBER: Extension number 1000 has already been used "
+ "in \"google.protobuf.FileOptions\" by extension \"option1\" defined in "
+ "foo.proto.\n");
+}
+
+// Helper function for tests that check for aggregate value parsing
+// errors. The "value" argument is embedded inside the
+// "uninterpreted_option" portion of the result.
+static string EmbedAggregateValue(const char* value) {
+ return strings::Substitute(
+ "name: \"foo.proto\" "
+ "dependency: \"google/protobuf/descriptor.proto\" "
+ "message_type { name: \"Foo\" } "
+ "extension { name: \"foo\" number: 7672757 label: LABEL_OPTIONAL "
+ " type: TYPE_MESSAGE type_name: \"Foo\" "
+ " extendee: \"google.protobuf.FileOptions\" }"
+ "options { uninterpreted_option { name { name_part: \"foo\" "
+ " is_extension: true } "
+ " $0 } }",
+ value);
+}
+
+TEST_F(ValidationErrorTest, AggregateValueNotFound) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ EmbedAggregateValue("string_value: \"\""),
+ "foo.proto: foo.proto: OPTION_VALUE: Option \"foo\" is a message. "
+ "To set the entire message, use syntax like "
+ "\"foo = { <proto text format> }\". To set fields within it, use "
+ "syntax like \"foo.foo = value\".\n");
+}
+
+TEST_F(ValidationErrorTest, AggregateValueParseError) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ EmbedAggregateValue("aggregate_value: \"1+2\""),
+ "foo.proto: foo.proto: OPTION_VALUE: Error while parsing option "
+ "value for \"foo\": Expected identifier, got: 1\n");
+}
+
+TEST_F(ValidationErrorTest, AggregateValueUnknownFields) {
+ BuildDescriptorMessagesInTestPool();
+
+ BuildFileWithErrors(
+ EmbedAggregateValue("aggregate_value: \"x:100\""),
+ "foo.proto: foo.proto: OPTION_VALUE: Error while parsing option "
+ "value for \"foo\": Message type \"Foo\" has no field named \"x\".\n");
+}
+
+TEST_F(ValidationErrorTest, NotLiteImportsLite) {
+ BuildFile(
+ "name: \"bar.proto\" "
+ "options { optimize_for: LITE_RUNTIME } ");
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"bar.proto\" ",
+
+ "foo.proto: foo.proto: OTHER: Files that do not use optimize_for = "
+ "LITE_RUNTIME cannot import files which do use this option. This file "
+ "is not lite, but it imports \"bar.proto\" which is.\n");
+}
+
+TEST_F(ValidationErrorTest, LiteExtendsNotLite) {
+ BuildFile(
+ "name: \"bar.proto\" "
+ "message_type: {"
+ " name: \"Bar\""
+ " extension_range { start: 1 end: 1000 }"
+ "}");
+
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "dependency: \"bar.proto\" "
+ "options { optimize_for: LITE_RUNTIME } "
+ "extension { name: \"ext\" number: 123 label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 extendee: \"Bar\" }",
+
+ "foo.proto: ext: EXTENDEE: Extensions to non-lite types can only be "
+ "declared in non-lite files. Note that you cannot extend a non-lite "
+ "type to contain a lite type, but the reverse is allowed.\n");
+}
+
+TEST_F(ValidationErrorTest, NoLiteServices) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "options {"
+ " optimize_for: LITE_RUNTIME"
+ " cc_generic_services: true"
+ " java_generic_services: true"
+ "} "
+ "service { name: \"Foo\" }",
+
+ "foo.proto: Foo: NAME: Files with optimize_for = LITE_RUNTIME cannot "
+ "define services unless you set both options cc_generic_services and "
+ "java_generic_sevices to false.\n");
+
+ BuildFile(
+ "name: \"bar.proto\" "
+ "options {"
+ " optimize_for: LITE_RUNTIME"
+ " cc_generic_services: false"
+ " java_generic_services: false"
+ "} "
+ "service { name: \"Bar\" }");
+}
+
+TEST_F(ValidationErrorTest, RollbackAfterError) {
+ // Build a file which contains every kind of construct but references an
+ // undefined type. All these constructs will be added to the symbol table
+ // before the undefined type error is noticed. The DescriptorPool will then
+ // have to roll everything back.
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"TestMessage\""
+ " field { name:\"foo\" label:LABEL_OPTIONAL type:TYPE_INT32 number:1 }"
+ "} "
+ "enum_type {"
+ " name: \"TestEnum\""
+ " value { name:\"BAR\" number:1 }"
+ "} "
+ "service {"
+ " name: \"TestService\""
+ " method {"
+ " name: \"Baz\""
+ " input_type: \"NoSuchType\"" // error
+ " output_type: \"TestMessage\""
+ " }"
+ "}",
+
+ "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
+ // be left defined, and this second attempt will fail since it tries to
+ // re-define the same symbols.
+ BuildFile(
+ "name: \"foo.proto\" "
+ "message_type {"
+ " name: \"TestMessage\""
+ " field { name:\"foo\" label:LABEL_OPTIONAL type:TYPE_INT32 number:1 }"
+ "} "
+ "enum_type {"
+ " name: \"TestEnum\""
+ " value { name:\"BAR\" number:1 }"
+ "} "
+ "service {"
+ " name: \"TestService\""
+ " method { name:\"Baz\""
+ " input_type:\"TestMessage\""
+ " output_type:\"TestMessage\" }"
+ "}");
+}
+
+TEST_F(ValidationErrorTest, ErrorsReportedToLogError) {
+ // Test that errors are reported to GOOGLE_LOG(ERROR) if no error collector is
+ // provided.
+
+ FileDescriptorProto file_proto;
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: \"foo.proto\" "
+ "message_type { name: \"Foo\" } "
+ "message_type { name: \"Foo\" } ",
+ &file_proto));
+
+ vector<string> errors;
+
+ {
+ ScopedMemoryLog log;
+ EXPECT_TRUE(pool_.BuildFile(file_proto) == NULL);
+ errors = log.GetMessages(ERROR);
+ }
+
+ ASSERT_EQ(2, errors.size());
+
+ EXPECT_EQ("Invalid proto descriptor for file \"foo.proto\":", errors[0]);
+ 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 }"
+ "}",
+ "foo.proto: Bar: NUMBER: "
+ "\"ENUM_B\" uses the same enum value as \"ENUM_A\". "
+ "If this is intended, set 'option allow_alias = true;' to the enum "
+ "definition.\n");
+}
+
+TEST_F(ValidationErrorTest, AllowEnumAlias) {
+ BuildFile(
+ "name: \"foo.proto\" "
+ "enum_type {"
+ " name: \"Bar\""
+ " value { name:\"ENUM_A\" number:0 }"
+ " value { name:\"ENUM_B\" number:0 }"
+ " options { allow_alias: true }"
+ "}");
+}
+
+TEST_F(ValidationErrorTest, UnusedImportWarning) {
+ pool_.AddUnusedImportTrackFile("bar.proto");
+ BuildFile(
+ "name: \"bar.proto\" "
+ "message_type { name: \"Bar\" }");
+
+ pool_.AddUnusedImportTrackFile("base.proto");
+ BuildFile(
+ "name: \"base.proto\" "
+ "message_type { name: \"Base\" }");
+
+ pool_.AddUnusedImportTrackFile("baz.proto");
+ BuildFile(
+ "name: \"baz.proto\" "
+ "message_type { name: \"Baz\" }");
+
+ pool_.AddUnusedImportTrackFile("public.proto");
+ BuildFile(
+ "name: \"public.proto\" "
+ "dependency: \"bar.proto\""
+ "public_dependency: 0");
+
+ // // forward.proto
+ // import "base.proto" // No warning: Base message is used.
+ // import "bar.proto" // Will log a warning.
+ // import public "baz.proto" // No warning: Do not track import public.
+ // import "public.proto" // No warning: public.proto has import public.
+ // message Forward {
+ // optional Base base = 1;
+ // }
+ //
+ pool_.AddUnusedImportTrackFile("forward.proto");
+ BuildFileWithWarnings(
+ "name: \"forward.proto\""
+ "dependency: \"base.proto\""
+ "dependency: \"bar.proto\""
+ "dependency: \"baz.proto\""
+ "dependency: \"public.proto\""
+ "public_dependency: 2 "
+ "message_type {"
+ " name: \"Forward\""
+ " field { name:\"base\" number:1 label:LABEL_OPTIONAL type_name:\"Base\" }"
+ "}",
+ "forward.proto: bar.proto: OTHER: Import bar.proto but not used.\n");
+}
+
+namespace {
+void FillValidMapEntry(FileDescriptorProto* file_proto) {
+ ASSERT_TRUE(TextFormat::ParseFromString(
+ "name: 'foo.proto' "
+ "message_type { "
+ " name: 'Foo' "
+ " field { "
+ " name: 'foo_map' number: 1 label:LABEL_REPEATED "
+ " type_name: 'FooMapEntry' "
+ " } "
+ " nested_type { "
+ " name: 'FooMapEntry' "
+ " options { map_entry: true } "
+ " field { "
+ " name: 'key' number: 1 type:TYPE_INT32 label:LABEL_OPTIONAL "
+ " } "
+ " field { "
+ " name: 'value' number: 2 type:TYPE_INT32 label:LABEL_OPTIONAL "
+ " } "
+ " } "
+ "} "
+ "message_type { "
+ " name: 'Bar' "
+ " extension_range { start: 1 end: 10 }"
+ "} ",
+ file_proto));
+}
+static const char* kMapEntryErrorMessage =
+ "foo.proto: Foo.foo_map: OTHER: map_entry should not be set explicitly. "
+ "Use map<KeyType, ValueType> instead.\n";
+static const char* kMapEntryKeyTypeErrorMessage =
+ "foo.proto: Foo.foo_map: TYPE: Key in map fields cannot be float/double, "
+ "bytes or message types.\n";
+
+} // namespace
+
+TEST_F(ValidationErrorTest, MapEntryBase) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ BuildFile(file_proto.DebugString());
+}
+
+TEST_F(ValidationErrorTest, MapEntryExtensionRange) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "extension_range { "
+ " start: 10 end: 20 "
+ "} ",
+ file_proto.mutable_message_type(0)->mutable_nested_type(0));
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryExtension) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "extension { "
+ " name: 'foo_ext' extendee: '.Bar' number: 5"
+ "} ",
+ file_proto.mutable_message_type(0)->mutable_nested_type(0));
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryNestedType) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "nested_type { "
+ " name: 'Bar' "
+ "} ",
+ file_proto.mutable_message_type(0)->mutable_nested_type(0));
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryEnumTypes) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "enum_type { "
+ " name: 'BarEnum' "
+ " value { name: 'BAR_BAR' number:0 } "
+ "} ",
+ file_proto.mutable_message_type(0)->mutable_nested_type(0));
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryExtraField) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "field { "
+ " name: 'other_field' "
+ " label: LABEL_OPTIONAL "
+ " type: TYPE_INT32 "
+ " number: 3 "
+ "} ",
+ file_proto.mutable_message_type(0)->mutable_nested_type(0));
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryMessageName) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ file_proto.mutable_message_type(0)->mutable_nested_type(0)->set_name(
+ "OtherMapEntry");
+ file_proto.mutable_message_type(0)->mutable_field(0)->set_type_name(
+ "OtherMapEntry");
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryNoneRepeatedMapEntry) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ file_proto.mutable_message_type(0)->mutable_field(0)->set_label(
+ FieldDescriptorProto::LABEL_OPTIONAL);
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryDifferentContainingType) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ // Move the nested MapEntry message into the top level, which should not pass
+ // the validation.
+ file_proto.mutable_message_type()->AddAllocated(
+ file_proto.mutable_message_type(0)->mutable_nested_type()->ReleaseLast());
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryKeyName) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* key = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(0);
+ key->set_name("Key");
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryKeyLabel) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* key = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(0);
+ key->set_label(FieldDescriptorProto::LABEL_REQUIRED);
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryKeyNumber) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* key = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(0);
+ key->set_number(3);
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryValueName) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* value = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(1);
+ value->set_name("Value");
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryValueLabel) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* value = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(1);
+ value->set_label(FieldDescriptorProto::LABEL_REQUIRED);
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryValueNumber) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* value = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(1);
+ value->set_number(3);
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryKeyTypeFloat) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* key = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(0);
+ key->set_type(FieldDescriptorProto::TYPE_FLOAT);
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryKeyTypeErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryKeyTypeDouble) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* key = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(0);
+ key->set_type(FieldDescriptorProto::TYPE_DOUBLE);
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryKeyTypeErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryKeyTypeBytes) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* key = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(0);
+ key->set_type(FieldDescriptorProto::TYPE_BYTES);
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryKeyTypeErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryKeyTypeEnum) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* key = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(0);
+ key->clear_type();
+ key->set_type_name("BarEnum");
+ EnumDescriptorProto* enum_proto = file_proto.add_enum_type();
+ enum_proto->set_name("BarEnum");
+ EnumValueDescriptorProto* enum_value_proto = enum_proto->add_value();
+ enum_value_proto->set_name("BAR_VALUE0");
+ enum_value_proto->set_number(0);
+ BuildFileWithErrors(file_proto.DebugString(),
+ "foo.proto: Foo.foo_map: TYPE: Key in map fields cannot "
+ "be enum types.\n");
+ // Enum keys are not allowed in proto3 as well.
+ // Get rid of extensions for proto3 to make it proto3 compatible.
+ file_proto.mutable_message_type()->RemoveLast();
+ file_proto.set_syntax("proto3");
+ BuildFileWithErrors(file_proto.DebugString(),
+ "foo.proto: Foo.foo_map: TYPE: Key in map fields cannot "
+ "be enum types.\n");
+}
+
+
+TEST_F(ValidationErrorTest, MapEntryKeyTypeMessage) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ FieldDescriptorProto* key = file_proto.mutable_message_type(0)
+ ->mutable_nested_type(0)
+ ->mutable_field(0);
+ key->clear_type();
+ key->set_type_name(".Bar");
+ BuildFileWithErrors(file_proto.DebugString(), kMapEntryKeyTypeErrorMessage);
+}
+
+TEST_F(ValidationErrorTest, MapEntryConflictsWithField) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "field { "
+ " name: 'FooMapEntry' "
+ " type: TYPE_INT32 "
+ " label: LABEL_OPTIONAL "
+ " number: 100 "
+ "}",
+ file_proto.mutable_message_type(0));
+ BuildFileWithErrors(
+ file_proto.DebugString(),
+ "foo.proto: Foo.FooMapEntry: NAME: \"FooMapEntry\" is already defined in "
+ "\"Foo\".\n"
+ "foo.proto: Foo.foo_map: TYPE: \"FooMapEntry\" is not defined.\n"
+ "foo.proto: Foo: NAME: Expanded map entry type FooMapEntry conflicts "
+ "with an existing field.\n");
+}
+
+TEST_F(ValidationErrorTest, MapEntryConflictsWithMessage) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "nested_type { "
+ " name: 'FooMapEntry' "
+ "}",
+ file_proto.mutable_message_type(0));
+ BuildFileWithErrors(
+ file_proto.DebugString(),
+ "foo.proto: Foo.FooMapEntry: NAME: \"FooMapEntry\" is already defined in "
+ "\"Foo\".\n"
+ "foo.proto: Foo: NAME: Expanded map entry type FooMapEntry conflicts "
+ "with an existing nested message type.\n");
+}
+
+TEST_F(ValidationErrorTest, MapEntryConflictsWithEnum) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "enum_type { "
+ " name: 'FooMapEntry' "
+ " value { name: 'ENTRY_FOO' number: 0 }"
+ "}",
+ file_proto.mutable_message_type(0));
+ BuildFileWithErrors(
+ file_proto.DebugString(),
+ "foo.proto: Foo.FooMapEntry: NAME: \"FooMapEntry\" is already defined in "
+ "\"Foo\".\n"
+ "foo.proto: Foo: NAME: Expanded map entry type FooMapEntry conflicts "
+ "with an existing enum type.\n");
+}
+
+TEST_F(ValidationErrorTest, MapEntryConflictsWithOneof) {
+ FileDescriptorProto file_proto;
+ FillValidMapEntry(&file_proto);
+ TextFormat::MergeFromString(
+ "oneof_decl { "
+ " name: 'FooMapEntry' "
+ "}"
+ "field { "
+ " name: 'int_field' "
+ " type: TYPE_INT32 "
+ " label: LABEL_OPTIONAL "
+ " oneof_index: 0 "
+ " number: 100 "
+ "} ",
+ file_proto.mutable_message_type(0));
+ BuildFileWithErrors(
+ file_proto.DebugString(),
+ "foo.proto: Foo.FooMapEntry: NAME: \"FooMapEntry\" is already defined in "
+ "\"Foo\".\n"
+ "foo.proto: Foo.foo_map: TYPE: \"FooMapEntry\" is not defined.\n"
+ "foo.proto: Foo: NAME: Expanded map entry type FooMapEntry conflicts "
+ "with an existing oneof type.\n");
+}
+
+TEST_F(ValidationErrorTest, MapEntryUsesNoneZeroEnumDefaultValue) {
+ BuildFileWithErrors(
+ "name: \"foo.proto\" "
+ "enum_type {"
+ " name: \"Bar\""
+ " value { name:\"ENUM_A\" number:1 }"
+ " value { name:\"ENUM_B\" number:2 }"
+ "}"
+ "message_type {"
+ " name: 'Foo' "
+ " field { "
+ " name: 'foo_map' number: 1 label:LABEL_REPEATED "
+ " type_name: 'FooMapEntry' "
+ " } "
+ " nested_type { "
+ " name: 'FooMapEntry' "
+ " options { map_entry: true } "
+ " field { "
+ " name: 'key' number: 1 type:TYPE_INT32 label:LABEL_OPTIONAL "
+ " } "
+ " field { "
+ " name: 'value' number: 2 type_name:\"Bar\" label:LABEL_OPTIONAL "
+ " } "
+ " } "
+ "}",
+ "foo.proto: Foo.foo_map: "
+ "TYPE: Enum value in map must define 0 as the first value.\n");
+}
+
+TEST_F(ValidationErrorTest, Proto3RequiredFields) {
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " field { name:'foo' number:1 label:LABEL_REQUIRED type:TYPE_INT32 } "
+ "}",
+ "foo.proto: Foo.foo: OTHER: Required fields are not allowed in "
+ "proto3.\n");
+
+ // applied to nested types as well.
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " nested_type { "
+ " name : 'Bar' "
+ " field { name:'bar' number:1 label:LABEL_REQUIRED type:TYPE_INT32 } "
+ " } "
+ "}",
+ "foo.proto: Foo.Bar.bar: OTHER: Required fields are not allowed in "
+ "proto3.\n");
+
+ // optional and repeated fields are OK.
+ BuildFile(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " field { name:'foo' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 } "
+ " field { name:'bar' number:2 label:LABEL_REPEATED type:TYPE_INT32 } "
+ "}");
+}
+
+TEST_F(ValidationErrorTest, ValidateProto3DefaultValue) {
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " field { name:'foo' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " default_value: '1' }"
+ "}",
+ "foo.proto: Foo.foo: OTHER: Explicit default values are not allowed in "
+ "proto3.\n");
+
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " nested_type { "
+ " name : 'Bar' "
+ " field { name:'bar' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 "
+ " default_value: '1' }"
+ " } "
+ "}",
+ "foo.proto: Foo.Bar.bar: OTHER: Explicit default values are not allowed "
+ "in proto3.\n");
+}
+
+TEST_F(ValidationErrorTest, ValidateProto3ExtensionRange) {
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " field { name:'foo' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 } "
+ " extension_range { start:10 end:100 } "
+ "}",
+ "foo.proto: Foo: OTHER: Extension ranges are not allowed in "
+ "proto3.\n");
+
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " nested_type { "
+ " name : 'Bar' "
+ " field { name:'bar' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 } "
+ " extension_range { start:10 end:100 } "
+ " } "
+ "}",
+ "foo.proto: Foo.Bar: OTHER: Extension ranges are not allowed in "
+ "proto3.\n");
+}
+
+TEST_F(ValidationErrorTest, ValidateProto3MessageSetWireFormat) {
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " options { message_set_wire_format: true } "
+ "}",
+ "foo.proto: Foo: OTHER: MessageSet is not supported "
+ "in proto3.\n");
+}
+
+TEST_F(ValidationErrorTest, ValidateProto3Enum) {
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "enum_type { "
+ " name: 'FooEnum' "
+ " value { name: 'FOO_FOO' number:1 } "
+ "}",
+ "foo.proto: FooEnum: OTHER: The first enum value must be "
+ "zero in proto3.\n");
+
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " enum_type { "
+ " name: 'FooEnum' "
+ " value { name: 'FOO_FOO' number:1 } "
+ " } "
+ "}",
+ "foo.proto: Foo.FooEnum: OTHER: The first enum value must be "
+ "zero in proto3.\n");
+
+ // valid case.
+ BuildFile(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "enum_type { "
+ " name: 'FooEnum' "
+ " value { name: 'FOO_FOO' number:0 } "
+ "}");
+}
+
+TEST_F(ValidationErrorTest, ValidateProto3Group) {
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " nested_type { "
+ " name: 'FooGroup' "
+ " } "
+ " field { name:'foo_group' number: 1 label:LABEL_OPTIONAL "
+ " type: TYPE_GROUP type_name:'FooGroup' } "
+ "}",
+ "foo.proto: Foo.foo_group: TYPE: Groups are not supported in proto3 "
+ "syntax.\n");
+}
+
+
+TEST_F(ValidationErrorTest, ValidateProto3EnumFromProto2) {
+ // Define an enum in a proto2 file.
+ BuildFile(
+ "name: 'foo.proto' "
+ "package: 'foo' "
+ "syntax: 'proto2' "
+ "enum_type { "
+ " name: 'FooEnum' "
+ " value { name: 'DEFAULT_OPTION' number:0 } "
+ "}");
+
+ // Now try to refer to it. (All tests in the fixture use the same pool, so we
+ // can refer to the enum above in this definition.)
+ BuildFileWithErrors(
+ "name: 'bar.proto' "
+ "dependency: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type { "
+ " name: 'Foo' "
+ " field { name:'bar' number:1 label:LABEL_OPTIONAL type:TYPE_ENUM "
+ " type_name: 'foo.FooEnum' }"
+ "}",
+ "bar.proto: Foo.bar: TYPE: Enum type \"foo.FooEnum\" is not a proto3 "
+ "enum, but is used in \"Foo\" which is a proto3 message type.\n");
+}
+
+TEST_F(ValidationErrorTest, ValidateProto3Extension) {
+ // Valid for options.
+ DescriptorPool pool;
+ FileDescriptorProto file_proto;
+ // Add "google/protobuf/descriptor.proto".
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+ // Add "foo.proto":
+ // import "google/protobuf/descriptor.proto";
+ // extend google.protobuf.FieldOptions {
+ // optional int32 option1 = 1000;
+ // }
+ file_proto.Clear();
+ file_proto.set_name("foo.proto");
+ file_proto.set_syntax("proto3");
+ file_proto.add_dependency("google/protobuf/descriptor.proto");
+ AddExtension(&file_proto, "google.protobuf.FieldOptions", "option1", 1000,
+ FieldDescriptorProto::LABEL_OPTIONAL,
+ FieldDescriptorProto::TYPE_INT32);
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL);
+
+ // Copy and change the package of the descriptor.proto
+ BuildFile(
+ "name: 'google.protobuf.proto' "
+ "syntax: 'proto2' "
+ "message_type { "
+ " name: 'Container' extension_range { start: 1 end: 1000 } "
+ "}");
+ BuildFileWithErrors(
+ "name: 'bar.proto' "
+ "syntax: 'proto3' "
+ "dependency: 'google.protobuf.proto' "
+ "extension { "
+ " name: 'bar' number: 1 label: LABEL_OPTIONAL type: TYPE_INT32 "
+ " extendee: 'Container' "
+ "}",
+ "bar.proto: bar: OTHER: Extensions in proto3 are only allowed for "
+ "defining options.\n");
+}
+
+// Test that field names that may conflict in JSON is not allowed by protoc.
+TEST_F(ValidationErrorTest, ValidateProto3JsonName) {
+ // The comparison is case-insensitive.
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type {"
+ " name: 'Foo'"
+ " field { name:'name' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name:'Name' number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ "}",
+ "foo.proto: Foo: OTHER: The JSON camel-case name of field \"Name\" "
+ "conflicts with field \"name\". This is not allowed in proto3.\n");
+ // Underscores are ignored.
+ BuildFileWithErrors(
+ "name: 'foo.proto' "
+ "syntax: 'proto3' "
+ "message_type {"
+ " name: 'Foo'"
+ " field { name:'ab' number:1 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ " field { name:'_a__b_' number:2 label:LABEL_OPTIONAL type:TYPE_INT32 }"
+ "}",
+ "foo.proto: Foo: OTHER: The JSON camel-case name of field \"_a__b_\" "
+ "conflicts with field \"ab\". This is not allowed in proto3.\n");
+}
+
+// ===================================================================
+// DescriptorDatabase
+
+static void AddToDatabase(SimpleDescriptorDatabase* database,
+ const char* file_text) {
+ FileDescriptorProto file_proto;
+ EXPECT_TRUE(TextFormat::ParseFromString(file_text, &file_proto));
+ database->Add(file_proto);
+}
+
+class DatabaseBackedPoolTest : public testing::Test {
+ protected:
+ DatabaseBackedPoolTest() {}
+
+ SimpleDescriptorDatabase database_;
+
+ 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' } ");
+ AddToDatabase(&database_,
+ "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
+ // need an actual mock DescriptorDatabase to test errors.
+ class ErrorDescriptorDatabase : public DescriptorDatabase {
+ public:
+ ErrorDescriptorDatabase() {}
+ ~ErrorDescriptorDatabase() {}
+
+ // implements DescriptorDatabase ---------------------------------
+ bool FindFileByName(const string& filename,
+ FileDescriptorProto* output) {
+ // error.proto and error2.proto cyclically import each other.
+ if (filename == "error.proto") {
+ output->Clear();
+ output->set_name("error.proto");
+ output->add_dependency("error2.proto");
+ return true;
+ } else if (filename == "error2.proto") {
+ output->Clear();
+ output->set_name("error2.proto");
+ output->add_dependency("error.proto");
+ return true;
+ } else {
+ return false;
+ }
+ }
+ bool FindFileContainingSymbol(const string& symbol_name,
+ FileDescriptorProto* output) {
+ return false;
+ }
+ bool FindFileContainingExtension(const string& containing_type,
+ int field_number,
+ FileDescriptorProto* output) {
+ return false;
+ }
+ };
+
+ // A DescriptorDatabase that counts how many times each method has been
+ // called and forwards to some other DescriptorDatabase.
+ class CallCountingDatabase : public DescriptorDatabase {
+ public:
+ CallCountingDatabase(DescriptorDatabase* wrapped_db)
+ : wrapped_db_(wrapped_db) {
+ Clear();
+ }
+ ~CallCountingDatabase() {}
+
+ DescriptorDatabase* wrapped_db_;
+
+ int call_count_;
+
+ void Clear() {
+ call_count_ = 0;
+ }
+
+ // implements DescriptorDatabase ---------------------------------
+ bool FindFileByName(const string& filename,
+ FileDescriptorProto* output) {
+ ++call_count_;
+ return wrapped_db_->FindFileByName(filename, output);
+ }
+ bool FindFileContainingSymbol(const string& symbol_name,
+ FileDescriptorProto* output) {
+ ++call_count_;
+ return wrapped_db_->FindFileContainingSymbol(symbol_name, output);
+ }
+ bool FindFileContainingExtension(const string& containing_type,
+ int field_number,
+ FileDescriptorProto* output) {
+ ++call_count_;
+ return wrapped_db_->FindFileContainingExtension(
+ containing_type, field_number, output);
+ }
+ };
+
+ // A DescriptorDatabase which falsely always returns foo.proto when searching
+ // for any symbol or extension number. This shouldn't cause the
+ // DescriptorPool to reload foo.proto if it is already loaded.
+ class FalsePositiveDatabase : public DescriptorDatabase {
+ public:
+ FalsePositiveDatabase(DescriptorDatabase* wrapped_db)
+ : wrapped_db_(wrapped_db) {}
+ ~FalsePositiveDatabase() {}
+
+ DescriptorDatabase* wrapped_db_;
+
+ // implements DescriptorDatabase ---------------------------------
+ bool FindFileByName(const string& filename,
+ FileDescriptorProto* output) {
+ return wrapped_db_->FindFileByName(filename, output);
+ }
+ bool FindFileContainingSymbol(const string& symbol_name,
+ FileDescriptorProto* output) {
+ return FindFileByName("foo.proto", output);
+ }
+ bool FindFileContainingExtension(const string& containing_type,
+ int field_number,
+ FileDescriptorProto* output) {
+ return FindFileByName("foo.proto", output);
+ }
+ };
+};
+
+TEST_F(DatabaseBackedPoolTest, FindFileByName) {
+ DescriptorPool pool(&database_);
+
+ const FileDescriptor* foo = pool.FindFileByName("foo.proto");
+ ASSERT_TRUE(foo != NULL);
+ EXPECT_EQ("foo.proto", foo->name());
+ ASSERT_EQ(1, foo->message_type_count());
+ EXPECT_EQ("Foo", foo->message_type(0)->name());
+
+ EXPECT_EQ(foo, pool.FindFileByName("foo.proto"));
+
+ EXPECT_TRUE(pool.FindFileByName("no_such_file.proto") == NULL);
+}
+
+TEST_F(DatabaseBackedPoolTest, FindDependencyBeforeDependent) {
+ DescriptorPool pool(&database_);
+
+ const FileDescriptor* foo = pool.FindFileByName("foo.proto");
+ ASSERT_TRUE(foo != NULL);
+ EXPECT_EQ("foo.proto", foo->name());
+ ASSERT_EQ(1, foo->message_type_count());
+ EXPECT_EQ("Foo", foo->message_type(0)->name());
+
+ const FileDescriptor* bar = pool.FindFileByName("bar.proto");
+ ASSERT_TRUE(bar != NULL);
+ EXPECT_EQ("bar.proto", bar->name());
+ ASSERT_EQ(1, bar->message_type_count());
+ EXPECT_EQ("Bar", bar->message_type(0)->name());
+
+ ASSERT_EQ(1, bar->dependency_count());
+ EXPECT_EQ(foo, bar->dependency(0));
+}
+
+TEST_F(DatabaseBackedPoolTest, FindDependentBeforeDependency) {
+ DescriptorPool pool(&database_);
+
+ const FileDescriptor* bar = pool.FindFileByName("bar.proto");
+ ASSERT_TRUE(bar != NULL);
+ EXPECT_EQ("bar.proto", bar->name());
+ ASSERT_EQ(1, bar->message_type_count());
+ ASSERT_EQ("Bar", bar->message_type(0)->name());
+
+ const FileDescriptor* foo = pool.FindFileByName("foo.proto");
+ ASSERT_TRUE(foo != NULL);
+ EXPECT_EQ("foo.proto", foo->name());
+ ASSERT_EQ(1, foo->message_type_count());
+ ASSERT_EQ("Foo", foo->message_type(0)->name());
+
+ ASSERT_EQ(1, bar->dependency_count());
+ EXPECT_EQ(foo, bar->dependency(0));
+}
+
+TEST_F(DatabaseBackedPoolTest, FindFileContainingSymbol) {
+ DescriptorPool pool(&database_);
+
+ const FileDescriptor* file = pool.FindFileContainingSymbol("Foo");
+ ASSERT_TRUE(file != NULL);
+ EXPECT_EQ("foo.proto", file->name());
+ EXPECT_EQ(file, pool.FindFileByName("foo.proto"));
+
+ EXPECT_TRUE(pool.FindFileContainingSymbol("NoSuchSymbol") == NULL);
+}
+
+TEST_F(DatabaseBackedPoolTest, FindMessageTypeByName) {
+ DescriptorPool pool(&database_);
+
+ const Descriptor* type = pool.FindMessageTypeByName("Foo");
+ ASSERT_TRUE(type != NULL);
+ EXPECT_EQ("Foo", type->name());
+ EXPECT_EQ(type->file(), pool.FindFileByName("foo.proto"));
+
+ EXPECT_TRUE(pool.FindMessageTypeByName("NoSuchType") == NULL);
+}
+
+TEST_F(DatabaseBackedPoolTest, FindExtensionByNumber) {
+ DescriptorPool pool(&database_);
+
+ const Descriptor* foo = pool.FindMessageTypeByName("Foo");
+ ASSERT_TRUE(foo != NULL);
+
+ const FieldDescriptor* extension = pool.FindExtensionByNumber(foo, 5);
+ ASSERT_TRUE(extension != NULL);
+ EXPECT_EQ("foo_ext", extension->name());
+ EXPECT_EQ(extension->file(), pool.FindFileByName("bar.proto"));
+
+ EXPECT_TRUE(pool.FindExtensionByNumber(foo, 12) == NULL);
+}
+
+TEST_F(DatabaseBackedPoolTest, FindAllExtensions) {
+ DescriptorPool pool(&database_);
+
+ const Descriptor* foo = pool.FindMessageTypeByName("Foo");
+
+ for (int i = 0; i < 2; ++i) {
+ // Repeat the lookup twice, to check that we get consistent
+ // results despite the fallback database lookup mutating the pool.
+ vector<const FieldDescriptor*> extensions;
+ pool.FindAllExtensions(foo, &extensions);
+ ASSERT_EQ(1, extensions.size());
+ EXPECT_EQ(5, extensions[0]->number());
+ }
+}
+
+TEST_F(DatabaseBackedPoolTest, ErrorWithoutErrorCollector) {
+ ErrorDescriptorDatabase error_database;
+ DescriptorPool pool(&error_database);
+
+ vector<string> errors;
+
+ {
+ ScopedMemoryLog log;
+ EXPECT_TRUE(pool.FindFileByName("error.proto") == NULL);
+ errors = log.GetMessages(ERROR);
+ }
+
+ EXPECT_FALSE(errors.empty());
+}
+
+TEST_F(DatabaseBackedPoolTest, ErrorWithErrorCollector) {
+ ErrorDescriptorDatabase error_database;
+ MockErrorCollector error_collector;
+ DescriptorPool pool(&error_database, &error_collector);
+
+ EXPECT_TRUE(pool.FindFileByName("error.proto") == NULL);
+ EXPECT_EQ(
+ "error.proto: error.proto: OTHER: File recursively imports itself: "
+ "error.proto -> error2.proto -> error.proto\n"
+ "error2.proto: error2.proto: OTHER: Import \"error.proto\" was not "
+ "found or had errors.\n"
+ "error.proto: error.proto: OTHER: Import \"error2.proto\" was not "
+ "found or had errors.\n",
+ 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
+ // are no deadlocking problems when pool_->mutex_ is non-NULL.
+ const FileDescriptor* original_file =
+ protobuf_unittest::TestAllTypes::descriptor()->file();
+
+ DescriptorPoolDatabase database(*DescriptorPool::generated_pool());
+ DescriptorPool pool(&database);
+ const FileDescriptor* file_from_database =
+ pool.FindFileByName(original_file->name());
+
+ ASSERT_TRUE(file_from_database != NULL);
+
+ FileDescriptorProto original_file_proto;
+ original_file->CopyTo(&original_file_proto);
+
+ FileDescriptorProto file_from_database_proto;
+ file_from_database->CopyTo(&file_from_database_proto);
+
+ EXPECT_EQ(original_file_proto.DebugString(),
+ file_from_database_proto.DebugString());
+
+ // Also verify that CopyTo() did not omit any information.
+ EXPECT_EQ(original_file->DebugString(),
+ file_from_database->DebugString());
+}
+
+TEST_F(DatabaseBackedPoolTest, DoesntRetryDbUnnecessarily) {
+ // Searching for a child of an existing descriptor should never fall back
+ // to the DescriptorDatabase even if it isn't found, because we know all
+ // children are already loaded.
+ CallCountingDatabase call_counter(&database_);
+ DescriptorPool pool(&call_counter);
+
+ const FileDescriptor* file = pool.FindFileByName("foo.proto");
+ ASSERT_TRUE(file != NULL);
+ const Descriptor* foo = pool.FindMessageTypeByName("Foo");
+ ASSERT_TRUE(foo != NULL);
+ const EnumDescriptor* test_enum = pool.FindEnumTypeByName("TestEnum");
+ ASSERT_TRUE(test_enum != NULL);
+ const ServiceDescriptor* test_service = pool.FindServiceByName("TestService");
+ ASSERT_TRUE(test_service != NULL);
+
+ EXPECT_NE(0, call_counter.call_count_);
+ call_counter.Clear();
+
+ EXPECT_TRUE(foo->FindFieldByName("no_such_field") == NULL);
+ EXPECT_TRUE(foo->FindExtensionByName("no_such_extension") == NULL);
+ EXPECT_TRUE(foo->FindNestedTypeByName("NoSuchMessageType") == NULL);
+ EXPECT_TRUE(foo->FindEnumTypeByName("NoSuchEnumType") == NULL);
+ EXPECT_TRUE(foo->FindEnumValueByName("NO_SUCH_VALUE") == NULL);
+ EXPECT_TRUE(test_enum->FindValueByName("NO_SUCH_VALUE") == NULL);
+ EXPECT_TRUE(test_service->FindMethodByName("NoSuchMethod") == NULL);
+
+ EXPECT_TRUE(file->FindMessageTypeByName("NoSuchMessageType") == NULL);
+ EXPECT_TRUE(file->FindEnumTypeByName("NoSuchEnumType") == NULL);
+ 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_);
+}
+
+TEST_F(DatabaseBackedPoolTest, DoesntReloadFilesUncesessarily) {
+ // If FindFileContainingSymbol() or FindFileContainingExtension() return a
+ // file that is already in the DescriptorPool, it should not attempt to
+ // reload the file.
+ FalsePositiveDatabase false_positive_database(&database_);
+ MockErrorCollector error_collector;
+ DescriptorPool pool(&false_positive_database, &error_collector);
+
+ // First make sure foo.proto is loaded.
+ const Descriptor* foo = pool.FindMessageTypeByName("Foo");
+ ASSERT_TRUE(foo != NULL);
+
+ // Try inducing false positives.
+ EXPECT_TRUE(pool.FindMessageTypeByName("NoSuchSymbol") == NULL);
+ EXPECT_TRUE(pool.FindExtensionByNumber(foo, 22) == NULL);
+
+ // No errors should have been reported. (If foo.proto was incorrectly
+ // loaded multiple times, errors would have been reported.)
+ EXPECT_EQ("", error_collector.text_);
+}
+
+// DescriptorDatabase that attempts to induce exponentially-bad performance
+// in DescriptorPool. For every positive N, the database contains a file
+// fileN.proto, which defines a message MessageN, which contains fields of
+// type MessageK for all K in [0,N). Message0 is not defined anywhere
+// (file0.proto exists, but is empty), so every other file and message type
+// will fail to build.
+//
+// If the DescriptorPool is not careful to memoize errors, an attempt to
+// build a descriptor for MessageN can require O(2^N) time.
+class ExponentialErrorDatabase : public DescriptorDatabase {
+ public:
+ ExponentialErrorDatabase() {}
+ ~ExponentialErrorDatabase() {}
+
+ // implements DescriptorDatabase ---------------------------------
+ bool FindFileByName(const string& filename,
+ FileDescriptorProto* output) {
+ int file_num = -1;
+ FullMatch(filename, "file", ".proto", &file_num);
+ if (file_num > -1) {
+ return PopulateFile(file_num, output);
+ } else {
+ return false;
+ }
+ }
+ bool FindFileContainingSymbol(const string& symbol_name,
+ FileDescriptorProto* output) {
+ int file_num = -1;
+ FullMatch(symbol_name, "Message", "", &file_num);
+ if (file_num > 0) {
+ return PopulateFile(file_num, output);
+ } else {
+ return false;
+ }
+ }
+ bool FindFileContainingExtension(const string& containing_type,
+ int field_number,
+ FileDescriptorProto* output) {
+ return false;
+ }
+
+ private:
+ void FullMatch(const string& name,
+ const string& begin_with,
+ const string& end_with,
+ int* file_num) {
+ int begin_size = begin_with.size();
+ int end_size = end_with.size();
+ if (name.substr(0, begin_size) != begin_with ||
+ name.substr(name.size()- end_size, end_size) != end_with) {
+ return;
+ }
+ safe_strto32(name.substr(begin_size, name.size() - end_size - begin_size),
+ file_num);
+ }
+
+ bool PopulateFile(int file_num, FileDescriptorProto* output) {
+ using strings::Substitute;
+ GOOGLE_CHECK_GE(file_num, 0);
+ output->Clear();
+ output->set_name(Substitute("file$0.proto", file_num));
+ // file0.proto doesn't define Message0
+ if (file_num > 0) {
+ DescriptorProto* message = output->add_message_type();
+ message->set_name(Substitute("Message$0", file_num));
+ for (int i = 0; i < file_num; ++i) {
+ output->add_dependency(Substitute("file$0.proto", i));
+ FieldDescriptorProto* field = message->add_field();
+ field->set_name(Substitute("field$0", i));
+ field->set_number(i);
+ field->set_label(FieldDescriptorProto::LABEL_OPTIONAL);
+ field->set_type(FieldDescriptorProto::TYPE_MESSAGE);
+ field->set_type_name(Substitute("Message$0", i));
+ }
+ }
+ return true;
+ }
+};
+
+TEST_F(DatabaseBackedPoolTest, DoesntReloadKnownBadFiles) {
+ ExponentialErrorDatabase error_database;
+ DescriptorPool pool(&error_database);
+
+ GOOGLE_LOG(INFO) << "A timeout in this test probably indicates a real bug.";
+
+ EXPECT_TRUE(pool.FindFileByName("file40.proto") == NULL);
+ EXPECT_TRUE(pool.FindMessageTypeByName("Message40") == NULL);
+}
+
+TEST_F(DatabaseBackedPoolTest, DoesntFallbackOnWrongType) {
+ // If a lookup finds a symbol of the wrong type (e.g. we pass a type name
+ // to FindFieldByName()), we should fail fast, without checking the fallback
+ // database.
+ CallCountingDatabase call_counter(&database_);
+ DescriptorPool pool(&call_counter);
+
+ const FileDescriptor* file = pool.FindFileByName("foo.proto");
+ ASSERT_TRUE(file != NULL);
+ const Descriptor* foo = pool.FindMessageTypeByName("Foo");
+ ASSERT_TRUE(foo != NULL);
+ const EnumDescriptor* test_enum = pool.FindEnumTypeByName("TestEnum");
+ ASSERT_TRUE(test_enum != NULL);
+
+ EXPECT_NE(0, call_counter.call_count_);
+ call_counter.Clear();
+
+ EXPECT_TRUE(pool.FindMessageTypeByName("TestEnum") == NULL);
+ EXPECT_TRUE(pool.FindFieldByName("Foo") == NULL);
+ EXPECT_TRUE(pool.FindExtensionByName("Foo") == NULL);
+ EXPECT_TRUE(pool.FindEnumTypeByName("Foo") == NULL);
+ EXPECT_TRUE(pool.FindEnumValueByName("Foo") == NULL);
+ EXPECT_TRUE(pool.FindServiceByName("Foo") == NULL);
+ EXPECT_TRUE(pool.FindMethodByName("Foo") == NULL);
+
+ EXPECT_EQ(0, call_counter.call_count_);
+}
+
+// ===================================================================
+
+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 << " ["
+ << element_name << "]: " << 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"
+ "message MessageWithExtensions {\n"
+ " extensions 1000 to max;\n"
+ "}\n"
+ "extend MessageWithExtensions {\n"
+ " optional int32 int32_extension = 1001;\n"
+ "}\n"
+ "message C {\n"
+ " extend MessageWithExtensions {\n"
+ " optional C message_extension = 1002;\n"
+ " }\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));
+
+}
+
+TEST_F(SourceLocationTest, ExtensionSourceLocation) {
+ SourceLocation loc;
+
+ const FileDescriptor *file_desc =
+ GOOGLE_CHECK_NOTNULL(pool_.FindFileByName("/test/test.proto"));
+
+ const FieldDescriptor *int32_extension_desc =
+ file_desc->FindExtensionByName("int32_extension");
+ EXPECT_TRUE(int32_extension_desc->GetSourceLocation(&loc));
+ EXPECT_EQ("21:3-21:41", PrintSourceLocation(loc));
+
+ const Descriptor *c_desc = file_desc->FindMessageTypeByName("C");
+ EXPECT_TRUE(c_desc->GetSourceLocation(&loc));
+ EXPECT_EQ("23:1-27:2", PrintSourceLocation(loc));
+
+ const FieldDescriptor *message_extension_desc =
+ c_desc->FindExtensionByName("message_extension");
+ EXPECT_TRUE(message_extension_desc->GetSourceLocation(&loc));
+ EXPECT_EQ("25:5-25:41", 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(4, info.location_size());
+ // Get the Foo message location
+ const SourceCodeInfo_Location& foo_location = info.location(2);
+ 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