/* * Copyright (c) Meta Platforms, Inc. and affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include using namespace apache::thrift; using namespace apache::thrift::test; using namespace ::testing; namespace { struct Handler : ServiceHandler { void sync_identity( EmptyWrapper& ret, std::unique_ptr empty) override { ret = EmptyWrapper(empty->deserialize()); } }; } // namespace TEST(CursorSerializerTest, RpcExample) { auto handler = std::make_shared(); auto client = makeTestClient( handler, nullptr /* injectFault */, nullptr /* streamInjectFault */, protocol::T_BINARY_PROTOCOL); EmptyWrapper empty(Empty{}); EmptyWrapper ret; client->sync_identity(ret, empty); std::ignore = ret.deserialize(); client = makeTestClient(handler, nullptr, nullptr, protocol::T_COMPACT_PROTOCOL); EXPECT_THAT( [&] { client->sync_identity(ret, empty); }, ThrowsMessage( "Single pass serialization only supports binary protocol.")); } TEST(CursorSerializer, QualifierRead) { Qualifiers obj; obj.opt() = 3; // Reading from a serialized default-constructed object sees the written // (default) values). CursorSerializationWrapper wrapper(obj); auto reader = wrapper.beginRead(); EXPECT_EQ(reader.read(), 3); EXPECT_EQ(reader.read(), 1); EXPECT_EQ(reader.read(), 2); wrapper.endRead(std::move(reader)); // Reading from a serialized empty object applies the appropriate default // based on the qualifier. folly::IOBufQueue q; BinarySerializer::serialize(Empty{}, &q); wrapper = CursorSerializationWrapper(q.move()); reader = wrapper.beginRead(); EXPECT_FALSE(reader.read()); EXPECT_EQ(reader.read(), 1); EXPECT_EQ(reader.read(), 0); wrapper.endRead(std::move(reader)); } TEST(CursorSerializer, ReadWithSkip) { CursorSerializationWrapper wrapper(Meal{}); auto reader = wrapper.beginRead(); EXPECT_EQ(reader.read(), 1); EXPECT_EQ(reader.read(), 2); EXPECT_EQ(reader.read(), 3); wrapper.endRead(std::move(reader)); } TEST(CursorSerializer, UnionRead) { Stringish string; CursorSerializationWrapper wrapper(string); auto reader = wrapper.beginRead(); EXPECT_EQ(reader.readType(), Stringish::Type::__EMPTY__); wrapper.endRead(std::move(reader)); string.string_field_ref() = "foo"; wrapper = CursorSerializationWrapper(string); reader = wrapper.beginRead(); EXPECT_EQ(reader.readType(), Stringish::Type::string_field); EXPECT_EQ(reader.read(), "foo"); EXPECT_FALSE(reader.read()); wrapper.endRead(std::move(reader)); string.binary_field_ref() = folly::IOBuf::wrapBufferAsValue( folly::ByteRange(std::string_view("bar"))); wrapper = CursorSerializationWrapper(string); reader = wrapper.beginRead(); if (auto str = reader.read()) { ADD_FAILURE(); } else if (auto buf = reader.read()) { EXPECT_EQ(buf->moveToFbString(), "bar"); } else { ADD_FAILURE(); } wrapper.endRead(std::move(reader)); } TEST(CursorSerializer, ManagedStringViewRead) { StructWithCppType obj; obj.someId() = 15u; obj.someName() = "baz"; CursorSerializationWrapper wrapper(obj); auto reader = wrapper.beginRead(); EXPECT_EQ(reader.read(), 15u); EXPECT_EQ(reader.read().str(), "baz"); wrapper.endRead(std::move(reader)); } TEST(CursorSerializer, StructWithOptionalRead) { { StructWithOptional obj; obj.optional_string() = "foo"; CursorSerializationWrapper wrapper(obj); auto reader = wrapper.beginRead(); std::string str; EXPECT_TRUE(reader.read(str)); EXPECT_EQ(str, "foo"); auto listReader = reader.beginRead(); EXPECT_FALSE(listReader.has_value()); auto map = reader.read(); EXPECT_FALSE(map.has_value()); auto innieReader = reader.beginRead(); EXPECT_FALSE(innieReader.has_value()); wrapper.endRead(std::move(reader)); } { StructWithOptional obj; obj.optional_string() = "foo"; obj.optional_list() = {1, 2, 3}; obj.optional_map() = {{1, 2}, {3, 4}}; Containers containers; containers.list_of_string() = {"foo", "bar"}; obj.optional_containers() = containers; CursorSerializationWrapper wrapper(obj); auto reader = wrapper.beginRead(); std::string str; EXPECT_TRUE(reader.read(str)); EXPECT_EQ(str, "foo"); auto listReader = reader.beginRead(); EXPECT_TRUE(listReader.has_value()); EXPECT_EQ(listReader->size(), 3); std::vector numbers(listReader->begin(), listReader->end()); EXPECT_THAT(numbers, ElementsAreArray({1, 2, 3})); reader.endRead(std::move(listReader.value())); auto map = reader.read(); EXPECT_TRUE(map.has_value()); EXPECT_EQ(map->size(), 2); EXPECT_EQ(map->at(1), 2); EXPECT_EQ(map->at(3), 4); auto innerReader = reader.beginRead(); EXPECT_TRUE(innerReader.has_value()); auto string_list = innerReader->read(); EXPECT_EQ(string_list.size(), 2); EXPECT_TRUE(string_list[0] == "foo" && string_list[1] == "bar"); reader.endRead(std::move(innerReader.value())); wrapper.endRead(std::move(reader)); } } TEST(CursorSerializer, NumericRead) { Numerics obj; obj.int16() = 1; obj.uint32() = 2; obj.enm() = E::A; obj.flt() = 3; CursorSerializationWrapper wrapper(obj); auto reader = wrapper.beginRead(); int16_t i; EXPECT_TRUE(reader.read(i)); EXPECT_EQ(i, *obj.int16()); uint32_t u; reader.read(u); EXPECT_EQ(u, *obj.uint32()); E e; reader.read(e); EXPECT_EQ(e, *obj.enm()); float f; reader.read(f); EXPECT_FLOAT_EQ(f, *obj.flt()); wrapper.endRead(std::move(reader)); } TEST(CursorSerializer, StringRead) { Struct obj; obj.string_field() = "foo"; StructCursor wrapper(obj); auto contiguousReader = wrapper.beginRead(); std::string_view str; EXPECT_TRUE(contiguousReader.read(str)); EXPECT_EQ("foo", str); wrapper.endRead(std::move(contiguousReader)); auto reader = wrapper.beginRead(); std::string str2; EXPECT_TRUE(reader.read(str2)); EXPECT_EQ("foo", str2); wrapper.endRead(std::move(reader)); reader = wrapper.beginRead(); folly::IOBuf str3; EXPECT_TRUE(reader.read(str3)); EXPECT_EQ("foo", str3.moveToFbString()); wrapper.endRead(std::move(reader)); } TEST(CursorSerializer, ContainerRead) { CursorSerializationWrapper wrapper(Cookie{}); auto reader = wrapper.beginRead(); auto listReader = reader.beginRead(); EXPECT_EQ(listReader.size(), 3); std::vector numbers(listReader.begin(), listReader.end()); EXPECT_THAT(numbers, ElementsAreArray({508, 493, 425})); // Can't use parent reader while child reader is active. EXPECT_THAT( [&] { reader.read(); }, ThrowsMessage("Child reader not passed to endRead")); reader.endRead(std::move(listReader)); wrapper.endRead(std::move(reader)); auto contiguousReader = wrapper.beginRead(); auto contiguousListReader = contiguousReader.beginRead(); for (auto i : contiguousListReader) { EXPECT_GT(i, 500); break; } // Ending read in the middle of iteration still allows reading next field. contiguousReader.endRead(std::move(contiguousListReader)); EXPECT_EQ(contiguousReader.read(), "Sugar"); static_assert( std::is_same_v< decltype(contiguousReader.read()), std::string_view>, ""); wrapper.endRead(std::move(contiguousReader)); // Reading from a finalized reader is not allowed (besides the obvious // use-after-move). EXPECT_THAT( // @lint-ignore CLANGTIDY bugprone-use-after-move [&] { listReader.begin(); }, ThrowsMessage("Reader already finalized")); // 0 and 1-element containers exercise separate edge cases Cookie c; c.lucky_numbers() = {}; wrapper = CursorSerializationWrapper(c); reader = wrapper.beginRead(); listReader = reader.beginRead(); EXPECT_EQ(listReader.size(), 0); for (auto i : listReader) { ADD_FAILURE() << i; } reader.endRead(std::move(listReader)); wrapper.endRead(std::move(reader)); c.lucky_numbers() = {1}; wrapper = CursorSerializationWrapper(c); reader = wrapper.beginRead(); listReader = reader.beginRead(); EXPECT_EQ(listReader.size(), 1); for (auto i : listReader) { EXPECT_EQ(i, 1); } reader.endRead(std::move(listReader)); wrapper.endRead(std::move(reader)); // Contiguous buffer means string turns into std::string_view Containers containers; containers.list_of_string() = {"foo", "bar"}; CursorSerializationWrapper containersWrapper(containers); auto containersReader = containersWrapper.beginRead(); auto listOfStringReader = containersReader.beginRead(); for (std::string_view& str : listOfStringReader) { EXPECT_TRUE(str == "foo" || str == "bar"); } containersReader.endRead(std::move(listOfStringReader)); containersWrapper.endRead(std::move(containersReader)); } TEST(CursorSerializer, NestedStructRead) { CursorSerializationWrapper wrapper(Meal{}); auto outerReader = wrapper.beginRead(); auto innerReader = outerReader.beginRead(); // Can't use parent reader while child reader is active. EXPECT_THAT( [&] { outerReader.read(); }, ThrowsMessage("Child reader not passed to endRead")); // endRead in the middle of reading the child skips to the end. EXPECT_EQ(innerReader.read(), 2); outerReader.endRead(std::move(innerReader)); EXPECT_EQ(outerReader.read(), 3); wrapper.endRead(std::move(outerReader)); } TEST(CursorSerializer, CursorReadInContainer) { Struct s; Stringish inner; inner.string_field_ref() = "foo"; s.set_nested_field() = std::vector{std::set{inner}}; inner.string_field_ref() = "bar"; s.set_nested_field()[0].insert(inner); StructCursor wrapper(s); auto reader = wrapper.beginRead(); auto listReader = reader.beginRead(); auto setReader = listReader.beginRead(); auto innerReader = setReader.beginRead(); EXPECT_EQ(innerReader.read(), "bar"); setReader.endRead(std::move(innerReader)); innerReader = setReader.beginRead(); EXPECT_EQ(innerReader.read(), "foo"); setReader.endRead(std::move(innerReader)); EXPECT_THROW(setReader.beginRead(), std::out_of_range); listReader.endRead(std::move(setReader)); reader.endRead(std::move(listReader)); wrapper.endRead(std::move(reader)); wrapper = StructCursor(Struct()); reader = wrapper.beginRead(); listReader = reader.beginRead(); EXPECT_THROW(listReader.beginRead(), std::out_of_range); reader.endRead(std::move(listReader)); wrapper.endRead(std::move(reader)); } TEST(CursorSerializer, TypesRead) { test::Types obj; obj.iobuf() = folly::IOBuf::wrapBufferAsValue("foo", 3); obj.iobufptr() = folly::IOBuf::wrapBuffer("bar", 3); obj.ms() = std::chrono::milliseconds(123456789); CursorSerializationWrapper wrapper(obj); auto reader = wrapper.beginRead(); EXPECT_EQ(reader.read().toString(), "foo"); EXPECT_EQ(reader.read()->toString(), "bar"); EXPECT_EQ(reader.read().count(), 123456789); wrapper.endRead(std::move(reader)); } TEST(CursorSerializer, QualifierWrite) { CursorSerializationWrapper wrapper; auto writer = wrapper.beginWrite(); writer.write(3); writer.write(1); writer.write(2); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_EQ(*obj.opt(), 3); EXPECT_EQ(*obj.unq(), 1); EXPECT_EQ(*obj.terse(), 2); auto serializedLen = wrapper.serializedData().computeChainDataLength(); // Skipping an optional field decreases serialized size. writer = wrapper.beginWrite(); writer.write(1); writer.write(2); wrapper.endWrite(std::move(writer)); obj = wrapper.deserialize(); EXPECT_FALSE(obj.opt()); EXPECT_EQ(*obj.unq(), 1); EXPECT_EQ(*obj.terse(), 2); EXPECT_LT(wrapper.serializedData().computeChainDataLength(), serializedLen); // Setting a terse field to its intrinsic default decreases serialized size. writer = wrapper.beginWrite(); writer.write(3); writer.write(1); writer.write(0); wrapper.endWrite(std::move(writer)); obj = wrapper.deserialize(); EXPECT_EQ(*obj.opt(), 3); EXPECT_EQ(*obj.unq(), 1); EXPECT_EQ(*obj.terse(), 0); EXPECT_LT(wrapper.serializedData().computeChainDataLength(), serializedLen); } TEST(CursorSerializer, WriteWithSkip) { StructCursor wrapper; auto writer = wrapper.beginWrite(); writer.write(42); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_FALSE(obj.string_field()); EXPECT_EQ(*obj.i32_field(), 42); EXPECT_TRUE(empty(*obj.union_field())); EXPECT_TRUE(obj.list_field()->empty()); CursorSerializationWrapper wrapperWithDefaults; auto writerWithDefaults = wrapperWithDefaults.beginWrite(); writerWithDefaults.write(1); wrapperWithDefaults.endWrite(std::move(writerWithDefaults)); auto meal = wrapperWithDefaults.deserialize(); EXPECT_EQ(*meal.appetizer(), 1); EXPECT_EQ(*meal.main(), 1); EXPECT_EQ(*meal.dessert(), 3); CursorSerializationWrapper oooWrapper; auto oooWriter = oooWrapper.beginWrite(); oooWriter.write(2); oooWrapper.endWrite(std::move(oooWriter)); // Ensure we've actually written the skipped fields. BinaryProtocolReader reader; reader.setInput(&oooWrapper.serializedData()); std::string name; reader.readStructBegin(name); auto checkField = [&](int16_t expectedId, int16_t expectedVal) { int16_t id; int16_t val; apache::thrift::protocol::TType type; reader.readFieldBegin(name, type, id); reader.readI16(val); reader.readFieldEnd(); EXPECT_EQ(id, expectedId); EXPECT_EQ(val, expectedVal); }; checkField(1, 0); checkField(4, 2); // field5 checkField(5, 0); checkField(7, 0); checkField(12, 1); } TEST(CursorSerializer, UnionWrite) { CursorSerializationWrapper wrapper; auto writer = wrapper.beginWrite(); writer.write("foo"); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_EQ(*obj.string_field_ref(), "foo"); } TEST(CursorSerializer, ManagedStringViewWrite) { CursorSerializationWrapper wrapper; auto writer = wrapper.beginWrite(); writer.write(14); writer.write("foobar"); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_EQ(obj.someId(), 14u); EXPECT_EQ(obj.someName()->str(), "foobar"); } TEST(CursorSerializer, StructWithOptionalWrite) { { CursorSerializationWrapper wrapper; auto writer = wrapper.beginWrite(); writer.write("baz"); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_EQ(obj.optional_string(), "baz"); EXPECT_FALSE(obj.optional_list().has_value()); EXPECT_FALSE(obj.optional_map().has_value()); EXPECT_FALSE(obj.optional_containers().has_value()); } { CursorSerializationWrapper wrapper; auto writer = wrapper.beginWrite(); writer.write("baz"); auto list_writer = writer.beginWrite(); list_writer.write(1); list_writer.write(2); list_writer.write(3); writer.endWrite(std::move(list_writer)); auto map = std::unordered_map{{1, 2}, {3, 4}}; writer.write(map); auto container_writer = writer.beginWrite(); auto string_list = {"foo", "bar"}; container_writer.write(string_list); writer.endWrite(std::move(container_writer)); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_EQ(obj.optional_string(), "baz"); EXPECT_TRUE(obj.optional_list().has_value()); EXPECT_TRUE(obj.optional_list()->size() == 3); EXPECT_TRUE(obj.optional_map().has_value()); EXPECT_TRUE(obj.optional_map()->size() == 2); EXPECT_TRUE(obj.optional_containers().has_value()); EXPECT_EQ(obj.optional_containers()->list_of_string()->size(), 2); } } TEST(CursorSerializer, NumericWrite) { CursorSerializationWrapper wrapper; auto writer = wrapper.beginWrite(); writer.write(1); writer.write(2); writer.write(E::B); writer.write(3.0); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_EQ(*obj.int16(), 1); EXPECT_EQ(*obj.uint32(), 2); EXPECT_EQ(*obj.enm(), E::B); EXPECT_FLOAT_EQ(*obj.flt(), 3.0); } TEST(CursorSerializer, StringWrite) { StructCursor wrapper; auto writer = wrapper.beginWrite(); writer.write(std::string_view("foo")); wrapper.endWrite(std::move(writer)); EXPECT_EQ(*wrapper.deserialize().string_field(), "foo"); writer = wrapper.beginWrite(); writer.write(std::string("foo")); wrapper.endWrite(std::move(writer)); EXPECT_EQ(*wrapper.deserialize().string_field(), "foo"); writer = wrapper.beginWrite(); writer.write(*folly::IOBuf::copyBuffer("foo")); wrapper.endWrite(std::move(writer)); EXPECT_EQ(*wrapper.deserialize().string_field(), "foo"); writer = wrapper.beginWrite(); auto str = writer.beginWrite(12); memcpy(str.writeableData(), "foo", 3); writer.endWrite(std::move(str), 3); writer.write(42); wrapper.endWrite(std::move(writer)); EXPECT_EQ(*wrapper.deserialize().string_field(), "foo"); EXPECT_EQ(*wrapper.deserialize().i32_field(), 42); } TEST(CursorSerializer, ContainerWrite) { StructCursor wrapper; auto writer = wrapper.beginWrite(); auto list = writer.beginWrite(); list.write('a'); list.write('b'); list.write('c'); writer.endWrite(std::move(list)); auto map = std::unordered_map{{'a', 'b'}, {'c', 'd'}}; writer.write(map); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_THAT(*obj.list_field(), UnorderedElementsAreArray({'a', 'b', 'c'})); EXPECT_EQ(*obj.map_field(), map); } TEST(CursorSerializer, NestedStructWrite) { StructCursor wrapper; auto writer = wrapper.beginWrite(); auto innerWriter = writer.beginWrite(); // Can't use parent writer while child writer is active. EXPECT_THAT( [&] { writer.write(std::array{42}); }, ThrowsMessage("Child writer not passed to endWrite")); // endWrite in the middle of writing the child skips to the end. writer.endWrite(std::move(innerWriter)); writer.write(std::array{42}); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_EQ(obj.union_field()->getType(), Inner::Type::__EMPTY__); EXPECT_THAT(*obj.list_field(), ElementsAre(42)); } TEST(CursorSerializer, CursorWriteInContainer) { StructCursor wrapper; auto writer = wrapper.beginWrite(); auto listWriter = writer.beginWrite(); auto setWriter = listWriter.beginWrite(); auto innerWriter = setWriter.beginWrite(); innerWriter.write("foo"); setWriter.endWrite(std::move(innerWriter)); listWriter.endWrite(std::move(setWriter)); writer.endWrite(std::move(listWriter)); wrapper.endWrite(std::move(writer)); auto obj = wrapper.deserialize(); EXPECT_THAT( *obj.set_nested_field(), Contains(Contains(IsThriftUnionWith(Eq("foo"))))); }