From e96ff30120a3834f7d1e31e43e591bf7cfbd731f Mon Sep 17 00:00:00 2001 From: Feng Xiao Date: Mon, 15 Jun 2015 18:21:48 -0700 Subject: Down-integrate from internal code base. [ci skip] Change-Id: I9391c09640e0b0b2b21c45a97a1fc91814d95c5d --- .../util/internal/protostream_objectwriter_test.cc | 1513 ++++++++++++++++++++ 1 file changed, 1513 insertions(+) create mode 100644 src/google/protobuf/util/internal/protostream_objectwriter_test.cc (limited to 'src/google/protobuf/util/internal/protostream_objectwriter_test.cc') diff --git a/src/google/protobuf/util/internal/protostream_objectwriter_test.cc b/src/google/protobuf/util/internal/protostream_objectwriter_test.cc new file mode 100644 index 00000000..bd4f29f5 --- /dev/null +++ b/src/google/protobuf/util/internal/protostream_objectwriter_test.cc @@ -0,0 +1,1513 @@ +// Protocol Buffers - Google's data interchange format +// Copyright 2008 Google Inc. All rights reserved. +// https://developers.google.com/protocol-buffers/ +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +#include + +#include // For size_t + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +namespace google { +namespace protobuf { +namespace util { +namespace converter { + +using google::protobuf::testing::Author; +using google::protobuf::testing::Book; +using google::protobuf::testing::Book_Data; +using google::protobuf::testing::Primitive; +using google::protobuf::testing::Publisher; +using google::protobuf::Descriptor; +using google::protobuf::DescriptorPool; +using google::protobuf::DynamicMessageFactory; +using google::protobuf::FileDescriptorProto; +using google::protobuf::Message; +using google::protobuf::io::ArrayInputStream; +using strings::GrowingArrayByteSink; +using ::testing::_; +using ::testing::Args; +using google::protobuf::testing::anys::AnyM; +using google::protobuf::testing::anys::AnyOut; +using google::protobuf::testing::FieldMaskTest; +using google::protobuf::testing::maps::MapIn; +using google::protobuf::testing::structs::StructType; +using google::protobuf::testing::timestampduration::TimestampDuration; + + +namespace { +string GetTypeUrl(const Descriptor* descriptor) { + return string(kTypeServiceBaseUrl) + "/" + descriptor->full_name(); +} +} // namespace + +class BaseProtoStreamObjectWriterTest + : public ::testing::TestWithParam { + protected: + BaseProtoStreamObjectWriterTest() + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() {} + + explicit BaseProtoStreamObjectWriterTest(const Descriptor* descriptor) + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() { + vector descriptors; + descriptors.push_back(descriptor); + ResetTypeInfo(descriptors); + } + + explicit BaseProtoStreamObjectWriterTest( + vector descriptors) + : helper_(GetParam()), + listener_(), + output_(new GrowingArrayByteSink(1000)), + ow_() { + ResetTypeInfo(descriptors); + } + + void ResetTypeInfo(vector descriptors) { + GOOGLE_CHECK(!descriptors.empty()) << "Must have at least one descriptor!"; + helper_.ResetTypeInfo(descriptors); + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(descriptors[0]), output_.get(), + &listener_)); + } + + virtual ~BaseProtoStreamObjectWriterTest() {} + + void CheckOutput(const Message& expected, int expected_length) { + size_t nbytes; + google::protobuf::scoped_array buffer(output_->GetBuffer(&nbytes)); + if (expected_length >= 0) { + EXPECT_EQ(expected_length, nbytes); + } + string str(buffer.get(), nbytes); + + std::stringbuf str_buf(str, std::ios_base::in); + std::istream istream(&str_buf); + google::protobuf::scoped_ptr message(expected.New()); + message->ParsePartialFromIstream(&istream); + + EXPECT_EQ(expected.DebugString(), message->DebugString()); + } + + void CheckOutput(const Message& expected) { CheckOutput(expected, -1); } + + const google::protobuf::Type* GetType(const Descriptor* descriptor) { + return helper_.GetTypeInfo()->GetType(GetTypeUrl(descriptor)); + } + + testing::TypeInfoTestHelper helper_; + MockErrorListener listener_; + google::protobuf::scoped_ptr output_; + google::protobuf::scoped_ptr ow_; +}; + +MATCHER_P(HasObjectLocation, expected, + "Verifies the expected object location") { + string actual = std::tr1::get<0>(arg).ToString(); + if (actual.compare(expected) == 0) return true; + *result_listener << "actual location is: " << actual; + return false; +} + +class ProtoStreamObjectWriterTest : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterTest() + : BaseProtoStreamObjectWriterTest(Book::descriptor()) {} + + virtual ~ProtoStreamObjectWriterTest() {} +}; + +INSTANTIATE_TEST_CASE_P(DifferentTypeInfoSourceTest, + ProtoStreamObjectWriterTest, + ::testing::Values( + testing::USE_TYPE_RESOLVER)); + +TEST_P(ProtoStreamObjectWriterTest, EmptyObject) { + Book empty; + ow_->StartObject("")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, SimpleObject) { + string content("My content"); + + Book book; + book.set_title("My Title"); + book.set_length(222); + book.set_content(content); + + ow_->StartObject("") + ->RenderString("title", "My Title") + ->RenderInt32("length", 222) + ->RenderBytes("content", content) + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, SimpleMessage) { + Book book; + book.set_title("Some Book"); + book.set_length(102); + Publisher* publisher = book.mutable_publisher(); + publisher->set_name("My Publisher"); + Author* robert = book.mutable_author(); + robert->set_alive(true); + robert->set_name("robert"); + robert->add_pseudonym("bob"); + robert->add_pseudonym("bobby"); + robert->add_friend_()->set_name("john"); + + ow_->StartObject("") + ->RenderString("title", "Some Book") + ->RenderInt32("length", 102) + ->StartObject("publisher") + ->RenderString("name", "My Publisher") + ->EndObject() + ->StartObject("author") + ->RenderBool("alive", true) + ->RenderString("name", "robert") + ->StartList("pseudonym") + ->RenderString("", "bob") + ->RenderString("", "bobby") + ->EndList() + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "john") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(book); +} + +TEST_P(ProtoStreamObjectWriterTest, PrimitiveFromStringConversion) { + Primitive full; + full.set_fix32(101); + full.set_u32(102); + full.set_i32(-103); + full.set_sf32(-104); + full.set_s32(-105); + full.set_fix64(40000000001L); + full.set_u64(40000000002L); + full.set_i64(-40000000003L); + full.set_sf64(-40000000004L); + full.set_s64(-40000000005L); + full.set_str("string1"); + full.set_bytes("Some Bytes"); + full.set_float_(3.14f); + full.set_double_(-4.05L); + full.set_bool_(true); + full.add_rep_fix32(201); + full.add_rep_u32(202); + full.add_rep_i32(-203); + full.add_rep_sf32(-204); + full.add_rep_s32(-205); + full.add_rep_fix64(80000000001L); + full.add_rep_u64(80000000002L); + full.add_rep_i64(-80000000003L); + full.add_rep_sf64(-80000000004L); + full.add_rep_s64(-80000000005L); + full.add_rep_str("string2"); + full.add_rep_bytes("More Bytes"); + full.add_rep_float(6.14f); + full.add_rep_double(-8.05L); + full.add_rep_bool(false); + + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()), + output_.get(), &listener_)); + + ow_->StartObject("") + ->RenderString("fix32", "101") + ->RenderString("u32", "102") + ->RenderString("i32", "-103") + ->RenderString("sf32", "-104") + ->RenderString("s32", "-105") + ->RenderString("fix64", "40000000001") + ->RenderString("u64", "40000000002") + ->RenderString("i64", "-40000000003") + ->RenderString("sf64", "-40000000004") + ->RenderString("s64", "-40000000005") + ->RenderString("str", "string1") + ->RenderString("bytes", "U29tZSBCeXRlcw==") // "Some Bytes" + ->RenderString("float", "3.14") + ->RenderString("double", "-4.05") + ->RenderString("bool", "true") + ->StartList("rep_fix32") + ->RenderString("", "201") + ->EndList() + ->StartList("rep_u32") + ->RenderString("", "202") + ->EndList() + ->StartList("rep_i32") + ->RenderString("", "-203") + ->EndList() + ->StartList("rep_sf32") + ->RenderString("", "-204") + ->EndList() + ->StartList("rep_s32") + ->RenderString("", "-205") + ->EndList() + ->StartList("rep_fix64") + ->RenderString("", "80000000001") + ->EndList() + ->StartList("rep_u64") + ->RenderString("", "80000000002") + ->EndList() + ->StartList("rep_i64") + ->RenderString("", "-80000000003") + ->EndList() + ->StartList("rep_sf64") + ->RenderString("", "-80000000004") + ->EndList() + ->StartList("rep_s64") + ->RenderString("", "-80000000005") + ->EndList() + ->StartList("rep_str") + ->RenderString("", "string2") + ->EndList() + ->StartList("rep_bytes") + ->RenderString("", "TW9yZSBCeXRlcw==") // "More Bytes" + ->EndList() + ->StartList("rep_float") + ->RenderString("", "6.14") + ->EndList() + ->StartList("rep_double") + ->RenderString("", "-8.05") + ->EndList() + ->StartList("rep_bool") + ->RenderString("", "false") + ->EndList() + ->EndObject(); + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, InfinityInputTest) { + Primitive full; + full.set_double_(std::numeric_limits::infinity()); + full.set_float_(std::numeric_limits::infinity()); + full.set_str("-Infinity"); + + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()), + output_.get(), &listener_)); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT32"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("i32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("u32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_SFIXED64"), + StringPiece("\"-Infinity\""))) + .With(Args<0>(HasObjectLocation("sf64"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_BOOL"), + StringPiece("\"Infinity\""))) + .With(Args<0>(HasObjectLocation("bool"))); + + ow_->StartObject("") + ->RenderString("double", "Infinity") + ->RenderString("float", "Infinity") + ->RenderString("i32", "Infinity") + ->RenderString("u32", "Infinity") + ->RenderString("sf64", "-Infinity") + ->RenderString("str", "-Infinity") + ->RenderString("bool", "Infinity") + ->EndObject(); + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, NaNInputTest) { + Primitive full; + full.set_double_(std::numeric_limits::quiet_NaN()); + full.set_float_(std::numeric_limits::quiet_NaN()); + full.set_str("NaN"); + + ow_.reset(helper_.NewProtoWriter(GetTypeUrl(Primitive::descriptor()), + output_.get(), &listener_)); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT32"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("i32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("u32"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_SFIXED64"), + StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("sf64"))); + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("TYPE_BOOL"), StringPiece("\"NaN\""))) + .With(Args<0>(HasObjectLocation("bool"))); + + ow_->StartObject("") + ->RenderString("double", "NaN") + ->RenderString("float", "NaN") + ->RenderString("i32", "NaN") + ->RenderString("u32", "NaN") + ->RenderString("sf64", "NaN") + ->RenderString("str", "NaN") + ->RenderString("bool", "NaN") + ->EndObject(); + + CheckOutput(full); +} + +TEST_P(ProtoStreamObjectWriterTest, ImplicitPrimitiveList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + author->add_pseudonym("first"); + author->add_pseudonym("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->RenderString("pseudonym", "first") + ->RenderString("pseudonym", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, + LastWriteWinsOnNonRepeatedPrimitiveFieldWithDuplicates) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "first") + ->RenderString("name", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ExplicitPrimitiveList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + author->add_pseudonym("first"); + author->add_pseudonym("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->StartList("pseudonym") + ->RenderString("", "first") + ->RenderString("", "second") + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, NonRepeatedExplicitPrimitiveList) { + Book expected; + expected.set_allocated_author(new Author()); + + EXPECT_CALL( + listener_, + InvalidName( + _, StringPiece("name"), + StringPiece("Proto field is not repeating, cannot start list."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->StartList("name") + ->RenderString("", "first") + ->RenderString("", "second") + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ImplicitMessageList) { + Book expected; + Author* outer = expected.mutable_author(); + outer->set_name("outer"); + outer->set_alive(true); + Author* first = outer->add_friend_(); + first->set_name("first"); + Author* second = outer->add_friend_(); + second->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "outer") + ->RenderBool("alive", true) + ->StartObject("friend") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("friend") + ->RenderString("name", "second") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, + LastWriteWinsOnNonRepeatedMessageFieldWithDuplicates) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + Publisher* publisher = expected.mutable_publisher(); + publisher->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->EndObject() + ->StartObject("publisher") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("publisher") + ->RenderString("name", "second") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, ExplicitMessageList) { + Book expected; + Author* outer = expected.mutable_author(); + outer->set_name("outer"); + outer->set_alive(true); + Author* first = outer->add_friend_(); + first->set_name("first"); + Author* second = outer->add_friend_(); + second->set_name("second"); + + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "outer") + ->RenderBool("alive", true) + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("") + ->RenderString("name", "second") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, NonRepeatedExplicitMessageList) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("The Author"); + + EXPECT_CALL( + listener_, + InvalidName( + _, StringPiece("publisher"), + StringPiece("Proto field is not repeating, cannot start list."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "The Author") + ->EndObject() + ->StartList("publisher") + ->StartObject("") + ->RenderString("name", "first") + ->EndObject() + ->StartObject("") + ->RenderString("name", "second") + ->EndObject() + ->EndList() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownFieldAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderString("unknown", "Nope!")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownFieldAtAuthorFriend) { + Book expected; + Author* paul = expected.mutable_author(); + paul->set_name("Paul"); + Author* mark = paul->add_friend_(); + mark->set_name("Mark"); + Author* john = paul->add_friend_(); + john->set_name("John"); + Author* luke = paul->add_friend_(); + luke->set_name("Luke"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("address"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("author.friend[1]"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "Paul") + ->StartList("friend") + ->StartObject("") + ->RenderString("name", "Mark") + ->EndObject() + ->StartObject("") + ->RenderString("name", "John") + ->RenderString("address", "Patmos") + ->EndObject() + ->StartObject("") + ->RenderString("name", "Luke") + ->EndObject() + ->EndList() + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownObjectAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->StartObject("unknown")->EndObject()->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownObjectAtAuthor) { + Book expected; + Author* author = expected.mutable_author(); + author->set_name("William"); + author->add_pseudonym("Bill"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("wife"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderString("name", "William") + ->StartObject("wife") + ->RenderString("name", "Hilary") + ->EndObject() + ->RenderString("pseudonym", "Bill") + ->EndObject() + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownListAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("unknown"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->StartList("unknown")->EndList()->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnknownListAtPublisher) { + Book expected; + expected.set_title("Brainwashing"); + Publisher* publisher = expected.mutable_publisher(); + publisher->set_name("propaganda"); + + EXPECT_CALL(listener_, InvalidName(_, StringPiece("alliance"), + StringPiece("Cannot find field."))) + .With(Args<0>(HasObjectLocation("publisher"))); + ow_->StartObject("") + ->StartObject("publisher") + ->RenderString("name", "propaganda") + ->StartList("alliance") + ->EndList() + ->EndObject() + ->RenderString("title", "Brainwashing") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, MissingRequiredField) { + Book expected; + expected.set_title("My Title"); + expected.set_allocated_publisher(new Publisher()); + + EXPECT_CALL(listener_, MissingField(_, StringPiece("name"))) + .With(Args<0>(HasObjectLocation("publisher"))); + ow_->StartObject("") + ->StartObject("publisher") + ->EndObject() + ->RenderString("title", "My Title") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, InvalidFieldValueAtRoot) { + Book empty; + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"garbage\""))) + .With(Args<0>(HasObjectLocation("length"))); + ow_->StartObject("")->RenderString("length", "garbage")->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, MultipleInvalidFieldValues) { + Book expected; + expected.set_title("My Title"); + + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_UINT32"), + StringPiece("\"-400\""))) + .With(Args<0>(HasObjectLocation("length"))); + EXPECT_CALL(listener_, InvalidValue(_, StringPiece("TYPE_INT64"), + StringPiece("\"3.14\""))) + .With(Args<0>(HasObjectLocation("published"))); + ow_->StartObject("") + ->RenderString("length", "-400") + ->RenderString("published", "3.14") + ->RenderString("title", "My Title") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedFieldAtRoot) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderFloat("", 3.14)->EndObject(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedFieldAtAuthor) { + Book expected; + expected.set_title("noname"); + expected.set_allocated_author(new Author()); + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation("author"))); + ow_->StartObject("") + ->StartObject("author") + ->RenderInt32("", 123) + ->EndObject() + ->RenderString("title", "noname") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, UnnamedListAtRoot) { + Book expected; + expected.set_title("noname"); + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("") + ->StartList("") + ->EndList() + ->RenderString("title", "noname") + ->EndObject(); + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedObject) { + Book expected; + expected.set_title("Annie"); + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element should not be named."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("oops")->RenderString("title", "Annie")->EndObject(); + CheckOutput(expected, 7); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedList) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element should not be named."))) + .With(Args<0>(HasObjectLocation(""))); + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartList("oops")->RenderString("", "item")->EndList(); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, RootUnnamedField) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Root element must be a message."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->RenderBool("", true); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, RootNamedField) { + Book empty; + + EXPECT_CALL(listener_, + InvalidName(_, StringPiece("oops"), + StringPiece("Root element must be a message."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->RenderBool("oops", true); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValue) { + Book empty; + + ow_->RenderNull(""); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValueForMessageField) { + Book empty; + + ow_->RenderNull("author"); + CheckOutput(empty, 0); +} + +TEST_P(ProtoStreamObjectWriterTest, NullValueForPrimitiveField) { + Book empty; + + ow_->RenderNull("length"); + CheckOutput(empty, 0); +} + +class ProtoStreamObjectWriterTimestampDurationTest + : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterTimestampDurationTest() { + vector descriptors; + descriptors.push_back(TimestampDuration::descriptor()); + descriptors.push_back(google::protobuf::Timestamp::descriptor()); + descriptors.push_back(google::protobuf::Duration::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError1) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Illegal timestamp format; timestamps " + "must end with 'Z'"))); + + ow_->StartObject("")->RenderString("ts", "")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError2) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Invalid time format: Failed to parse input"))); + + ow_->StartObject("")->RenderString("ts", "Z")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError3) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Invalid time format, failed to parse nano " + "seconds"))); + + ow_->StartObject("") + ->RenderString("ts", "1970-01-01T00:00:00.ABZ") + ->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidTimestampError4) { + TimestampDuration timestamp; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece("Field 'ts', Timestamp value exceeds limits"))); + + ow_->StartObject("") + ->RenderString("ts", "-8032-10-18T00:00:00.000Z") + ->EndObject(); + CheckOutput(timestamp); +} + +// TODO(skarvaje): Write a test for nanos that exceed limit. Currently, it is +// not possible to construct a test case where nanos exceed limit because of +// floating point arithmetic. + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError1) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Illegal duration format; duration must " + "end with 's'"))); + + ow_->StartObject("")->RenderString("dur", "")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError2) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Invalid duration format, failed to parse " + "seconds"))); + + ow_->StartObject("")->RenderString("dur", "s")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError3) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Invalid duration format, failed to " + "parse nanos seconds"))); + + ow_->StartObject("")->RenderString("dur", "123.DEFs")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, InvalidDurationError4) { + TimestampDuration duration; + + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece("Field 'dur', Duration value exceeds limits"))); + + ow_->StartObject("")->RenderString("dur", "315576000002s")->EndObject(); + CheckOutput(duration); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + MismatchedTimestampTypeInput) { + TimestampDuration timestamp; + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Timestamp"), + StringPiece( + "Field 'ts', Invalid data type for timestamp, value is null"))) + .With(Args<0>(HasObjectLocation("ts"))); + ow_->StartObject("")->RenderNull("ts")->EndObject(); + CheckOutput(timestamp); +} + +TEST_P(ProtoStreamObjectWriterTimestampDurationTest, + MismatchedDurationTypeInput) { + TimestampDuration duration; + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.Duration"), + StringPiece( + "Field 'dur', Invalid data type for duration, value is null"))) + .With(Args<0>(HasObjectLocation("dur"))); + ow_->StartObject("")->RenderNull("dur")->EndObject(); + CheckOutput(duration); +} + +class ProtoStreamObjectWriterStructTest + : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterStructTest() { + vector descriptors; + descriptors.push_back(StructType::descriptor()); + descriptors.push_back(google::protobuf::Struct::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +// TODO(skarvaje): Write tests for failure cases. +TEST_P(ProtoStreamObjectWriterStructTest, StructRenderSuccess) { + StructType struct_type; + google::protobuf::Struct* s = struct_type.mutable_object(); + s->mutable_fields()->operator[]("k1").set_number_value(123); + s->mutable_fields()->operator[]("k2").set_bool_value(true); + + ow_->StartObject("") + ->StartObject("object") + ->RenderDouble("k1", 123) + ->RenderBool("k2", true) + ->EndObject() + ->EndObject(); + CheckOutput(struct_type); +} + +TEST_P(ProtoStreamObjectWriterStructTest, StructNullInputSuccess) { + StructType struct_type; + EXPECT_CALL(listener_, + InvalidName(_, StringPiece(""), + StringPiece("Proto fields must have a name."))) + .With(Args<0>(HasObjectLocation(""))); + ow_->StartObject("")->RenderNull("")->EndObject(); + CheckOutput(struct_type); +} + +TEST_P(ProtoStreamObjectWriterStructTest, StructInvalidInputFailure) { + StructType struct_type; + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("type.googleapis.com/google.protobuf.Struct"), + StringPiece("true"))) + .With(Args<0>(HasObjectLocation("object"))); + + ow_->StartObject("")->RenderBool("object", true)->EndObject(); + CheckOutput(struct_type); +} + +class ProtoStreamObjectWriterMapTest : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterMapTest() + : BaseProtoStreamObjectWriterTest(MapIn::descriptor()) {} +}; + +TEST_P(ProtoStreamObjectWriterMapTest, MapShouldNotAcceptList) { + MapIn mm; + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Map"), + StringPiece("Cannot bind a list to map."))) + .With(Args<0>(HasObjectLocation("map_input"))); + ow_->StartObject("") + ->StartList("map_input") + ->RenderString("a", "b") + ->EndList() + ->EndObject(); + CheckOutput(mm); +} + +class ProtoStreamObjectWriterAnyTest : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterAnyTest() { + vector descriptors; + descriptors.push_back(AnyOut::descriptor()); + descriptors.push_back(google::protobuf::DoubleValue::descriptor()); + descriptors.push_back(google::protobuf::Any::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyRenderSuccess) { + AnyOut any; + google::protobuf::Any* any_type = any.mutable_any(); + any_type->set_type_url("type.googleapis.com/google.protobuf.DoubleValue"); + google::protobuf::DoubleValue d; + d.set_value(40.2); + any_type->set_value(d.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.DoubleValue") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, RecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any nested_any; + nested_any.set_type_url( + "type.googleapis.com/google.protobuf.testing.anys.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + nested_any.set_value(m.SerializeAsString()); + + any->set_value(nested_any.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/google.protobuf.testing.anys.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject(); + + CheckOutput(out, 115); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, DoubleRecursiveAny) { + AnyOut out; + ::google::protobuf::Any* any = out.mutable_any(); + any->set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any nested_any; + nested_any.set_type_url("type.googleapis.com/google.protobuf.Any"); + + ::google::protobuf::Any second_nested_any; + second_nested_any.set_type_url( + "type.googleapis.com/google.protobuf.testing.anys.AnyM"); + + AnyM m; + m.set_foo("foovalue"); + second_nested_any.set_value(m.SerializeAsString()); + + nested_any.set_value(second_nested_any.SerializeAsString()); + any->set_value(nested_any.SerializeAsString()); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", "type.googleapis.com/google.protobuf.Any") + ->StartObject("value") + ->RenderString("@type", + "type.googleapis.com/google.protobuf.testing.anys.AnyM") + ->RenderString("foo", "foovalue") + ->EndObject() + ->EndObject() + ->EndObject() + ->EndObject(); + + CheckOutput(out, 159); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, EmptyAnyFromEmptyObject) { + AnyOut out; + out.mutable_any(); + + ow_->StartObject("")->StartObject("any")->EndObject()->EndObject(); + + CheckOutput(out, 2); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails1) { + AnyOut any; + + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Missing or invalid @type for any field in " + "google.protobuf.testing.anys.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->StartObject("another") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails2) { + AnyOut any; + + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Missing or invalid @type for any field in " + "google.protobuf.testing.anys.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->StartList("another") + ->EndObject() + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithoutTypeUrlFails3) { + AnyOut any; + + EXPECT_CALL(listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Missing or invalid @type for any field in " + "google.protobuf.testing.anys.AnyOut"))); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("value", "somevalue") + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithInvalidTypeUrlFails) { + AnyOut any; + + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece( + "Invalid type URL, type URLs must be of the form " + "'type.googleapis.com/', got: " + "type.other.com/some.Type"))); + + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.other.com/some.Type") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyWithUnknownTypeFails) { + AnyOut any; + + EXPECT_CALL( + listener_, + InvalidValue(_, StringPiece("Any"), + StringPiece("Invalid type URL, unknown type: some.Type"))); + ow_->StartObject("") + ->StartObject("any") + ->RenderString("@type", "type.googleapis.com/some.Type") + ->RenderDouble("value", 40.2) + ->EndObject() + ->EndObject(); + CheckOutput(any); +} + +TEST_P(ProtoStreamObjectWriterAnyTest, AnyNullInputFails) { + AnyOut any; + + ow_->StartObject("")->RenderNull("any")->EndObject(); + CheckOutput(any); +} + +class ProtoStreamObjectWriterFieldMaskTest + : public BaseProtoStreamObjectWriterTest { + protected: + ProtoStreamObjectWriterFieldMaskTest() { + vector descriptors; + descriptors.push_back(FieldMaskTest::descriptor()); + descriptors.push_back(google::protobuf::FieldMask::descriptor()); + ResetTypeInfo(descriptors); + } +}; + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, SimpleFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + expected.mutable_single_mask()->add_paths("path1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "path1"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MutipleMasksInCompactForm) { + FieldMaskTest expected; + expected.set_id("1"); + expected.mutable_single_mask()->add_paths("camel_case1"); + expected.mutable_single_mask()->add_paths("camel_case2"); + expected.mutable_single_mask()->add_paths("camel_case3"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "camelCase1,camelCase2,camelCase3"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, RepeatedFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + google::protobuf::FieldMask* mask = expected.add_repeated_mask(); + mask->add_paths("field1"); + mask->add_paths("field2"); + expected.add_repeated_mask()->add_paths("field3"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->StartList("repeated_mask"); + ow_->RenderString("", "field1,field2"); + ow_->RenderString("", "field3"); + ow_->EndList(); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, EmptyFieldMaskTest) { + FieldMaskTest expected; + expected.set_id("1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", ""); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MaskUsingApiaryStyleShouldWork) { + FieldMaskTest expected; + expected.set_id("1"); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + // Case1 + ow_->RenderString("single_mask", + "outerField(camelCase1,camelCase2,camelCase3)"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case1"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case2"); + expected.mutable_single_mask()->add_paths("outer_field.camel_case3"); + + ow_->StartList("repeated_mask"); + + ow_->RenderString("", "a(field1,field2)"); + google::protobuf::FieldMask* mask = expected.add_repeated_mask(); + mask->add_paths("a.field1"); + mask->add_paths("a.field2"); + + ow_->RenderString("", "a(field3)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field3"); + + ow_->RenderString("", "a()"); + expected.add_repeated_mask(); + + ow_->RenderString("", "a(,)"); + expected.add_repeated_mask(); + + ow_->RenderString("", "a(field1(field2(field3)))"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field1.field2.field3"); + + ow_->RenderString("", "a(field1(field2(field3,field4),field5),field6)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field1.field2.field3"); + mask->add_paths("a.field1.field2.field4"); + mask->add_paths("a.field1.field5"); + mask->add_paths("a.field6"); + + ow_->RenderString("", "a(id,field1(id,field2(field3,field4),field5),field6)"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.id"); + mask->add_paths("a.field1.id"); + mask->add_paths("a.field1.field2.field3"); + mask->add_paths("a.field1.field2.field4"); + mask->add_paths("a.field1.field5"); + mask->add_paths("a.field6"); + + ow_->RenderString("", "a(((field3,field4)))"); + mask = expected.add_repeated_mask(); + mask->add_paths("a.field3"); + mask->add_paths("a.field4"); + + ow_->EndList(); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MoreCloseThanOpenParentheses) { + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask 'a(b,c))'. " + "Cannot find matching '(' for all ')'."))); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "a(b,c))"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MoreOpenThanCloseParentheses) { + EXPECT_CALL( + listener_, + InvalidValue( + _, StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask 'a(((b,c)'. Cannot " + "find matching ')' for all '('."))); + + ow_->StartObject(""); + ow_->RenderString("id", "1"); + ow_->RenderString("single_mask", "a(((b,c)"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, PathWithMapKeyShouldWork) { + FieldMaskTest expected; + expected.mutable_single_mask()->add_paths("path.to.map[\"key1\"]"); + expected.mutable_single_mask()->add_paths( + "path.to.map[\"e\\\"[]][scape\\\"\"]"); + expected.mutable_single_mask()->add_paths("path.to.map[\"key2\"]"); + + ow_->StartObject(""); + ow_->RenderString("single_mask", + "path.to.map[\"key1\"],path.to.map[\"e\\\"[]][scape\\\"\"]," + "path.to.map[\"key2\"]"); + ow_->EndObject(); + + CheckOutput(expected); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, + MapKeyMustBeAtTheEndOfAPathSegment) { + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"key1\"]a,path.to.map[\"key2\"]'. " + "Map keys should be at the end of a path segment."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", + "path.to.map[\"key1\"]a,path.to.map[\"key2\"]"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyMustEnd) { + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"key1\"'. Map keys should be " + "represented as [\"some_key\"]."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", "path.to.map[\"key1\""); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyMustBeEscapedCorrectly) { + EXPECT_CALL( + listener_, + InvalidValue(_, + StringPiece("type.googleapis.com/google.protobuf.FieldMask"), + StringPiece( + "Field 'single_mask', Invalid FieldMask " + "'path.to.map[\"ke\"y1\"]'. Map keys should be " + "represented as [\"some_key\"]."))); + + ow_->StartObject(""); + ow_->RenderString("single_mask", "path.to.map[\"ke\"y1\"]"); + ow_->EndObject(); +} + +TEST_P(ProtoStreamObjectWriterFieldMaskTest, MapKeyCanContainAnyChars) { + FieldMaskTest expected; + expected.mutable_single_mask()->add_paths( + "path.to.map[\"(),[],\\\"'!@#$%^&*123_|War孙天涌,./?><\\\\\"]"); + expected.mutable_single_mask()->add_paths("path.to.map[\"key2\"]"); + + ow_->StartObject(""); + ow_->RenderString( + "single_mask", + "path.to.map[\"(),[],\\\"'!@#$%^&*123_|War孙天涌,./?><\\\\\"]," + "path.to.map[\"key2\"]"); + ow_->EndObject(); + + CheckOutput(expected); +} + +} // namespace converter +} // namespace util +} // namespace protobuf +} // namespace google -- cgit v1.2.3