aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/google/protobuf/compiler/parser_unittest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/google/protobuf/compiler/parser_unittest.cc')
-rw-r--r--src/google/protobuf/compiler/parser_unittest.cc866
1 files changed, 866 insertions, 0 deletions
diff --git a/src/google/protobuf/compiler/parser_unittest.cc b/src/google/protobuf/compiler/parser_unittest.cc
index e2262b8b..156c0dc3 100644
--- a/src/google/protobuf/compiler/parser_unittest.cc
+++ b/src/google/protobuf/compiler/parser_unittest.cc
@@ -34,6 +34,7 @@
#include <vector>
#include <algorithm>
+#include <map>
#include <google/protobuf/compiler/parser.h>
@@ -45,6 +46,7 @@
#include <google/protobuf/unittest.pb.h>
#include <google/protobuf/stubs/strutil.h>
#include <google/protobuf/stubs/substitute.h>
+#include <google/protobuf/stubs/map-util.h>
#include <google/protobuf/testing/googletest.h>
#include <gtest/gtest.h>
@@ -118,6 +120,9 @@ class ParserTest : public testing::Test {
EXPECT_EQ(io::Tokenizer::TYPE_END, input_->current().type);
ASSERT_EQ("", error_collector_.text_);
+ // We don't cover SourceCodeInfo in these tests.
+ actual.clear_source_code_info();
+
// Parse the ASCII representation in order to canonicalize it. We could
// just compare directly to actual.DebugString(), but that would require
// that the caller precisely match the formatting that DebugString()
@@ -930,6 +935,12 @@ TEST_F(ParseErrorTest, MultipleParseErrors) {
"3:25: Expected \";\".\n");
}
+TEST_F(ParseErrorTest, EofInAggregateValue) {
+ ExpectHasErrors(
+ "option (fileopt) = { i:100\n",
+ "1:0: Unexpected end of stream while parsing aggregate value.\n");
+}
+
// -------------------------------------------------------------------
// Enum errors
@@ -1248,6 +1259,861 @@ TEST_F(ParseDecriptorDebugTest, TestAllDescriptorTypes) {
}
// ===================================================================
+// SourceCodeInfo tests.
+
+// Follows a path -- as defined by SourceCodeInfo.Location.path -- from a
+// message to a particular sub-field.
+// * If the target is itself a message, sets *output_message to point at it,
+// *output_field to NULL, and *output_index to -1.
+// * Otherwise, if the target is an element of a repeated field, sets
+// *output_message to the containing message, *output_field to the descriptor
+// of the field, and *output_index to the index of the element.
+// * Otherwise, the target is a field (possibly a repeated field, but not any
+// one element). Sets *output_message to the containing message,
+// *output_field to the descriptor of the field, and *output_index to -1.
+// Returns true if the path was valid, false otherwise. A gTest failure is
+// recorded before returning false.
+bool FollowPath(const Message& root,
+ const int* path_begin, const int* path_end,
+ const Message** output_message,
+ const FieldDescriptor** output_field,
+ int* output_index) {
+ if (path_begin == path_end) {
+ // Path refers to this whole message.
+ *output_message = &root;
+ *output_field = NULL;
+ *output_index = -1;
+ return true;
+ }
+
+ const Descriptor* descriptor = root.GetDescriptor();
+ const Reflection* reflection = root.GetReflection();
+
+ const FieldDescriptor* field = descriptor->FindFieldByNumber(*path_begin);
+
+ if (field == NULL) {
+ ADD_FAILURE() << descriptor->name() << " has no field number: "
+ << *path_begin;
+ return false;
+ }
+
+ ++path_begin;
+
+ if (field->is_repeated()) {
+ if (path_begin == path_end) {
+ // Path refers to the whole repeated field.
+ *output_message = &root;
+ *output_field = field;
+ *output_index = -1;
+ return true;
+ }
+
+ int index = *path_begin++;
+ int size = reflection->FieldSize(root, field);
+
+ if (index >= size) {
+ ADD_FAILURE() << descriptor->name() << "." << field->name()
+ << " has size " << size << ", but path contained index: "
+ << index;
+ return false;
+ }
+
+ if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+ // Descend into child message.
+ const Message& child = reflection->GetRepeatedMessage(root, field, index);
+ return FollowPath(child, path_begin, path_end,
+ output_message, output_field, output_index);
+ } else if (path_begin == path_end) {
+ // Path refers to this element.
+ *output_message = &root;
+ *output_field = field;
+ *output_index = index;
+ return true;
+ } else {
+ ADD_FAILURE() << descriptor->name() << "." << field->name()
+ << " is not a message; cannot descend into it.";
+ return false;
+ }
+ } else {
+ if (field->cpp_type() == FieldDescriptor::CPPTYPE_MESSAGE) {
+ const Message& child = reflection->GetMessage(root, field);
+ return FollowPath(child, path_begin, path_end,
+ output_message, output_field, output_index);
+ } else if (path_begin == path_end) {
+ // Path refers to this field.
+ *output_message = &root;
+ *output_field = field;
+ *output_index = -1;
+ return true;
+ } else {
+ ADD_FAILURE() << descriptor->name() << "." << field->name()
+ << " is not a message; cannot descend into it.";
+ return false;
+ }
+ }
+}
+
+// Split some text on line breaks. The line breaks are retained in the output,
+// so each line (except the last) ends with a '\n', and the lines can be
+// concatenated to produce the original text.
+//
+// I couldn't find the proper string utility function for this. Our
+// split-on-delimiter functions don't include the delimiter in the output.
+void SplitLines(const string& text, vector<string>* lines) {
+ string::size_type pos = 0;
+
+ while (pos != string::npos) {
+ string::size_type last_pos = pos;
+ pos = text.find_first_of('\n', pos);
+ if (pos != string::npos) ++pos;
+ lines->push_back(text.substr(last_pos, pos - last_pos));
+ }
+}
+
+// Look for the given tags in the given text and construct a span (as defined
+// by SourceCodeInfo.Location.span) from them. E.g. for text like:
+// /*a*/message /*b*/Foo/*c*/ {}/*d*/
+// There are four tags: "a", "b", "c", and "d". The constructed span starts
+// immediately after the start tag's trailing '/' and ends immediately before
+// the end tags leading '/'.
+void MakeExpectedSpan(const vector<string>& lines,
+ const string& start_tag, const string& end_tag,
+ RepeatedField<int>* output) {
+ string start_comment = "/*" + start_tag + "*/";
+ string end_comment = "/*" + end_tag + "*/";
+
+ int start_line = -1;
+ int start_column = -1;
+ for (int i = 0; i < lines.size(); i++) {
+ string::size_type pos = lines[i].find(start_comment);
+ if (pos != string::npos) {
+ start_line = i;
+ start_column = pos + start_comment.size();
+ break;
+ }
+ }
+ ASSERT_NE(start_line, -1)
+ << "Tag \"" << start_comment << "\" not found in text.";
+
+ int end_line = -1;
+ int end_column = -1;
+ for (int i = start_line; i < lines.size(); i++) {
+ string::size_type pos = lines[i].find(end_comment);
+ if (pos != string::npos) {
+ end_line = i;
+ end_column = pos;
+ break;
+ }
+ }
+ ASSERT_NE(end_line, -1)
+ << "Tag \"" << end_comment << "\" not found in text.";
+
+ output->Add(start_line);
+ output->Add(start_column);
+ if (end_line != start_line) output->Add(end_line);
+ output->Add(end_column);
+}
+
+// Check if two spans are equal.
+bool CompareSpans(const RepeatedField<int>& span1,
+ const RepeatedField<int>& span2) {
+ if (span1.size() != span2.size()) return false;
+ for (int i = 0; i < span1.size(); i++) {
+ if (span1.Get(i) != span2.Get(i)) return false;
+ }
+ return true;
+}
+
+// Test fixture for source info tests, which check that source locations are
+// recorded correctly in FileDescriptorProto.source_code_info.location.
+class SourceInfoTest : public ParserTest {
+ protected:
+ // The parsed file (initialized by Parse()).
+ FileDescriptorProto file_;
+
+ // Parse the given text as a .proto file and populate the spans_ map with
+ // all the source location spans in its SourceCodeInfo table.
+ bool Parse(const char* text) {
+ SetupParser(text);
+ SplitLines(text, &lines_);
+ if (!parser_->Parse(input_.get(), &file_)) {
+ return false;
+ }
+
+ const SourceCodeInfo& source_info = file_.source_code_info();
+ for (int i = 0; i < source_info.location_size(); i++) {
+ const SourceCodeInfo::Location& location = source_info.location(i);
+ const Message* descriptor_proto = NULL;
+ const FieldDescriptor* field = NULL;
+ int index = 0;
+ if (!FollowPath(file_, location.path().begin(), location.path().end(),
+ &descriptor_proto, &field, &index)) {
+ return false;
+ }
+
+ spans_.insert(make_pair(SpanKey(*descriptor_proto, field, index),
+ &location));
+ }
+
+ return true;
+ }
+
+ virtual void TearDown() {
+ EXPECT_TRUE(spans_.empty())
+ << "Forgot to call HasSpan() for:\n"
+ << spans_.begin()->second->DebugString();
+ }
+
+ // -----------------------------------------------------------------
+ // HasSpan() checks that the span of source code delimited by the given
+ // tags (comments) correspond via the SourceCodeInfo table to the given
+ // part of the FileDescriptorProto. (If unclear, look at the actual tests;
+ // it should quickly become obvious.)
+
+ bool HasSpan(const char* start_tag, const char* end_tag,
+ const Message& descriptor_proto) {
+ return HasSpan(start_tag, end_tag, descriptor_proto, NULL, -1);
+ }
+
+ bool HasSpan(const char* start_tag, const char* end_tag,
+ const Message& descriptor_proto, const string& field_name) {
+ return HasSpan(start_tag, end_tag, descriptor_proto, field_name, -1);
+ }
+
+ bool HasSpan(const char* start_tag, const char* end_tag,
+ const Message& descriptor_proto, const string& field_name,
+ int index) {
+ const FieldDescriptor* field =
+ descriptor_proto.GetDescriptor()->FindFieldByName(field_name);
+ if (field == NULL) {
+ ADD_FAILURE() << descriptor_proto.GetDescriptor()->name()
+ << " has no such field: " << field_name;
+ return false;
+ }
+
+ return HasSpan(start_tag, end_tag, descriptor_proto, field, index);
+ }
+
+ bool HasSpan(const Message& descriptor_proto) {
+ return HasSpan(NULL, NULL, descriptor_proto, NULL, -1);
+ }
+
+ bool HasSpan(const Message& descriptor_proto, const string& field_name) {
+ return HasSpan(NULL, NULL, descriptor_proto, field_name, -1);
+ }
+
+ bool HasSpan(const Message& descriptor_proto, const string& field_name,
+ int index) {
+ return HasSpan(NULL, NULL, descriptor_proto, field_name, index);
+ }
+
+ bool HasSpan(const char* start_tag, const char* end_tag,
+ const Message& descriptor_proto, const FieldDescriptor* field,
+ int index) {
+ pair<SpanMap::iterator, SpanMap::iterator> range =
+ spans_.equal_range(SpanKey(descriptor_proto, field, index));
+
+ if (start_tag == NULL) {
+ if (range.first == range.second) {
+ return false;
+ } else {
+ spans_.erase(range.first);
+ return true;
+ }
+ } else {
+ RepeatedField<int> expected_span;
+ MakeExpectedSpan(lines_, start_tag, end_tag, &expected_span);
+
+ for (SpanMap::iterator iter = range.first; iter != range.second; ++iter) {
+ if (CompareSpans(expected_span, iter->second->span())) {
+ spans_.erase(iter);
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+
+ private:
+ struct SpanKey {
+ const Message* descriptor_proto;
+ const FieldDescriptor* field;
+ int index;
+
+ inline SpanKey() {}
+ inline SpanKey(const Message& descriptor_proto,
+ const FieldDescriptor* field,
+ int index)
+ : descriptor_proto(&descriptor_proto), field(field), index(index) {}
+
+ inline bool operator<(const SpanKey& other) const {
+ if (descriptor_proto < other.descriptor_proto) return true;
+ if (descriptor_proto > other.descriptor_proto) return false;
+ if (field < other.field) return true;
+ if (field > other.field) return false;
+ return index < other.index;
+ }
+ };
+
+ typedef multimap<SpanKey, const SourceCodeInfo::Location*> SpanMap;
+ SpanMap spans_;
+ vector<string> lines_;
+};
+
+TEST_F(SourceInfoTest, BasicFileDecls) {
+ EXPECT_TRUE(Parse(
+ "/*a*/syntax = \"proto2\";\n"
+ "package /*b*/foo.bar/*c*/;\n"
+ "import /*d*/\"baz.proto\"/*e*/;\n"
+ "import /*f*/\"qux.proto\"/*g*/;/*h*/\n"
+ "// comment ignored\n"));
+
+ EXPECT_TRUE(HasSpan("a", "h", file_));
+ EXPECT_TRUE(HasSpan("b", "c", file_, "package"));
+ EXPECT_TRUE(HasSpan("d", "e", file_, "dependency", 0));
+ EXPECT_TRUE(HasSpan("f", "g", file_, "dependency", 1));
+}
+
+TEST_F(SourceInfoTest, Messages) {
+ EXPECT_TRUE(Parse(
+ "/*a*/message /*b*/Foo/*c*/ {}/*d*/\n"
+ "/*e*/message /*f*/Bar/*g*/ {}/*h*/\n"));
+
+ EXPECT_TRUE(HasSpan("a", "d", file_.message_type(0)));
+ EXPECT_TRUE(HasSpan("b", "c", file_.message_type(0), "name"));
+ EXPECT_TRUE(HasSpan("e", "h", file_.message_type(1)));
+ EXPECT_TRUE(HasSpan("f", "g", file_.message_type(1), "name"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+}
+
+TEST_F(SourceInfoTest, Fields) {
+ EXPECT_TRUE(Parse(
+ "message Foo {\n"
+ " /*a*/optional/*b*/ /*c*/int32/*d*/ /*e*/bar/*f*/ = /*g*/1/*h*/;/*i*/\n"
+ " /*j*/repeated/*k*/ /*l*/X.Y/*m*/ /*n*/baz/*o*/ = /*p*/2/*q*/;/*r*/\n"
+ "}\n"));
+
+ const FieldDescriptorProto& field1 = file_.message_type(0).field(0);
+ const FieldDescriptorProto& field2 = file_.message_type(0).field(1);
+
+ EXPECT_TRUE(HasSpan("a", "i", field1));
+ EXPECT_TRUE(HasSpan("a", "b", field1, "label"));
+ EXPECT_TRUE(HasSpan("c", "d", field1, "type"));
+ EXPECT_TRUE(HasSpan("e", "f", field1, "name"));
+ EXPECT_TRUE(HasSpan("g", "h", field1, "number"));
+
+ EXPECT_TRUE(HasSpan("j", "r", field2));
+ EXPECT_TRUE(HasSpan("j", "k", field2, "label"));
+ EXPECT_TRUE(HasSpan("l", "m", field2, "type_name"));
+ EXPECT_TRUE(HasSpan("n", "o", field2, "name"));
+ EXPECT_TRUE(HasSpan("p", "q", field2, "number"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.message_type(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0), "name"));
+}
+
+TEST_F(SourceInfoTest, Extensions) {
+ EXPECT_TRUE(Parse(
+ "/*a*/extend /*b*/Foo/*c*/ {\n"
+ " /*d*/optional/*e*/ int32 bar = 1;/*f*/\n"
+ " /*g*/repeated/*h*/ X.Y baz = 2;/*i*/\n"
+ "}/*j*/\n"
+ "/*k*/extend /*l*/Bar/*m*/ {\n"
+ " /*n*/optional int32 qux = 1;/*o*/\n"
+ "}/*p*/\n"));
+
+ const FieldDescriptorProto& field1 = file_.extension(0);
+ const FieldDescriptorProto& field2 = file_.extension(1);
+ const FieldDescriptorProto& field3 = file_.extension(2);
+
+ EXPECT_TRUE(HasSpan("a", "j", file_, "extension"));
+ EXPECT_TRUE(HasSpan("k", "p", file_, "extension"));
+
+ EXPECT_TRUE(HasSpan("d", "f", field1));
+ EXPECT_TRUE(HasSpan("d", "e", field1, "label"));
+ EXPECT_TRUE(HasSpan("b", "c", field1, "extendee"));
+
+ EXPECT_TRUE(HasSpan("g", "i", field2));
+ EXPECT_TRUE(HasSpan("g", "h", field2, "label"));
+ EXPECT_TRUE(HasSpan("b", "c", field2, "extendee"));
+
+ EXPECT_TRUE(HasSpan("n", "o", field3));
+ EXPECT_TRUE(HasSpan("l", "m", field3, "extendee"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(field1, "type"));
+ EXPECT_TRUE(HasSpan(field1, "name"));
+ EXPECT_TRUE(HasSpan(field1, "number"));
+ EXPECT_TRUE(HasSpan(field2, "type_name"));
+ EXPECT_TRUE(HasSpan(field2, "name"));
+ EXPECT_TRUE(HasSpan(field2, "number"));
+ EXPECT_TRUE(HasSpan(field3, "label"));
+ EXPECT_TRUE(HasSpan(field3, "type"));
+ EXPECT_TRUE(HasSpan(field3, "name"));
+ EXPECT_TRUE(HasSpan(field3, "number"));
+}
+
+TEST_F(SourceInfoTest, NestedExtensions) {
+ EXPECT_TRUE(Parse(
+ "message Message {\n"
+ " /*a*/extend /*b*/Foo/*c*/ {\n"
+ " /*d*/optional/*e*/ int32 bar = 1;/*f*/\n"
+ " /*g*/repeated/*h*/ X.Y baz = 2;/*i*/\n"
+ " }/*j*/\n"
+ " /*k*/extend /*l*/Bar/*m*/ {\n"
+ " /*n*/optional int32 qux = 1;/*o*/\n"
+ " }/*p*/\n"
+ "}\n"));
+
+ const FieldDescriptorProto& field1 = file_.message_type(0).extension(0);
+ const FieldDescriptorProto& field2 = file_.message_type(0).extension(1);
+ const FieldDescriptorProto& field3 = file_.message_type(0).extension(2);
+
+ EXPECT_TRUE(HasSpan("a", "j", file_.message_type(0), "extension"));
+ EXPECT_TRUE(HasSpan("k", "p", file_.message_type(0), "extension"));
+
+ EXPECT_TRUE(HasSpan("d", "f", field1));
+ EXPECT_TRUE(HasSpan("d", "e", field1, "label"));
+ EXPECT_TRUE(HasSpan("b", "c", field1, "extendee"));
+
+ EXPECT_TRUE(HasSpan("g", "i", field2));
+ EXPECT_TRUE(HasSpan("g", "h", field2, "label"));
+ EXPECT_TRUE(HasSpan("b", "c", field2, "extendee"));
+
+ EXPECT_TRUE(HasSpan("n", "o", field3));
+ EXPECT_TRUE(HasSpan("l", "m", field3, "extendee"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.message_type(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0), "name"));
+ EXPECT_TRUE(HasSpan(field1, "type"));
+ EXPECT_TRUE(HasSpan(field1, "name"));
+ EXPECT_TRUE(HasSpan(field1, "number"));
+ EXPECT_TRUE(HasSpan(field2, "type_name"));
+ EXPECT_TRUE(HasSpan(field2, "name"));
+ EXPECT_TRUE(HasSpan(field2, "number"));
+ EXPECT_TRUE(HasSpan(field3, "label"));
+ EXPECT_TRUE(HasSpan(field3, "type"));
+ EXPECT_TRUE(HasSpan(field3, "name"));
+ EXPECT_TRUE(HasSpan(field3, "number"));
+}
+
+TEST_F(SourceInfoTest, ExtensionRanges) {
+ EXPECT_TRUE(Parse(
+ "message Message {\n"
+ " /*a*/extensions /*b*/1/*c*/ to /*d*/4/*e*/, /*f*/6/*g*/;/*h*/\n"
+ " /*i*/extensions /*j*/8/*k*/ to /*l*/max/*m*/;/*n*/\n"
+ "}\n"));
+
+ const DescriptorProto::ExtensionRange& range1 =
+ file_.message_type(0).extension_range(0);
+ const DescriptorProto::ExtensionRange& range2 =
+ file_.message_type(0).extension_range(1);
+ const DescriptorProto::ExtensionRange& range3 =
+ file_.message_type(0).extension_range(2);
+
+ EXPECT_TRUE(HasSpan("a", "h", file_.message_type(0), "extension_range"));
+ EXPECT_TRUE(HasSpan("i", "n", file_.message_type(0), "extension_range"));
+
+ EXPECT_TRUE(HasSpan("b", "e", range1));
+ EXPECT_TRUE(HasSpan("b", "c", range1, "start"));
+ EXPECT_TRUE(HasSpan("d", "e", range1, "end"));
+
+ EXPECT_TRUE(HasSpan("f", "g", range2));
+ EXPECT_TRUE(HasSpan("f", "g", range2, "start"));
+ EXPECT_TRUE(HasSpan("f", "g", range2, "end"));
+
+ EXPECT_TRUE(HasSpan("j", "m", range3));
+ EXPECT_TRUE(HasSpan("j", "k", range3, "start"));
+ EXPECT_TRUE(HasSpan("l", "m", range3, "end"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.message_type(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0), "name"));
+}
+
+TEST_F(SourceInfoTest, NestedMessages) {
+ EXPECT_TRUE(Parse(
+ "message Foo {\n"
+ " /*a*/message /*b*/Bar/*c*/ {\n"
+ " /*d*/message /*e*/Baz/*f*/ {}/*g*/\n"
+ " }/*h*/\n"
+ " /*i*/message /*j*/Qux/*k*/ {}/*l*/\n"
+ "}\n"));
+
+ const DescriptorProto& bar = file_.message_type(0).nested_type(0);
+ const DescriptorProto& baz = bar.nested_type(0);
+ const DescriptorProto& qux = file_.message_type(0).nested_type(1);
+
+ EXPECT_TRUE(HasSpan("a", "h", bar));
+ EXPECT_TRUE(HasSpan("b", "c", bar, "name"));
+ EXPECT_TRUE(HasSpan("d", "g", baz));
+ EXPECT_TRUE(HasSpan("e", "f", baz, "name"));
+ EXPECT_TRUE(HasSpan("i", "l", qux));
+ EXPECT_TRUE(HasSpan("j", "k", qux, "name"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.message_type(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0), "name"));
+}
+
+TEST_F(SourceInfoTest, Groups) {
+ EXPECT_TRUE(Parse(
+ "message Foo {\n"
+ " message Bar {}\n"
+ " /*a*/optional/*b*/ /*c*/group/*d*/ /*e*/Baz/*f*/ = /*g*/1/*h*/ {\n"
+ " /*i*/message Qux {}/*j*/\n"
+ " }/*k*/\n"
+ "}\n"));
+
+ const DescriptorProto& bar = file_.message_type(0).nested_type(0);
+ const DescriptorProto& baz = file_.message_type(0).nested_type(1);
+ const DescriptorProto& qux = baz.nested_type(0);
+ const FieldDescriptorProto& field = file_.message_type(0).field(0);
+
+ EXPECT_TRUE(HasSpan("a", "k", field));
+ EXPECT_TRUE(HasSpan("a", "b", field, "label"));
+ EXPECT_TRUE(HasSpan("c", "d", field, "type"));
+ EXPECT_TRUE(HasSpan("e", "f", field, "name"));
+ EXPECT_TRUE(HasSpan("e", "f", field, "type_name"));
+ EXPECT_TRUE(HasSpan("g", "h", field, "number"));
+
+ EXPECT_TRUE(HasSpan("a", "k", baz));
+ EXPECT_TRUE(HasSpan("e", "f", baz, "name"));
+ EXPECT_TRUE(HasSpan("i", "j", qux));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.message_type(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0), "name"));
+ EXPECT_TRUE(HasSpan(bar));
+ EXPECT_TRUE(HasSpan(bar, "name"));
+ EXPECT_TRUE(HasSpan(qux, "name"));
+}
+
+TEST_F(SourceInfoTest, Enums) {
+ EXPECT_TRUE(Parse(
+ "/*a*/enum /*b*/Foo/*c*/ {}/*d*/\n"
+ "/*e*/enum /*f*/Bar/*g*/ {}/*h*/\n"));
+
+ EXPECT_TRUE(HasSpan("a", "d", file_.enum_type(0)));
+ EXPECT_TRUE(HasSpan("b", "c", file_.enum_type(0), "name"));
+ EXPECT_TRUE(HasSpan("e", "h", file_.enum_type(1)));
+ EXPECT_TRUE(HasSpan("f", "g", file_.enum_type(1), "name"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+}
+
+TEST_F(SourceInfoTest, EnumValues) {
+ EXPECT_TRUE(Parse(
+ "enum Foo {\n"
+ " /*a*/BAR/*b*/ = /*c*/1/*d*/;/*e*/\n"
+ " /*f*/BAZ/*g*/ = /*h*/2/*i*/;/*j*/\n"
+ "}"));
+
+ const EnumValueDescriptorProto& bar = file_.enum_type(0).value(0);
+ const EnumValueDescriptorProto& baz = file_.enum_type(0).value(1);
+
+ EXPECT_TRUE(HasSpan("a", "e", bar));
+ EXPECT_TRUE(HasSpan("a", "b", bar, "name"));
+ EXPECT_TRUE(HasSpan("c", "d", bar, "number"));
+ EXPECT_TRUE(HasSpan("f", "j", baz));
+ EXPECT_TRUE(HasSpan("f", "g", baz, "name"));
+ EXPECT_TRUE(HasSpan("h", "i", baz, "number"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0)));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0), "name"));
+}
+
+TEST_F(SourceInfoTest, NestedEnums) {
+ EXPECT_TRUE(Parse(
+ "message Foo {\n"
+ " /*a*/enum /*b*/Bar/*c*/ {}/*d*/\n"
+ " /*e*/enum /*f*/Baz/*g*/ {}/*h*/\n"
+ "}\n"));
+
+ const EnumDescriptorProto& bar = file_.message_type(0).enum_type(0);
+ const EnumDescriptorProto& baz = file_.message_type(0).enum_type(1);
+
+ EXPECT_TRUE(HasSpan("a", "d", bar));
+ EXPECT_TRUE(HasSpan("b", "c", bar, "name"));
+ EXPECT_TRUE(HasSpan("e", "h", baz));
+ EXPECT_TRUE(HasSpan("f", "g", baz, "name"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.message_type(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0), "name"));
+}
+
+TEST_F(SourceInfoTest, Services) {
+ EXPECT_TRUE(Parse(
+ "/*a*/service /*b*/Foo/*c*/ {}/*d*/\n"
+ "/*e*/service /*f*/Bar/*g*/ {}/*h*/\n"));
+
+ EXPECT_TRUE(HasSpan("a", "d", file_.service(0)));
+ EXPECT_TRUE(HasSpan("b", "c", file_.service(0), "name"));
+ EXPECT_TRUE(HasSpan("e", "h", file_.service(1)));
+ EXPECT_TRUE(HasSpan("f", "g", file_.service(1), "name"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+}
+
+TEST_F(SourceInfoTest, Methods) {
+ EXPECT_TRUE(Parse(
+ "service Foo {\n"
+ " /*a*/rpc /*b*/Bar/*c*/(/*d*/X/*e*/) returns(/*f*/Y/*g*/);/*h*/"
+ " /*i*/rpc /*j*/Baz/*k*/(/*l*/Z/*m*/) returns(/*n*/W/*o*/);/*p*/"
+ "}"));
+
+ const MethodDescriptorProto& bar = file_.service(0).method(0);
+ const MethodDescriptorProto& baz = file_.service(0).method(1);
+
+ EXPECT_TRUE(HasSpan("a", "h", bar));
+ EXPECT_TRUE(HasSpan("b", "c", bar, "name"));
+ EXPECT_TRUE(HasSpan("d", "e", bar, "input_type"));
+ EXPECT_TRUE(HasSpan("f", "g", bar, "output_type"));
+
+ EXPECT_TRUE(HasSpan("i", "p", baz));
+ EXPECT_TRUE(HasSpan("j", "k", baz, "name"));
+ EXPECT_TRUE(HasSpan("l", "m", baz, "input_type"));
+ EXPECT_TRUE(HasSpan("n", "o", baz, "output_type"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.service(0)));
+ EXPECT_TRUE(HasSpan(file_.service(0), "name"));
+}
+
+TEST_F(SourceInfoTest, Options) {
+ EXPECT_TRUE(Parse(
+ "/*a*/option /*b*/foo/*c*/./*d*/(/*e*/bar.baz/*f*/)/*g*/ = "
+ "/*h*/123/*i*/;/*j*/\n"
+ "/*k*/option qux = /*l*/-123/*m*/;/*n*/\n"
+ "/*o*/option corge = /*p*/abc/*q*/;/*r*/\n"
+ "/*s*/option grault = /*t*/'blah'/*u*/;/*v*/\n"
+ "/*w*/option garply = /*x*/{ yadda yadda }/*y*/;/*z*/\n"
+ "/*0*/option waldo = /*1*/123.0/*2*/;/*3*/\n"
+ ));
+
+ const UninterpretedOption& option1 = file_.options().uninterpreted_option(0);
+ const UninterpretedOption& option2 = file_.options().uninterpreted_option(1);
+ const UninterpretedOption& option3 = file_.options().uninterpreted_option(2);
+ const UninterpretedOption& option4 = file_.options().uninterpreted_option(3);
+ const UninterpretedOption& option5 = file_.options().uninterpreted_option(4);
+ const UninterpretedOption& option6 = file_.options().uninterpreted_option(5);
+
+ EXPECT_TRUE(HasSpan("a", "j", file_.options()));
+ EXPECT_TRUE(HasSpan("b", "i", option1));
+ EXPECT_TRUE(HasSpan("b", "g", option1, "name"));
+ EXPECT_TRUE(HasSpan("b", "c", option1.name(0)));
+ EXPECT_TRUE(HasSpan("b", "c", option1.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan("d", "g", option1.name(1)));
+ EXPECT_TRUE(HasSpan("e", "f", option1.name(1), "name_part"));
+ EXPECT_TRUE(HasSpan("h", "i", option1, "positive_int_value"));
+
+ EXPECT_TRUE(HasSpan("k", "n", file_.options()));
+ EXPECT_TRUE(HasSpan("l", "m", option2, "negative_int_value"));
+
+ EXPECT_TRUE(HasSpan("o", "r", file_.options()));
+ EXPECT_TRUE(HasSpan("p", "q", option3, "identifier_value"));
+
+ EXPECT_TRUE(HasSpan("s", "v", file_.options()));
+ EXPECT_TRUE(HasSpan("t", "u", option4, "string_value"));
+
+ EXPECT_TRUE(HasSpan("w", "z", file_.options()));
+ EXPECT_TRUE(HasSpan("x", "y", option5, "aggregate_value"));
+
+ EXPECT_TRUE(HasSpan("0", "3", file_.options()));
+ EXPECT_TRUE(HasSpan("1", "2", option6, "double_value"));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(option2));
+ EXPECT_TRUE(HasSpan(option3));
+ EXPECT_TRUE(HasSpan(option4));
+ EXPECT_TRUE(HasSpan(option5));
+ EXPECT_TRUE(HasSpan(option6));
+ EXPECT_TRUE(HasSpan(option2, "name"));
+ EXPECT_TRUE(HasSpan(option3, "name"));
+ EXPECT_TRUE(HasSpan(option4, "name"));
+ EXPECT_TRUE(HasSpan(option5, "name"));
+ EXPECT_TRUE(HasSpan(option6, "name"));
+ EXPECT_TRUE(HasSpan(option2.name(0)));
+ EXPECT_TRUE(HasSpan(option3.name(0)));
+ EXPECT_TRUE(HasSpan(option4.name(0)));
+ EXPECT_TRUE(HasSpan(option5.name(0)));
+ EXPECT_TRUE(HasSpan(option6.name(0)));
+ EXPECT_TRUE(HasSpan(option2.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(option3.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(option4.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(option5.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(option6.name(0), "name_part"));
+}
+
+TEST_F(SourceInfoTest, ScopedOptions) {
+ EXPECT_TRUE(Parse(
+ "message Foo {\n"
+ " /*a*/option mopt = 1;/*b*/\n"
+ "}\n"
+ "enum Bar {\n"
+ " /*c*/option eopt = 1;/*d*/\n"
+ "}\n"
+ "service Baz {\n"
+ " /*e*/option sopt = 1;/*f*/\n"
+ " rpc M(X) returns(Y) {\n"
+ " /*g*/option mopt = 1;/*h*/\n"
+ " }\n"
+ "}\n"));
+
+ EXPECT_TRUE(HasSpan("a", "b", file_.message_type(0).options()));
+ EXPECT_TRUE(HasSpan("c", "d", file_.enum_type(0).options()));
+ EXPECT_TRUE(HasSpan("e", "f", file_.service(0).options()));
+ EXPECT_TRUE(HasSpan("g", "h", file_.service(0).method(0).options()));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.message_type(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0), "name"));
+ EXPECT_TRUE(HasSpan(file_.message_type(0).options()
+ .uninterpreted_option(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0).options()
+ .uninterpreted_option(0), "name"));
+ EXPECT_TRUE(HasSpan(file_.message_type(0).options()
+ .uninterpreted_option(0).name(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0).options()
+ .uninterpreted_option(0).name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(file_.message_type(0).options()
+ .uninterpreted_option(0), "positive_int_value"));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0)));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0), "name"));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0).options()
+ .uninterpreted_option(0)));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0).options()
+ .uninterpreted_option(0), "name"));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0).options()
+ .uninterpreted_option(0).name(0)));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0).options()
+ .uninterpreted_option(0).name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0).options()
+ .uninterpreted_option(0), "positive_int_value"));
+ EXPECT_TRUE(HasSpan(file_.service(0)));
+ EXPECT_TRUE(HasSpan(file_.service(0), "name"));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0)));
+ EXPECT_TRUE(HasSpan(file_.service(0).options()
+ .uninterpreted_option(0)));
+ EXPECT_TRUE(HasSpan(file_.service(0).options()
+ .uninterpreted_option(0), "name"));
+ EXPECT_TRUE(HasSpan(file_.service(0).options()
+ .uninterpreted_option(0).name(0)));
+ EXPECT_TRUE(HasSpan(file_.service(0).options()
+ .uninterpreted_option(0).name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(file_.service(0).options()
+ .uninterpreted_option(0), "positive_int_value"));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0), "name"));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0), "input_type"));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0), "output_type"));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0).options()
+ .uninterpreted_option(0)));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0).options()
+ .uninterpreted_option(0), "name"));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0).options()
+ .uninterpreted_option(0).name(0)));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0).options()
+ .uninterpreted_option(0).name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(file_.service(0).method(0).options()
+ .uninterpreted_option(0), "positive_int_value"));
+}
+
+TEST_F(SourceInfoTest, FieldOptions) {
+ // The actual "name = value" pairs are parsed by the same code as for
+ // top-level options so we won't re-test that -- just make sure that the
+ // syntax used for field options is understood.
+ EXPECT_TRUE(Parse(
+ "message Foo {"
+ " optional int32 bar = 1 "
+ "/*a*/[default=/*b*/123/*c*/,/*d*/opt1=123/*e*/,"
+ "/*f*/opt2='hi'/*g*/]/*h*/;"
+ "}\n"
+ ));
+
+ const FieldDescriptorProto& field = file_.message_type(0).field(0);
+ const UninterpretedOption& option1 = field.options().uninterpreted_option(0);
+ const UninterpretedOption& option2 = field.options().uninterpreted_option(1);
+
+ EXPECT_TRUE(HasSpan("a", "h", field.options()));
+ EXPECT_TRUE(HasSpan("b", "c", field, "default_value"));
+ EXPECT_TRUE(HasSpan("d", "e", option1));
+ EXPECT_TRUE(HasSpan("f", "g", option2));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.message_type(0)));
+ EXPECT_TRUE(HasSpan(file_.message_type(0), "name"));
+ EXPECT_TRUE(HasSpan(field));
+ EXPECT_TRUE(HasSpan(field, "label"));
+ EXPECT_TRUE(HasSpan(field, "type"));
+ EXPECT_TRUE(HasSpan(field, "name"));
+ EXPECT_TRUE(HasSpan(field, "number"));
+ EXPECT_TRUE(HasSpan(option1, "name"));
+ EXPECT_TRUE(HasSpan(option2, "name"));
+ EXPECT_TRUE(HasSpan(option1.name(0)));
+ EXPECT_TRUE(HasSpan(option2.name(0)));
+ EXPECT_TRUE(HasSpan(option1.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(option2.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(option1, "positive_int_value"));
+ EXPECT_TRUE(HasSpan(option2, "string_value"));
+}
+
+TEST_F(SourceInfoTest, EnumValueOptions) {
+ // The actual "name = value" pairs are parsed by the same code as for
+ // top-level options so we won't re-test that -- just make sure that the
+ // syntax used for enum options is understood.
+ EXPECT_TRUE(Parse(
+ "enum Foo {"
+ " BAR = 1 /*a*/[/*b*/opt1=123/*c*/,/*d*/opt2='hi'/*e*/]/*f*/;"
+ "}\n"
+ ));
+
+ const EnumValueDescriptorProto& value = file_.enum_type(0).value(0);
+ const UninterpretedOption& option1 = value.options().uninterpreted_option(0);
+ const UninterpretedOption& option2 = value.options().uninterpreted_option(1);
+
+ EXPECT_TRUE(HasSpan("a", "f", value.options()));
+ EXPECT_TRUE(HasSpan("b", "c", option1));
+ EXPECT_TRUE(HasSpan("d", "e", option2));
+
+ // Ignore these.
+ EXPECT_TRUE(HasSpan(file_));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0)));
+ EXPECT_TRUE(HasSpan(file_.enum_type(0), "name"));
+ EXPECT_TRUE(HasSpan(value));
+ EXPECT_TRUE(HasSpan(value, "name"));
+ EXPECT_TRUE(HasSpan(value, "number"));
+ EXPECT_TRUE(HasSpan(option1, "name"));
+ EXPECT_TRUE(HasSpan(option2, "name"));
+ EXPECT_TRUE(HasSpan(option1.name(0)));
+ EXPECT_TRUE(HasSpan(option2.name(0)));
+ EXPECT_TRUE(HasSpan(option1.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(option2.name(0), "name_part"));
+ EXPECT_TRUE(HasSpan(option1, "positive_int_value"));
+ EXPECT_TRUE(HasSpan(option2, "string_value"));
+}
+
+// ===================================================================
} // anonymous namespace