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.cc372
1 files changed, 177 insertions, 195 deletions
diff --git a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
index addc830..96125f7 100644
--- a/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
+++ b/Firestore/core/test/firebase/firestore/remote/serializer_test.cc
@@ -14,24 +14,15 @@
* limitations under the License.
*/
-/* NB: proto bytes were created via:
- echo 'TEXT_FORMAT_PROTO' \
- | ./build/external/protobuf/src/protobuf-build/src/protoc \
- -I./Firestore/Protos/protos \
- -I./build/external/protobuf/src/protobuf/src \
- --encode=google.firestore.v1beta1.Value \
- google/firestore/v1beta1/document.proto \
- | hexdump -C
- * where TEXT_FORMAT_PROTO is the text format of the protobuf. (go/textformat).
+/* Most tests use libprotobuf to create the bytes used for testing the
+ * serializer. (Previously, protoc was used, but that meant that the bytes were
+ * generated ahead of time and just copy+paste'd into the test suite, leading to
+ * a lot of magic.) Also note that bytes are no longer compared in any of the
+ * tests. Instead, we ensure that encoding with our serializer and decoding with
+ * libprotobuf (and vice versa) yield the same results.
*
- * Examples:
- * - For null, TEXT_FORMAT_PROTO would be 'null_value: NULL_VALUE' and would
- * yield the bytes: { 0x58, 0x00 }.
- * - For true, TEXT_FORMAT_PROTO would be 'boolean_value: true' and would yield
- * the bytes { 0x08, 0x01 }.
- *
- * All uses are documented below, so search for TEXT_FORMAT_PROTO to find more
- * examples.
+ * libprotobuf is only used in the test suite, and should never be present in
+ * the production code.
*/
#include "Firestore/core/src/firebase/firestore/remote/serializer.h"
@@ -41,13 +32,18 @@
#include <limits>
#include <vector>
+#include "Firestore/Protos/cpp/google/firestore/v1beta1/document.pb.h"
#include "Firestore/core/src/firebase/firestore/model/field_value.h"
#include "Firestore/core/src/firebase/firestore/util/status.h"
+#include "google/protobuf/stubs/common.h"
+#include "google/protobuf/util/message_differencer.h"
#include "gtest/gtest.h"
using firebase::firestore::model::FieldValue;
+using firebase::firestore::model::ObjectValue;
using firebase::firestore::remote::Serializer;
using firebase::firestore::util::Status;
+using google::protobuf::util::MessageDifferencer;
TEST(Serializer, CanLinkToNanopb) {
// This test doesn't actually do anything interesting as far as actually using
@@ -65,215 +61,201 @@ class SerializerTest : public ::testing::Test {
Serializer serializer;
void ExpectRoundTrip(const FieldValue& model,
- const std::vector<uint8_t>& bytes,
+ const google::firestore::v1beta1::Value& proto,
FieldValue::Type type) {
+ // First, serialize model with our (nanopb based) serializer, then
+ // deserialize the resulting bytes with libprotobuf and ensure the result is
+ // the same as the expected proto.
+ ExpectSerializationRoundTrip(model, proto, type);
+
+ // Next, serialize proto with libprotobuf, then deserialize the resulting
+ // bytes with our (nanopb based) deserializer and ensure the result is the
+ // same as the expected model.
+ ExpectDeserializationRoundTrip(model, proto, type);
+ }
+
+ google::firestore::v1beta1::Value ValueProto(nullptr_t) {
+ std::vector<uint8_t> bytes;
+ Status status =
+ serializer.EncodeFieldValue(FieldValue::NullValue(), &bytes);
+ EXPECT_TRUE(status.ok());
+ 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> bytes;
+ Status status =
+ serializer.EncodeFieldValue(FieldValue::BooleanValue(b), &bytes);
+ EXPECT_TRUE(status.ok());
+ google::firestore::v1beta1::Value proto;
+ bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_TRUE(ok);
+ return proto;
+ }
+
+ 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());
+ google::firestore::v1beta1::Value proto;
+ bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_TRUE(ok);
+ return proto;
+ }
+
+ google::firestore::v1beta1::Value ValueProto(const char* s) {
+ return ValueProto(std::string(s));
+ }
+
+ 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());
+ google::firestore::v1beta1::Value proto;
+ bool ok = proto.ParseFromArray(bytes.data(), bytes.size());
+ EXPECT_TRUE(ok);
+ return proto;
+ }
+
+ private:
+ void ExpectSerializationRoundTrip(
+ const FieldValue& model,
+ const google::firestore::v1beta1::Value& proto,
+ FieldValue::Type type) {
EXPECT_EQ(type, model.type());
- std::vector<uint8_t> actual_bytes;
- Status status = serializer.EncodeFieldValue(model, &actual_bytes);
+ std::vector<uint8_t> bytes;
+ Status status = serializer.EncodeFieldValue(model, &bytes);
EXPECT_TRUE(status.ok());
- EXPECT_EQ(bytes, actual_bytes);
+ 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));
+ }
+
+ void ExpectDeserializationRoundTrip(
+ const FieldValue& model,
+ const google::firestore::v1beta1::Value& proto,
+ FieldValue::Type type) {
+ size_t size = proto.ByteSizeLong();
+ std::vector<uint8_t> bytes(size);
+ bool status = proto.SerializeToArray(bytes.data(), size);
+ EXPECT_TRUE(status);
FieldValue actual_model = serializer.DecodeFieldValue(bytes);
EXPECT_EQ(type, actual_model.type());
EXPECT_EQ(model, actual_model);
}
};
-TEST_F(SerializerTest, WritesNullModelToBytes) {
- FieldValue model = FieldValue::NullValue();
+// TODO(rsgowman): whoops! A previous commit performed approx s/Encodes/Writes/,
+// but should not have done so here. Change it back in this file.
- // TEXT_FORMAT_PROTO: 'null_value: NULL_VALUE'
- std::vector<uint8_t> bytes{0x58, 0x00};
- ExpectRoundTrip(model, bytes, FieldValue::Type::Null);
+TEST_F(SerializerTest, WritesNull) {
+ FieldValue model = FieldValue::NullValue();
+ ExpectRoundTrip(model, ValueProto(nullptr), FieldValue::Type::Null);
}
-TEST_F(SerializerTest, WritesBoolModelToBytes) {
- struct TestCase {
- bool value;
- std::vector<uint8_t> bytes;
- };
-
- std::vector<TestCase> cases{// TEXT_FORMAT_PROTO: 'boolean_value: true'
- {true, {0x08, 0x01}},
- // TEXT_FORMAT_PROTO: 'boolean_value: false'
- {false, {0x08, 0x00}}};
-
- for (const TestCase& test : cases) {
- FieldValue model = FieldValue::BooleanValue(test.value);
- ExpectRoundTrip(model, test.bytes, FieldValue::Type::Boolean);
+TEST_F(SerializerTest, WritesBool) {
+ for (bool bool_value : {true, false}) {
+ FieldValue model = FieldValue::BooleanValue(bool_value);
+ ExpectRoundTrip(model, ValueProto(bool_value), FieldValue::Type::Boolean);
}
}
-TEST_F(SerializerTest, WritesIntegersModelToBytes) {
- // NB: because we're calculating the bytes via protoc (instead of
- // libprotobuf) we have to look at values from numeric_limits<T> ahead of
- // time. So these test cases make the following assumptions about
- // numeric_limits: (linking libprotobuf is starting to sound like a better
- // idea. :)
- // -9223372036854775808
- // == -8000000000000000
- // == numeric_limits<int64_t>::min()
- // 9223372036854775807
- // == 7FFFFFFFFFFFFFFF
- // == numeric_limits<int64_t>::max()
- //
- // For now, lets at least verify these values:
- EXPECT_EQ(-9223372036854775807 - 1, std::numeric_limits<int64_t>::min());
- EXPECT_EQ(9223372036854775807, std::numeric_limits<int64_t>::max());
- // TODO(rsgowman): link libprotobuf to the test suite and eliminate the
- // above.
-
- struct TestCase {
- int64_t value;
- std::vector<uint8_t> bytes;
- };
+TEST_F(SerializerTest, WritesIntegers) {
+ std::vector<int64_t> cases{0,
+ 1,
+ -1,
+ 100,
+ -100,
+ std::numeric_limits<int64_t>::min(),
+ std::numeric_limits<int64_t>::max()};
- std::vector<TestCase> cases{
- // TEXT_FORMAT_PROTO: 'integer_value: 0'
- TestCase{0, {0x10, 0x00}},
- // TEXT_FORMAT_PROTO: 'integer_value: 1'
- TestCase{1, {0x10, 0x01}},
- // TEXT_FORMAT_PROTO: 'integer_value: -1'
- TestCase{
- -1,
- {0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}},
- // TEXT_FORMAT_PROTO: 'integer_value: 100'
- TestCase{100, {0x10, 0x64}},
- // TEXT_FORMAT_PROTO: 'integer_value: -100'
- TestCase{
- -100,
- {0x10, 0x9c, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01}},
- // TEXT_FORMAT_PROTO: 'integer_value: -9223372036854775808'
- TestCase{
- -9223372036854775807 - 1,
- {0x10, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x01}},
- // TEXT_FORMAT_PROTO: 'integer_value: 9223372036854775807'
- TestCase{9223372036854775807,
- {0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f}}};
-
- for (const TestCase& test : cases) {
- FieldValue model = FieldValue::IntegerValue(test.value);
- ExpectRoundTrip(model, test.bytes, FieldValue::Type::Integer);
+ for (int64_t int_value : cases) {
+ FieldValue model = FieldValue::IntegerValue(int_value);
+ ExpectRoundTrip(model, ValueProto(int_value), FieldValue::Type::Integer);
}
}
-TEST_F(SerializerTest, WritesStringModelToBytes) {
- struct TestCase {
- std::string value;
- std::vector<uint8_t> bytes;
- };
-
- std::vector<TestCase> cases{
- // TEXT_FORMAT_PROTO: 'string_value: ""'
- {"", {0x8a, 0x01, 0x00}},
- // TEXT_FORMAT_PROTO: 'string_value: "a"'
- {"a", {0x8a, 0x01, 0x01, 0x61}},
- // TEXT_FORMAT_PROTO: 'string_value: "abc def"'
- {"abc def", {0x8a, 0x01, 0x07, 0x61, 0x62, 0x63, 0x20, 0x64, 0x65, 0x66}},
- // TEXT_FORMAT_PROTO: 'string_value: "æ"'
- {"æ", {0x8a, 0x01, 0x02, 0xc3, 0xa6}},
- // TEXT_FORMAT_PROTO: 'string_value: "\0\ud7ff\ue000\uffff"'
+TEST_F(SerializerTest, WritesString) {
+ std::vector<std::string> cases{
+ "",
+ "a",
+ "abc def",
+ "æ",
// Note: Each one of the three embedded universal character names
// (\u-escaped) maps to three chars, so the total length of the string
// literal is 10 (ignoring the terminating null), and the resulting string
// literal is the same as '\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf'". The
// size of 10 must be added, or else std::string will see the \0 at the
// start and assume that's the end of the string.
- {{"\0\ud7ff\ue000\uffff", 10},
- {0x8a, 0x01, 0x0a, 0x00, 0xed, 0x9f, 0xbf, 0xee, 0x80, 0x80, 0xef, 0xbf,
- 0xbf}},
- {{"\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf", 10},
- {0x8a, 0x01, 0x0a, 0x00, 0xed, 0x9f, 0xbf, 0xee, 0x80, 0x80, 0xef, 0xbf,
- 0xbf}},
- // TEXT_FORMAT_PROTO: 'string_value: "(╯°□°)╯︵ ┻━┻"'
- {"(╯°□°)╯︵ ┻━┻",
- {0x8a, 0x01, 0x1e, 0x28, 0xe2, 0x95, 0xaf, 0xc2, 0xb0, 0xe2, 0x96,
- 0xa1, 0xc2, 0xb0, 0xef, 0xbc, 0x89, 0xe2, 0x95, 0xaf, 0xef, 0xb8,
- 0xb5, 0x20, 0xe2, 0x94, 0xbb, 0xe2, 0x94, 0x81, 0xe2, 0x94, 0xbb}}};
-
- for (const TestCase& test : cases) {
- FieldValue model = FieldValue::StringValue(test.value);
- ExpectRoundTrip(model, test.bytes, FieldValue::Type::String);
+ {"\0\ud7ff\ue000\uffff", 10},
+ {"\0\xed\x9f\xbf\xee\x80\x80\xef\xbf\xbf", 10},
+ "(╯°□°)╯︵ ┻━┻",
+ };
+
+ for (const std::string& string_value : cases) {
+ FieldValue model = FieldValue::StringValue(string_value);
+ ExpectRoundTrip(model, ValueProto(string_value), FieldValue::Type::String);
}
}
-TEST_F(SerializerTest, WritesEmptyMapToBytes) {
+TEST_F(SerializerTest, WritesEmptyMap) {
FieldValue model = FieldValue::ObjectValueFromMap({});
- // TEXT_FORMAT_PROTO: 'map_value: {}'
- std::vector<uint8_t> bytes{0x32, 0x00};
- ExpectRoundTrip(model, bytes, FieldValue::Type::Object);
+
+ google::firestore::v1beta1::Value proto;
+ proto.mutable_map_value();
+
+ ExpectRoundTrip(model, proto, FieldValue::Type::Object);
}
-TEST_F(SerializerTest, WritesNestedObjectsToBytes) {
- // As above, verify max int64_t value.
- EXPECT_EQ(9223372036854775807, std::numeric_limits<int64_t>::max());
- // TODO(rsgowman): link libprotobuf to the test suite and eliminate the
- // above.
-
- FieldValue model = FieldValue::ObjectValueFromMap(
- {{"b", FieldValue::TrueValue()},
- // TODO(rsgowman): add doubles (once they're supported)
- // {"d", FieldValue::DoubleValue(std::numeric_limits<double>::max())},
- {"i", FieldValue::IntegerValue(1)},
- {"n", FieldValue::NullValue()},
- {"s", FieldValue::StringValue("foo")},
- // TODO(rsgowman): add arrays (once they're supported)
- // {"a", [2, "bar", {"b", false}]},
- {"o", FieldValue::ObjectValueFromMap(
- {{"d", FieldValue::IntegerValue(100)},
- {"nested",
- FieldValue::ObjectValueFromMap(
- {{"e", FieldValue::IntegerValue(
- std::numeric_limits<int64_t>::max())}})}})}});
-
- /* WARNING: "Wire format ordering and map iteration ordering of map values is
- * undefined, so you cannot rely on your map items being in a particular
- * order."
- * - https://developers.google.com/protocol-buffers/docs/proto#maps-features
- *
- * In reality, the map items are serialized by protoc in whatever order you
- * provide them in. Since FieldValue::ObjectValue is currently backed by a
- * std::map (and not an unordered_map) this implies ~alpha ordering. So we
- * need to provide the text format input in alpha ordering for things to match
- * up.
- *
- * This is... not ideal. Nothing stops libprotobuf from changing this
- * behaviour (since it's not guaranteed) nor does anything stop us from
- * switching map->unordered_map in FieldValue. (Arguably, that would be
- * better.) But the alternative is to not test the serializing to bytes, and
- * instead just assume we got that right. A *better* solution is to serialize
- * to bytes, and then deserialize with libprotobuf (rather than nanopb) and
- * then do a second test of serializing via libprotobuf and deserializing via
- * nanopb. In both cases, we would ignore the bytes themselves (since the
- * ordering is not defined) and instead compare the input objects with the
- * output objects.
- *
- * TODO(rsgowman): ^
- *
- * TEXT_FORMAT_PROTO (with multi-line formatting to preserve sanity):
- 'map_value: {
- fields: {key:"b", value:{boolean_value: true}}
- fields: {key:"i", value:{integer_value: 1}}
- fields: {key:"n", value:{null_value: NULL_VALUE}}
- fields: {key:"o", value:{map_value: {
- fields: {key:"d", value:{integer_value: 100}}
- fields: {key:"nested", value{map_value: {
- fields: {key:"e", value:{integer_value: 9223372036854775807}}
- }}}
- }}}
- fields: {key:"s", value:{string_value: "foo"}}
- }'
- */
- std::vector<uint8_t> bytes{
- 0x32, 0x59, 0x0a, 0x07, 0x0a, 0x01, 0x62, 0x12, 0x02, 0x08, 0x01, 0x0a,
- 0x07, 0x0a, 0x01, 0x69, 0x12, 0x02, 0x10, 0x01, 0x0a, 0x07, 0x0a, 0x01,
- 0x6e, 0x12, 0x02, 0x58, 0x00, 0x0a, 0x2f, 0x0a, 0x01, 0x6f, 0x12, 0x2a,
- 0x32, 0x28, 0x0a, 0x07, 0x0a, 0x01, 0x64, 0x12, 0x02, 0x10, 0x64, 0x0a,
- 0x1d, 0x0a, 0x06, 0x6e, 0x65, 0x73, 0x74, 0x65, 0x64, 0x12, 0x13, 0x32,
- 0x11, 0x0a, 0x0f, 0x0a, 0x01, 0x65, 0x12, 0x0a, 0x10, 0xff, 0xff, 0xff,
- 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f, 0x0a, 0x0b, 0x0a, 0x01, 0x73, 0x12,
- 0x06, 0x8a, 0x01, 0x03, 0x66, 0x6f, 0x6f};
-
- ExpectRoundTrip(model, bytes, FieldValue::Type::Object);
+TEST_F(SerializerTest, WritesNestedObjects) {
+ FieldValue model = FieldValue::ObjectValueFromMap({
+ {"b", FieldValue::TrueValue()},
+ // TODO(rsgowman): add doubles (once they're supported)
+ // {"d", FieldValue::DoubleValue(std::numeric_limits<double>::max())},
+ {"i", FieldValue::IntegerValue(1)},
+ {"n", FieldValue::NullValue()},
+ {"s", FieldValue::StringValue("foo")},
+ // TODO(rsgowman): add arrays (once they're supported)
+ // {"a", [2, "bar", {"b", false}]},
+ {"o", FieldValue::ObjectValueFromMap({
+ {"d", FieldValue::IntegerValue(100)},
+ {"nested", FieldValue::ObjectValueFromMap({
+ {
+ "e",
+ FieldValue::IntegerValue(
+ std::numeric_limits<int64_t>::max()),
+ },
+ })},
+ })},
+ });
+
+ google::firestore::v1beta1::Value inner_proto;
+ google::protobuf::Map<std::string, google::firestore::v1beta1::Value>*
+ inner_fields = inner_proto.mutable_map_value()->mutable_fields();
+ (*inner_fields)["e"] = ValueProto(std::numeric_limits<int64_t>::max());
+
+ google::firestore::v1beta1::Value middle_proto;
+ google::protobuf::Map<std::string, google::firestore::v1beta1::Value>*
+ middle_fields = middle_proto.mutable_map_value()->mutable_fields();
+ (*middle_fields)["d"] = ValueProto(int64_t{100});
+ (*middle_fields)["nested"] = inner_proto;
+
+ google::firestore::v1beta1::Value proto;
+ google::protobuf::Map<std::string, google::firestore::v1beta1::Value>*
+ fields = proto.mutable_map_value()->mutable_fields();
+ (*fields)["b"] = ValueProto(true);
+ (*fields)["i"] = ValueProto(int64_t{1});
+ (*fields)["n"] = ValueProto(nullptr);
+ (*fields)["s"] = ValueProto("foo");
+ (*fields)["o"] = middle_proto;
+
+ ExpectRoundTrip(model, proto, FieldValue::Type::Object);
}
// TODO(rsgowman): Test [en|de]coding multiple protos into the same output