aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
diff options
context:
space:
mode:
Diffstat (limited to 'Firestore/core/test/firebase/firestore/remote/serializer_test.cc')
-rw-r--r--Firestore/core/test/firebase/firestore/remote/serializer_test.cc434
1 files changed, 402 insertions, 32 deletions
diff --git a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
index 96125f7..a147309 100644
--- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
+++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
@@ -33,18 +33,41 @@
#include <vector>
#include "Firestore/Protos/cpp/google/firestore/v1beta1/document.pb.h"
+#include "Firestore/Protos/cpp/google/firestore/v1beta1/firestore.pb.h"
+#include "Firestore/core/include/firebase/firestore/firestore_errors.h"
+#include "Firestore/core/include/firebase/firestore/timestamp.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
+#include "Firestore/core/src/firebase/firestore/model/snapshot_version.h"
+#include "Firestore/core/src/firebase/firestore/timestamp_internal.h"
#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "Firestore/core/src/firebase/firestore/util/statusor.h"
+#include "Firestore/core/test/firebase/firestore/testutil/testutil.h"
#include "google/protobuf/stubs/common.h"
#include "google/protobuf/util/message_differencer.h"
#include "gtest/gtest.h"
+using firebase::Timestamp;
+using firebase::TimestampInternal;
+using firebase::firestore::FirestoreErrorCode;
+using firebase::firestore::model::DatabaseId;
+using firebase::firestore::model::Document;
+using firebase::firestore::model::DocumentKey;
using firebase::firestore::model::FieldValue;
+using firebase::firestore::model::MaybeDocument;
+using firebase::firestore::model::NoDocument;
using firebase::firestore::model::ObjectValue;
+using firebase::firestore::model::SnapshotVersion;
using firebase::firestore::remote::Serializer;
+using firebase::firestore::testutil::Key;
using firebase::firestore::util::Status;
+using firebase::firestore::util::StatusOr;
using google::protobuf::util::MessageDifferencer;
+#define ASSERT_OK(status) ASSERT_TRUE(StatusOk(status))
+#define ASSERT_NOT_OK(status) ASSERT_FALSE(StatusOk(status))
+#define EXPECT_OK(status) EXPECT_TRUE(StatusOk(status))
+#define EXPECT_NOT_OK(status) EXPECT_FALSE(StatusOk(status))
+
TEST(Serializer, CanLinkToNanopb) {
// This test doesn't actually do anything interesting as far as actually using
// nanopb is concerned but that it can run at all is proof that all the
@@ -56,8 +79,11 @@ TEST(Serializer, CanLinkToNanopb) {
// Fixture for running serializer tests.
class SerializerTest : public ::testing::Test {
public:
- SerializerTest() : serializer(/*DatabaseId("p", "d")*/) {
+ SerializerTest() : serializer(kDatabaseId) {
+ msg_diff.ReportDifferencesToString(&message_differences);
}
+
+ const DatabaseId kDatabaseId{"p", "d"};
Serializer serializer;
void ExpectRoundTrip(const FieldValue& model,
@@ -74,22 +100,88 @@ class SerializerTest : public ::testing::Test {
ExpectDeserializationRoundTrip(model, proto, type);
}
+ void ExpectRoundTrip(
+ const DocumentKey& key,
+ const FieldValue& value,
+ const SnapshotVersion& update_time,
+ const google::firestore::v1beta1::BatchGetDocumentsResponse& proto) {
+ ExpectSerializationRoundTrip(key, value, update_time, proto);
+ ExpectDeserializationRoundTrip(key, value, update_time, proto);
+ }
+
+ /**
+ * Checks the status. Don't use directly; use one of the relevant macros
+ * instead. eg:
+ *
+ * Status good_status = ...;
+ * ASSERT_OK(good_status);
+ *
+ * Status bad_status = ...;
+ * EXPECT_NOT_OK(bad_status);
+ */
+ testing::AssertionResult StatusOk(const Status& status) {
+ if (!status.ok()) {
+ return testing::AssertionFailure()
+ << "Status should have been ok, but instead contained "
+ << status.ToString();
+ }
+ return testing::AssertionSuccess();
+ }
+
+ template <typename T>
+ testing::AssertionResult StatusOk(const StatusOr<T>& status) {
+ return StatusOk(status.status());
+ }
+
+ /**
+ * Ensures that decoding fails with the given status.
+ *
+ * @param status the expected (failed) status. Only the code() is verified.
+ */
+ void ExpectFailedStatusDuringDecode(Status status,
+ const std::vector<uint8_t>& bytes) {
+ StatusOr<FieldValue> bad_status = serializer.DecodeFieldValue(bytes);
+ ASSERT_NOT_OK(bad_status);
+ EXPECT_EQ(status.code(), bad_status.status().code());
+ }
+
google::firestore::v1beta1::Value ValueProto(nullptr_t) {
- std::vector<uint8_t> bytes;
- Status status =
- serializer.EncodeFieldValue(FieldValue::NullValue(), &bytes);
- EXPECT_TRUE(status.ok());
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::NullValue());
google::firestore::v1beta1::Value proto;
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
return proto;
}
- google::firestore::v1beta1::Value ValueProto(bool b) {
+ std::vector<uint8_t> EncodeFieldValue(Serializer* serializer,
+ const FieldValue& fv) {
+ std::vector<uint8_t> bytes;
+ Status status = serializer->EncodeFieldValue(fv, &bytes);
+ EXPECT_OK(status);
+ return bytes;
+ }
+
+ std::vector<uint8_t> EncodeDocument(Serializer* serializer,
+ const DocumentKey& key,
+ const FieldValue& value) {
std::vector<uint8_t> bytes;
Status status =
- serializer.EncodeFieldValue(FieldValue::BooleanValue(b), &bytes);
- EXPECT_TRUE(status.ok());
+ serializer->EncodeDocument(key, value.object_value(), &bytes);
+ EXPECT_OK(status);
+ return bytes;
+ }
+
+ void Mutate(uint8_t* byte,
+ uint8_t expected_initial_value,
+ uint8_t new_value) {
+ ASSERT_EQ(*byte, expected_initial_value);
+ *byte = new_value;
+ }
+
+ google::firestore::v1beta1::Value ValueProto(bool b) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::BooleanValue(b));
google::firestore::v1beta1::Value proto;
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
@@ -97,10 +189,8 @@ class SerializerTest : public ::testing::Test {
}
google::firestore::v1beta1::Value ValueProto(int64_t i) {
- std::vector<uint8_t> bytes;
- Status status =
- serializer.EncodeFieldValue(FieldValue::IntegerValue(i), &bytes);
- EXPECT_TRUE(status.ok());
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::IntegerValue(i));
google::firestore::v1beta1::Value proto;
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
@@ -112,10 +202,17 @@ class SerializerTest : public ::testing::Test {
}
google::firestore::v1beta1::Value ValueProto(const std::string& s) {
- std::vector<uint8_t> bytes;
- Status status =
- serializer.EncodeFieldValue(FieldValue::StringValue(s), &bytes);
- EXPECT_TRUE(status.ok());
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::StringValue(s));
+ google::firestore::v1beta1::Value proto;
+ bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_TRUE(ok);
+ return proto;
+ }
+
+ google::firestore::v1beta1::Value ValueProto(const Timestamp& ts) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::TimestampValue(ts));
google::firestore::v1beta1::Value proto;
bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
@@ -128,13 +225,11 @@ class SerializerTest : public ::testing::Test {
const google::firestore::v1beta1::Value& proto,
FieldValue::Type type) {
EXPECT_EQ(type, model.type());
- std::vector<uint8_t> bytes;
- Status status = serializer.EncodeFieldValue(model, &bytes);
- EXPECT_TRUE(status.ok());
+ std::vector<uint8_t> bytes = EncodeFieldValue(&serializer, model);
google::firestore::v1beta1::Value actual_proto;
bool ok = actual_proto.ParseFromArray(bytes.data(), bytes.size());
EXPECT_TRUE(ok);
- EXPECT_TRUE(MessageDifferencer::Equals(proto, actual_proto));
+ EXPECT_TRUE(msg_diff.Compare(proto, actual_proto)) << message_differences;
}
void ExpectDeserializationRoundTrip(
@@ -145,28 +240,87 @@ class SerializerTest : public ::testing::Test {
std::vector<uint8_t> bytes(size);
bool status = proto.SerializeToArray(bytes.data(), size);
EXPECT_TRUE(status);
- FieldValue actual_model = serializer.DecodeFieldValue(bytes);
+ StatusOr<FieldValue> actual_model_status =
+ serializer.DecodeFieldValue(bytes);
+ EXPECT_OK(actual_model_status);
+ FieldValue actual_model = actual_model_status.ValueOrDie();
EXPECT_EQ(type, actual_model.type());
EXPECT_EQ(model, actual_model);
}
-};
-// TODO(rsgowman): whoops! A previous commit performed approx s/Encodes/Writes/,
-// but should not have done so here. Change it back in this file.
+ void ExpectSerializationRoundTrip(
+ const DocumentKey& key,
+ const FieldValue& value,
+ const SnapshotVersion& update_time,
+ const google::firestore::v1beta1::BatchGetDocumentsResponse& proto) {
+ std::vector<uint8_t> bytes = EncodeDocument(&serializer, key, value);
+ google::firestore::v1beta1::Document actual_proto;
+ bool ok = actual_proto.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_TRUE(ok);
+
+ // TODO(rsgowman): Right now, we only support Document (and don't support
+ // NoDocument). That should change in the next PR or so.
+ EXPECT_TRUE(proto.has_found());
+
+ // Slight weirdness: When we *encode* a document for sending it to the
+ // backend, we don't encode the update_time (or create_time). But when we
+ // *decode* a document, we *do* decode the update_time (though we still
+ // ignore the create_time). Therefore, we'll verify the update_time
+ // independently, and then strip it out before comparing the rest.
+ EXPECT_FALSE(actual_proto.has_create_time());
+ EXPECT_EQ(update_time.timestamp().seconds(),
+ proto.found().update_time().seconds());
+ EXPECT_EQ(update_time.timestamp().nanoseconds(),
+ proto.found().update_time().nanos());
+ google::firestore::v1beta1::BatchGetDocumentsResponse proto_copy{proto};
+ proto_copy.mutable_found()->clear_update_time();
+ proto_copy.mutable_found()->clear_create_time();
+
+ EXPECT_TRUE(msg_diff.Compare(proto_copy.found(), actual_proto))
+ << message_differences;
+ }
+
+ void ExpectDeserializationRoundTrip(
+ const DocumentKey& key,
+ const FieldValue& value,
+ const SnapshotVersion& update_time,
+ const google::firestore::v1beta1::BatchGetDocumentsResponse& proto) {
+ size_t size = proto.ByteSizeLong();
+ std::vector<uint8_t> bytes(size);
+ bool status = proto.SerializeToArray(bytes.data(), size);
+ EXPECT_TRUE(status);
+ StatusOr<std::unique_ptr<MaybeDocument>> actual_model_status =
+ serializer.DecodeMaybeDocument(bytes);
+ EXPECT_OK(actual_model_status);
+ std::unique_ptr<MaybeDocument> actual_model =
+ std::move(actual_model_status).ValueOrDie();
+
+ // TODO(rsgowman): Right now, we only support Document (and don't support
+ // NoDocument). That should change in the next PR or so.
+ EXPECT_EQ(MaybeDocument::Type::Document, actual_model->type());
+ Document* actual_doc_model = static_cast<Document*>(actual_model.get());
+ EXPECT_EQ(key, actual_model->key());
+ EXPECT_EQ(value, actual_doc_model->data());
+ EXPECT_EQ(update_time, actual_model->version());
+ }
+
+ std::string message_differences;
+ MessageDifferencer msg_diff;
+};
-TEST_F(SerializerTest, WritesNull) {
+TEST_F(SerializerTest, EncodesNull) {
FieldValue model = FieldValue::NullValue();
ExpectRoundTrip(model, ValueProto(nullptr), FieldValue::Type::Null);
}
-TEST_F(SerializerTest, WritesBool) {
+TEST_F(SerializerTest, EncodesBool) {
for (bool bool_value : {true, false}) {
FieldValue model = FieldValue::BooleanValue(bool_value);
ExpectRoundTrip(model, ValueProto(bool_value), FieldValue::Type::Boolean);
}
}
-TEST_F(SerializerTest, WritesIntegers) {
+TEST_F(SerializerTest, EncodesIntegers) {
std::vector<int64_t> cases{0,
1,
-1,
@@ -181,7 +335,7 @@ TEST_F(SerializerTest, WritesIntegers) {
}
}
-TEST_F(SerializerTest, WritesString) {
+TEST_F(SerializerTest, EncodesString) {
std::vector<std::string> cases{
"",
"a",
@@ -204,7 +358,24 @@ TEST_F(SerializerTest, WritesString) {
}
}
-TEST_F(SerializerTest, WritesEmptyMap) {
+TEST_F(SerializerTest, EncodesTimestamps) {
+ std::vector<Timestamp> cases{
+ {}, // epoch
+ {1234, 0},
+ {1234, 999999999},
+ {-1234, 0},
+ {-1234, 999999999},
+ TimestampInternal::Max(),
+ TimestampInternal::Min(),
+ };
+
+ for (const Timestamp& ts_value : cases) {
+ FieldValue model = FieldValue::TimestampValue(ts_value);
+ ExpectRoundTrip(model, ValueProto(ts_value), FieldValue::Type::Timestamp);
+ }
+}
+
+TEST_F(SerializerTest, EncodesEmptyMap) {
FieldValue model = FieldValue::ObjectValueFromMap({});
google::firestore::v1beta1::Value proto;
@@ -213,7 +384,7 @@ TEST_F(SerializerTest, WritesEmptyMap) {
ExpectRoundTrip(model, proto, FieldValue::Type::Object);
}
-TEST_F(SerializerTest, WritesNestedObjects) {
+TEST_F(SerializerTest, EncodesNestedObjects) {
FieldValue model = FieldValue::ObjectValueFromMap({
{"b", FieldValue::TrueValue()},
// TODO(rsgowman): add doubles (once they're supported)
@@ -258,7 +429,206 @@ TEST_F(SerializerTest, WritesNestedObjects) {
ExpectRoundTrip(model, proto, FieldValue::Type::Object);
}
+TEST_F(SerializerTest, BadNullValue) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::NullValue());
+
+ // Alter the null value from 0 to 1.
+ Mutate(&bytes[1], /*expected_initial_value=*/0, /*new_value=*/1);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadBoolValue) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::BooleanValue(true));
+
+ // Alter the bool value from 1 to 2. (Value values are 0,1)
+ Mutate(&bytes[1], /*expected_initial_value=*/1, /*new_value=*/2);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadIntegerValue) {
+ // Encode 'maxint'. This should result in 9 0xff bytes, followed by a 1.
+ std::vector<uint8_t> bytes = EncodeFieldValue(
+ &serializer,
+ FieldValue::IntegerValue(std::numeric_limits<uint64_t>::max()));
+ ASSERT_EQ(11u, bytes.size());
+ for (size_t i = 1; i < bytes.size() - 1; i++) {
+ ASSERT_EQ(0xff, bytes[i]);
+ }
+
+ // make the number a bit bigger
+ Mutate(&bytes[10], /*expected_initial_value=*/1, /*new_value=*/0xff);
+ bytes.resize(12);
+ bytes[11] = 0x7f;
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadStringValue) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::StringValue("a"));
+
+ // Claim that the string length is 5 instead of 1. (The first two bytes are
+ // used by the encoded tag.)
+ Mutate(&bytes[2], /*expected_initial_value=*/1, /*new_value=*/5);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadTimestampValue_TooLarge) {
+ std::vector<uint8_t> bytes = EncodeFieldValue(
+ &serializer, FieldValue::TimestampValue(TimestampInternal::Max()));
+
+ // Add some time, which should push us above the maximum allowed timestamp.
+ Mutate(&bytes[4], 0x82, 0x83);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadTimestampValue_TooSmall) {
+ std::vector<uint8_t> bytes = EncodeFieldValue(
+ &serializer, FieldValue::TimestampValue(TimestampInternal::Min()));
+
+ // Remove some time, which should push us below the minimum allowed timestamp.
+ Mutate(&bytes[4], 0x92, 0x91);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, BadTag) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::NullValue());
+
+ // The google::firestore::v1beta1::Value value_type oneof currently has tags
+ // up to 18. For this test, we'll pick a tag that's unlikely to be added in
+ // the near term but still fits within a uint8_t even when encoded.
+ // Specifically 31. 0xf8 represents field number 31 encoded as a varint.
+ Mutate(&bytes[0], /*expected_initial_value=*/0x58, /*new_value=*/0xf8);
+
+ // TODO(rsgowman): The behaviour is *temporarily* slightly different during
+ // development; this will cause a failed assertion rather than a failed
+ // status. Remove this EXPECT_ANY_THROW statement (and reenable the
+ // following commented out statement) once the corresponding assert has been
+ // removed from serializer.cc.
+ EXPECT_ANY_THROW(ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes));
+ // ExpectFailedStatusDuringDecode(
+ // Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, TagVarintWiretypeStringMismatch) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::BooleanValue(true));
+
+ // 0x0a represents a bool value encoded as a string. (We're using a
+ // boolean_value tag here, but any tag that would be represented by a varint
+ // would do.)
+ Mutate(&bytes[0], /*expected_initial_value=*/0x08, /*new_value=*/0x0a);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, TagStringWiretypeVarintMismatch) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::StringValue("foo"));
+
+ // 0x88 represents a string value encoded as a varint.
+ Mutate(&bytes[0], /*expected_initial_value=*/0x8a, /*new_value=*/0x88);
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, IncompleteFieldValue) {
+ std::vector<uint8_t> bytes =
+ EncodeFieldValue(&serializer, FieldValue::NullValue());
+ ASSERT_EQ(2u, bytes.size());
+
+ // Remove the (null) payload
+ ASSERT_EQ(0x00, bytes[1]);
+ bytes.pop_back();
+
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, IncompleteTag) {
+ std::vector<uint8_t> bytes;
+ ExpectFailedStatusDuringDecode(
+ Status(FirestoreErrorCode::DataLoss, "ignored"), bytes);
+}
+
+TEST_F(SerializerTest, EncodesKey) {
+ EXPECT_EQ("projects/p/databases/d/documents", serializer.EncodeKey(Key("")));
+ EXPECT_EQ("projects/p/databases/d/documents/one/two/three/four",
+ serializer.EncodeKey(Key("one/two/three/four")));
+}
+
+TEST_F(SerializerTest, DecodesKey) {
+ EXPECT_EQ(Key(""), serializer.DecodeKey("projects/p/databases/d/documents"));
+ EXPECT_EQ(Key("one/two/three/four"),
+ serializer.DecodeKey(
+ "projects/p/databases/d/documents/one/two/three/four"));
+ // Same, but with a leading slash
+ EXPECT_EQ(Key("one/two/three/four"),
+ serializer.DecodeKey(
+ "/projects/p/databases/d/documents/one/two/three/four"));
+}
+
+TEST_F(SerializerTest, BadKey) {
+ std::vector<std::string> bad_cases{
+ "", // empty (and too short)
+ "projects/p", // too short
+ "projects/p/databases/d", // too short
+ "projects/p/databases/d/documents/odd_number_of_local_elements",
+ "projects_spelled_wrong/p/databases/d/documents",
+ "projects/p/databases_spelled_wrong/d/documents",
+ "projects/not_project_p/databases/d/documents",
+ "projects/p/databases/not_database_d/documents",
+ "projects/p/databases/d/not_documents",
+ };
+
+ for (const std::string& bad_key : bad_cases) {
+ EXPECT_ANY_THROW(serializer.DecodeKey(bad_key));
+ }
+}
+
+TEST_F(SerializerTest, EncodesEmptyDocument) {
+ DocumentKey key = DocumentKey::FromPathString("path/to/the/doc");
+ FieldValue empty_value = FieldValue::ObjectValueFromMap({});
+ SnapshotVersion update_time = SnapshotVersion{{1234, 5678}};
+
+ google::firestore::v1beta1::BatchGetDocumentsResponse proto;
+ google::firestore::v1beta1::Document* doc_proto = proto.mutable_found();
+ doc_proto->set_name(serializer.EncodeKey(key));
+ doc_proto->mutable_fields();
+
+ google::protobuf::Timestamp* update_time_proto =
+ doc_proto->mutable_update_time();
+ update_time_proto->set_seconds(1234);
+ update_time_proto->set_nanos(5678);
+
+ // Note that we ignore create time in our serializer. We never set it, and
+ // never read it (other than to throw it away). But the server could (and
+ // probably does) set it, so we need to be able to discard it properly. The
+ // ExpectRoundTrip deals with this asymmetry.
+ google::protobuf::Timestamp* create_time_proto =
+ doc_proto->mutable_create_time();
+ create_time_proto->set_seconds(8765);
+ create_time_proto->set_nanos(4321);
+
+ ExpectRoundTrip(key, empty_value, update_time, proto);
+}
+
// TODO(rsgowman): Test [en|de]coding multiple protos into the same output
// vector.
-
-// TODO(rsgowman): Death test for decoding invalid bytes.