Index: third_party/protobuf/src/google/protobuf/descriptor_unittest.cc |
diff --git a/third_party/protobuf/src/google/protobuf/descriptor_unittest.cc b/third_party/protobuf/src/google/protobuf/descriptor_unittest.cc |
index 86e6a49d8c83ee1a501bc1d7adef0b8084652a28..be8e0b720fbee9b936badcba20bf1792004ef08f 100644 |
--- a/third_party/protobuf/src/google/protobuf/descriptor_unittest.cc |
+++ b/third_party/protobuf/src/google/protobuf/descriptor_unittest.cc |
@@ -1,6 +1,6 @@ |
// Protocol Buffers - Google's data interchange format |
// Copyright 2008 Google Inc. All rights reserved. |
-// http://code.google.com/p/protobuf/ |
+// 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 |
@@ -34,6 +34,10 @@ |
// |
// 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> |
@@ -49,6 +53,8 @@ |
#include <google/protobuf/stubs/substitute.h> |
#include <google/protobuf/stubs/common.h> |
+#include <google/protobuf/stubs/logging.h> |
+#include <google/protobuf/stubs/scoped_ptr.h> |
#include <google/protobuf/testing/googletest.h> |
#include <gtest/gtest.h> |
@@ -139,6 +145,14 @@ DescriptorProto::ExtensionRange* AddExtensionRange(DescriptorProto* parent, |
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(); |
@@ -164,6 +178,61 @@ 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. |
@@ -360,6 +429,65 @@ TEST_F(FileDescriptorTest, BuildAgain) { |
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. |
@@ -387,6 +515,21 @@ class DescriptorTest : public testing::Test { |
// 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. |
// |
@@ -434,6 +577,48 @@ class DescriptorTest : public testing::Test { |
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); |
@@ -441,6 +626,12 @@ class DescriptorTest : public testing::Test { |
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); |
@@ -461,15 +652,33 @@ class DescriptorTest : public testing::Test { |
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_; |
@@ -481,6 +690,8 @@ class DescriptorTest : public testing::Test { |
const FieldDescriptor* foo2_; |
const FieldDescriptor* bar2_; |
const FieldDescriptor* quux2_; |
+ |
+ const FieldDescriptor* map_; |
}; |
TEST_F(DescriptorTest, Name) { |
@@ -559,6 +770,35 @@ TEST_F(DescriptorTest, FieldFullName) { |
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()); |
@@ -610,6 +850,12 @@ TEST_F(DescriptorTest, FieldLabel) { |
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()); |
@@ -646,6 +892,104 @@ TEST_F(DescriptorTest, FieldEnumType) { |
// =================================================================== |
+// 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() { |
@@ -1516,6 +1860,118 @@ TEST_F(ExtensionDescriptorTest, FindAllExtensions) { |
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 { |
@@ -1567,7 +2023,19 @@ class MiscTest : public testing::Test { |
return field != NULL ? field->cpp_type_name() : ""; |
} |
- scoped_ptr<DescriptorPool> pool_; |
+ 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) { |
@@ -1595,6 +2063,31 @@ TEST_F(MiscTest, TypeNames) { |
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. |
@@ -1645,6 +2138,74 @@ TEST_F(MiscTest, CppTypeNames) { |
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; |
@@ -1904,38 +2465,47 @@ class AllowUnknownDependenciesTest |
const FieldDescriptor* qux_field_; |
SimpleDescriptorDatabase db_; // used if in FALLBACK_DATABASE mode. |
- scoped_ptr<DescriptorPool> pool_; |
+ 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("Baz.placeholder.proto", baz_type->file()->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_EQ("corge.Qux.placeholder.proto", qux_type->file()->name()); |
+ EXPECT_TRUE(qux_type->is_placeholder()); |
// Placeholder types should not be findable. |
EXPECT_EQ(bar_type_, pool_->FindMessageTypeByName(bar_type_->full_name())); |
@@ -1993,6 +2563,7 @@ TEST_P(AllowUnknownDependenciesTest, UnknownExtendee) { |
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); |
@@ -2111,13 +2682,13 @@ TEST_P(AllowUnknownDependenciesTest, |
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_EQ("undeclared.Quux.placeholder.proto", |
- quux_field->message_type()->file()->name()); |
+ EXPECT_TRUE(quux_field->message_type()->is_placeholder()); |
// The place holder type should not be findable. |
ASSERT_TRUE(pool_->FindMessageTypeByName("undeclared.Quux") == NULL); |
} |
@@ -2167,7 +2738,7 @@ TEST(CustomOptions, OptionTypes) { |
options = |
&protobuf_unittest::CustomOptionMinIntegerValues::descriptor()->options(); |
- EXPECT_FALSE( options->GetExtension(protobuf_unittest::bool_opt)); |
+ 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)); |
@@ -2181,7 +2752,7 @@ TEST(CustomOptions, OptionTypes) { |
options = |
&protobuf_unittest::CustomOptionMaxIntegerValues::descriptor()->options(); |
- EXPECT_TRUE( options->GetExtension(protobuf_unittest::bool_opt)); |
+ 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)); |
@@ -2389,33 +2960,197 @@ TEST(CustomOptions, MessageOptionThreeFieldsSet) { |
EXPECT_EQ(1234, options.GetExtension(protobuf_unittest::complex_opt1).foo()); |
} |
-// 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"); |
+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; |
- // 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()); |
+ FileDescriptorProto file_proto; |
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto); |
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL); |
- // Simple tests for all the other types of annotations |
- EXPECT_EQ("MessageAnnotation", |
+ 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()); |
@@ -2429,52 +3164,115 @@ TEST(CustomOptions, AggregateOptions) { |
method->options().GetExtension(protobuf_unittest::methodopt).s()); |
} |
-// =================================================================== |
+TEST(CustomOptions, UnusedImportWarning) { |
+ DescriptorPool pool; |
-// The tests below trigger every unique call to AddError() in descriptor.cc, |
-// in the order in which they appear in that file. I'm using TextFormat here |
-// to specify the input descriptors because building them using code would |
-// be too bulky. |
+ FileDescriptorProto file_proto; |
+ FileDescriptorProto::descriptor()->file()->CopyTo(&file_proto); |
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL); |
-class MockErrorCollector : public DescriptorPool::ErrorCollector { |
- public: |
- MockErrorCollector() {} |
- ~MockErrorCollector() {} |
+ protobuf_unittest::TestMessageWithCustomOptions::descriptor() |
+ ->file()->CopyTo(&file_proto); |
+ ASSERT_TRUE(pool.BuildFile(file_proto) != NULL); |
- string text_; |
+ 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)); |
- // 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; |
- } |
+ MockErrorCollector error_collector; |
+ EXPECT_TRUE(pool.BuildFileCollectingErrors(file_proto, &error_collector)); |
+ EXPECT_EQ("", error_collector.warning_text_); |
+} |
- strings::SubstituteAndAppend( |
- &text_, "$0: $1: $2: $3\n", |
- filename, element_name, location_name, message); |
- } |
-}; |
+// 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. |
- void BuildFile(const string& file_text) { |
+ const FileDescriptor* BuildFile(const string& file_text) { |
FileDescriptorProto file_proto; |
- ASSERT_TRUE(TextFormat::ParseFromString(file_text, &file_proto)); |
- ASSERT_TRUE(pool_.BuildFile(file_proto) != NULL); |
+ 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 |
@@ -2491,6 +3289,19 @@ class ValidationErrorTest : public testing::Test { |
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; |
@@ -2712,82 +3523,178 @@ TEST_F(ValidationErrorTest, OverlappingExtensionRanges) { |
"already-defined range 20 to 29.\n"); |
} |
-TEST_F(ValidationErrorTest, InvalidDefaults) { |
+TEST_F(ValidationErrorTest, ReservedFieldError) { |
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\" }" |
+ " field { name: \"foo\" number: 15 label:LABEL_OPTIONAL type:TYPE_INT32 }" |
+ " reserved_range { start: 10 end: 20 }" |
"}", |
- "foo.proto: Foo.foo: DEFAULT_VALUE: Couldn't parse default value.\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"); |
+ "foo.proto: Foo.foo: NUMBER: Field \"foo\" uses reserved number 15.\n"); |
} |
-TEST_F(ValidationErrorTest, NegativeFieldNumber) { |
+TEST_F(ValidationErrorTest, ReservedExtensionRangeError) { |
BuildFileWithErrors( |
"name: \"foo.proto\" " |
"message_type {" |
" name: \"Foo\"" |
- " field { name: \"foo\" number: -1 label:LABEL_OPTIONAL type:TYPE_INT32 }" |
+ " extension_range { start: 10 end: 20 }" |
+ " reserved_range { start: 5 end: 15 }" |
"}", |
- "foo.proto: Foo.foo: NUMBER: Field numbers must be positive integers.\n"); |
+ "foo.proto: Foo: NUMBER: Extension range 10 to 19" |
+ " overlaps with reserved range 5 to 14.\n"); |
} |
-TEST_F(ValidationErrorTest, HugeFieldNumber) { |
+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\"" |
- " field { name: \"foo\" number: 0x70000000 " |
- " label:LABEL_OPTIONAL type:TYPE_INT32 }" |
+ " reserved_range { start: 10 end: 20 }" |
+ " reserved_range { start: 5 end: 15 }" |
"}", |
- "foo.proto: Foo.foo: NUMBER: Field numbers cannot be greater than " |
- "536870911.\n"); |
+ "foo.proto: Foo: NUMBER: Reserved range 5 to 14" |
+ " overlaps with already-defined range 10 to 19.\n"); |
} |
-TEST_F(ValidationErrorTest, ReservedFieldNumber) { |
+TEST_F(ValidationErrorTest, ReservedNameError) { |
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: \"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 }" |
"}", |
@@ -2828,6 +3735,99 @@ TEST_F(ValidationErrorTest, NonExtensionWithExtendee) { |
"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\" " |
@@ -2988,6 +3988,28 @@ TEST_F(ValidationErrorTest, NotAnExtensionNumber) { |
"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\" " |
@@ -2999,6 +4021,36 @@ TEST_F(ValidationErrorTest, UndefinedFieldType) { |
"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\" " |
@@ -3174,7 +4226,8 @@ TEST_F(ValidationErrorTest, |
TEST_F(ValidationErrorTest, SearchMostLocalFirst) { |
- // The following should produce an error that Bar.Baz is not defined: |
+ // The following should produce an error that Bar.Baz is resolved but |
+ // not defined: |
// message Bar { message Baz {} } |
// message Foo { |
// message Bar { |
@@ -3200,7 +4253,10 @@ TEST_F(ValidationErrorTest, SearchMostLocalFirst) { |
" type_name:\"Bar.Baz\" }" |
"}", |
- "foo.proto: Foo.baz: TYPE: \"Bar.Baz\" is not defined.\n"); |
+ "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) { |
@@ -3342,6 +4398,20 @@ TEST_F(ValidationErrorTest, BadEnumDefaultValue) { |
"\"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\" " |
@@ -3366,6 +4436,31 @@ TEST_F(ValidationErrorTest, NonPrimitiveWithoutTypeName) { |
"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\" " |
@@ -3527,20 +4622,85 @@ TEST_F(ValidationErrorTest, InvalidOptionName) { |
"reserved name \"uninterpreted_option\".\n"); |
} |
-TEST_F(ValidationErrorTest, RepeatedOption) { |
+TEST_F(ValidationErrorTest, RepeatedMessageOption) { |
BuildDescriptorMessagesInTestPool(); |
BuildFileWithErrors( |
"name: \"foo.proto\" " |
"dependency: \"google/protobuf/descriptor.proto\" " |
- "extension { name: \"foo\" number: 7672757 label: LABEL_REPEATED " |
- " type: TYPE_FLOAT extendee: \"google.protobuf.FileOptions\" }" |
- "options { uninterpreted_option { name { name_part: \"foo\" " |
+ "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 } " |
- " double_value: 1.2 } }", |
+ " name { name_part: \"foo\" " |
+ " is_extension: false } " |
+ " positive_int_value: 1 } }", |
- "foo.proto: foo.proto: OPTION_NAME: Option field \"(foo)\" is repeated. " |
- "Repeated options are not supported.\n"); |
+ "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) { |
@@ -3813,6 +4973,25 @@ TEST_F(ValidationErrorTest, StringOptionValueIsNotString) { |
"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. |
@@ -3830,180 +5009,855 @@ static string EmbedAggregateValue(const char* value) { |
value); |
} |
-TEST_F(ValidationErrorTest, AggregateValueNotFound) { |
- BuildDescriptorMessagesInTestPool(); |
+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.\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( |
- 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"); |
+ "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, AggregateValueParseError) { |
- BuildDescriptorMessagesInTestPool(); |
+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( |
- EmbedAggregateValue("aggregate_value: \"1+2\""), |
- "foo.proto: foo.proto: OPTION_VALUE: Error while parsing option " |
- "value for \"foo\": Expected identifier.\n"); |
+ "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, AggregateValueUnknownFields) { |
- BuildDescriptorMessagesInTestPool(); |
+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( |
- 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"); |
+ "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, NotLiteImportsLite) { |
- BuildFile( |
- "name: \"bar.proto\" " |
- "options { optimize_for: LITE_RUNTIME } "); |
- |
+TEST_F(ValidationErrorTest, ValidateProto3MessageSetWireFormat) { |
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"); |
+ "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, LiteExtendsNotLite) { |
- BuildFile( |
- "name: \"bar.proto\" " |
- "message_type: {" |
- " name: \"Bar\"" |
- " extension_range { start: 1 end: 1000 }" |
- "}"); |
+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\" " |
- "dependency: \"bar.proto\" " |
- "options { optimize_for: LITE_RUNTIME } " |
- "extension { name: \"ext\" number: 123 label: LABEL_OPTIONAL " |
- " type: TYPE_INT32 extendee: \"Bar\" }", |
+ "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"); |
- "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"); |
+ // valid case. |
+ BuildFile( |
+ "name: 'foo.proto' " |
+ "syntax: 'proto3' " |
+ "enum_type { " |
+ " name: 'FooEnum' " |
+ " value { name: 'FOO_FOO' number:0 } " |
+ "}"); |
} |
-TEST_F(ValidationErrorTest, NoLiteServices) { |
+TEST_F(ValidationErrorTest, ValidateProto3LiteRuntime) { |
+ // Lite runtime is not supported in proto3. |
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\" }"); |
+ "name: 'foo.proto' " |
+ "syntax: 'proto3' " |
+ "options { " |
+ " optimize_for: LITE_RUNTIME " |
+ "} ", |
+ "foo.proto: foo.proto: OTHER: Lite runtime is not supported " |
+ "in proto3.\n"); |
} |
-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. |
+TEST_F(ValidationErrorTest, ValidateProto3Group) { |
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\"" |
- " }" |
- "}", |
+ "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"); |
+} |
- "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. |
+TEST_F(ValidationErrorTest, ValidateProto3EnumFromProto2) { |
+ // Define an enum in a proto2 file. |
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\" }" |
- "}"); |
-} |
+ "name: 'foo.proto' " |
+ "package: 'foo' " |
+ "syntax: 'proto2' " |
+ "enum_type { " |
+ " name: 'FooEnum' " |
+ " value { name: 'DEFAULT_OPTION' number:0 } " |
+ "}"); |
-TEST_F(ValidationErrorTest, ErrorsReportedToLogError) { |
- // Test that errors are reported to GOOGLE_LOG(ERROR) if no error collector is |
- // provided. |
+ // 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; |
- 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()); |
+ // 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); |
- EXPECT_EQ("Invalid proto descriptor for file \"foo.proto\":", errors[0]); |
- EXPECT_EQ(" Foo: \"Foo\" is already defined.", errors[1]); |
+ // 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_F(ValidationErrorTest, DisallowEnumAlias) { |
+// 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\" " |
- "enum_type {" |
- " name: \"Bar\"" |
- " value { name:\"ENUM_A\" number:0 }" |
- " value { name:\"ENUM_B\" number:0 }" |
- " options { allow_alias: false }" |
- "}", |
- "foo.proto: Bar: NUMBER: " |
- "\"ENUM_B\" uses the same enum value as \"ENUM_A\". " |
- "If this is intended, set 'option allow_alias = true;' to the enum " |
- "definition.\n"); |
+ "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 camcel-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 camcel-case name of field \"_a__b_\" " |
+ "conflicts with field \"ab\". This is not allowed in proto3.\n"); |
} |
// =================================================================== |
@@ -4328,6 +6182,10 @@ TEST_F(DatabaseBackedPoolTest, UnittestProto) { |
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) { |
@@ -4396,15 +6254,93 @@ TEST_F(DatabaseBackedPoolTest, DoesntReloadFilesUncesessarily) { |
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) { |
- ErrorDescriptorDatabase error_database; |
- MockErrorCollector error_collector; |
- DescriptorPool pool(&error_database, &error_collector); |
+ ExponentialErrorDatabase error_database; |
+ DescriptorPool pool(&error_database); |
- EXPECT_TRUE(pool.FindFileByName("error.proto") == NULL); |
- error_collector.text_.clear(); |
- EXPECT_TRUE(pool.FindFileByName("error.proto") == NULL); |
- EXPECT_EQ("", error_collector.text_); |
+ 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) { |
@@ -4447,8 +6383,8 @@ class AbortingErrorCollector : public DescriptorPool::ErrorCollector { |
const Message *message, |
ErrorLocation location, |
const string &error_message) { |
- GOOGLE_LOG(FATAL) << "AddError() called unexpectedly: " << filename << ": " |
- << error_message; |
+ GOOGLE_LOG(FATAL) << "AddError() called unexpectedly: " << filename << " [" |
+ << element_name << "]: " << error_message; |
} |
private: |
GOOGLE_DISALLOW_EVIL_CONSTRUCTORS(AbortingErrorCollector); |
@@ -4489,6 +6425,17 @@ const char *const kSourceLocationTestInput = |
" 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 { |
@@ -4550,6 +6497,27 @@ TEST_F(SourceLocationTest, GetSourceLocation) { |
} |
+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; |
@@ -4636,9 +6604,9 @@ TEST_F(CopySourceCodeInfoToTest, CopySourceCodeInfoTo) { |
file_desc->CopySourceCodeInfoTo(&file_desc_proto); |
const SourceCodeInfo& info = file_desc_proto.source_code_info(); |
- ASSERT_EQ(3, info.location_size()); |
+ ASSERT_EQ(4, info.location_size()); |
// Get the Foo message location |
- const SourceCodeInfo_Location& foo_location = info.location(1); |
+ 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 |