/* * 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 #include #include #include #include using namespace ::apache::thrift::conformance; using detail::protocol_reader_t; using detail::protocol_writer_t; namespace apache::thrift::op::detail { namespace { using apache::thrift::protocol::asValueStruct; using apache::thrift::protocol::TType; using detail::typeTagToTType; TEST(EncodeTest, TypeTagToTType) { EXPECT_EQ(typeTagToTType, TType::T_BOOL); EXPECT_EQ(typeTagToTType, TType::T_BYTE); EXPECT_EQ(typeTagToTType, TType::T_I16); EXPECT_EQ(typeTagToTType, TType::T_I32); EXPECT_EQ(typeTagToTType, TType::T_I64); EXPECT_EQ(typeTagToTType, TType::T_FLOAT); EXPECT_EQ(typeTagToTType, TType::T_DOUBLE); EXPECT_EQ(typeTagToTType, TType::T_UTF7); EXPECT_EQ(typeTagToTType, TType::T_STRING); EXPECT_EQ(typeTagToTType>, TType::T_I32); EXPECT_EQ(typeTagToTType>, TType::T_STRUCT); EXPECT_EQ(typeTagToTType>, TType::T_STRUCT); EXPECT_EQ(typeTagToTType>, TType::T_STRUCT); EXPECT_EQ(typeTagToTType>, TType::T_LIST); EXPECT_EQ( (typeTagToTType>>), TType::T_SET); EXPECT_EQ( (typeTagToTType>>), TType::T_MAP); // test adapted EXPECT_EQ( (typeTagToTType>), TType::T_FLOAT); EXPECT_EQ( (typeTagToTType>>), TType::T_LIST); EXPECT_EQ( (typeTagToTType>>), TType::T_STRUCT); } template < conformance::StandardProtocol Protocol, bool ZeroCopy, typename Tag, typename TypeClass, bool IsAdapted = false, typename T> void testSerializedSize(T value) { SCOPED_TRACE(folly::pretty_name()); protocol_writer_t writer; uint32_t size; if constexpr (IsAdapted) { using AdaptedTag = type::adapted; size = op::serialized_size( writer, test::TemplatedTestAdapter::fromThrift(value)); } else { size = op::serialized_size(writer, value); } uint32_t expected = apache::thrift::detail::pm::protocol_methods:: template serializedSize(writer, value); EXPECT_EQ(size, expected); } template < conformance::StandardProtocol Protocol, typename Tag, typename TypeClass, bool IsAdapted = false, typename T> void testSerializedSize(T value) { testSerializedSize(value); testSerializedSize(value); } template void testSerializedSizeBasicTypes() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testSerializedSize(true); testSerializedSize((int8_t)1); testSerializedSize((int16_t)1); testSerializedSize((int32_t)1); testSerializedSize((int64_t)1); testSerializedSize(1.5f); testSerializedSize(1.5); testSerializedSize( std::string("foo")); testSerializedSize( folly::StringPiece("foo")); testSerializedSize("foo"); testSerializedSize( std::string("foo")); testSerializedSize( folly::StringPiece("foo")); testSerializedSize("foo"); enum class MyEnum { value = 1 }; testSerializedSize, type_class::enumeration>( MyEnum::value); } template void testSerializedSizeContainers() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testSerializedSize< Protocol, type::list, type_class::list>( std::vector{true, false, true}); testSerializedSize< Protocol, type::set, type_class::set>(std::set{true, false}); testSerializedSize< Protocol, type::map, type_class::map>( std::map{ {std::string("foo"), 1}, {std::string("foo"), 2}}); } enum class EmptyEnum : std::int16_t {}; template void testSerializedSizeCppType() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); // test cpp_type with primitive testSerializedSize< Protocol, type::cpp_type, type_class::integral>((int16_t)1); // test strongly typed integer testSerializedSize< Protocol, type::cpp_type, type_class::integral>(static_cast(1)); { // test cpp_type with list using T = std::deque; auto value = T{1, 2, 3}; using Tag = type::list; testSerializedSize< Protocol, type::cpp_type, type_class::list>(value); } { // test cpp_type with set using T = std::unordered_set; auto value = T{"foo", "bar"}; using Tag = type::set; testSerializedSize< Protocol, type::cpp_type, type_class::set>(value); } { // test cpp_type with map using T = std::unordered_map; auto value = T{{"foo", 1}, {"bar", 2}}; using Tag = type::map; testSerializedSize< Protocol, type::cpp_type, type_class::map>(value); } } template < conformance::StandardProtocol Protocol, typename Struct, typename Tag, typename TypeClass, bool IsAdapted = false, typename T> void testSerializedSizeObject(T value) { Struct s; s.field_1_ref() = value; testSerializedSize(s); } template void testSerializedSizeStruct() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); using Struct = test::testset::struct_with>; std::map mapValues = {{"one", 1}, {"four", 4}, {"two", 2}}; testSerializedSizeObject< Protocol, Struct, type::struct_t, type_class::structure>(mapValues); using Union = test::testset::union_with>; std::set setValues = {"foo", "bar", "baz"}; testSerializedSizeObject< Protocol, Union, type::union_t, type_class::structure>(setValues); using Exception = test::testset::exception_with; testSerializedSizeObject< Protocol, Exception, type::exception_t, type_class::structure>(1); } template void testSerializedSizeAdapted() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testSerializedSize("foo"); testSerializedSize< Protocol, type::set, type_class::set, true>(std::set{1, 2, 3}); using Struct = test::testset::struct_with; testSerializedSizeObject< Protocol, Struct, type::struct_t, type_class::structure, true>(1); using Union = test::testset::union_with; testSerializedSizeObject< Protocol, Union, type::union_t, type_class::structure, true>(1); } TEST(SerializedSizeTest, SerializedSizeBasicTypes) { testSerializedSizeBasicTypes(); testSerializedSizeBasicTypes(); } TEST(SerializedSizeTest, SerializedSizeContainers) { testSerializedSizeContainers(); testSerializedSizeContainers(); } TEST(SerializedSizeTest, SerializedSizeCppType) { testSerializedSizeCppType(); testSerializedSizeCppType(); } TEST(SerializedSizeTest, SerializedSizeStruct) { testSerializedSizeStruct(); testSerializedSizeStruct(); } TEST(SerializedSizeTest, SerializedSizeAdapted) { testSerializedSizeAdapted(); testSerializedSizeAdapted(); } template protocol::Value encodeAndParseValue( type::native_type value, TType ttype, bool string_to_binary = true) { protocol_writer_t writer; folly::IOBufQueue queue; writer.setOutput(&queue); encode(writer, value); protocol_reader_t reader; auto serialized = queue.move(); reader.setInput(serialized.get()); return protocol::detail::parseValue(reader, ttype, string_to_binary); } template void testEncode(type::native_type value, bool string_to_binary = true) { SCOPED_TRACE(folly::pretty_name()); auto result = encodeAndParseValue( value, typeTagToTType, string_to_binary); EXPECT_EQ(result, asValueStruct(value)); } template void testEncodeBasicTypes() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testEncode(true); testEncode(1); testEncode(1); testEncode(1); testEncode(1); testEncode(1.5); testEncode(1.5); testEncode(std::string("foo"), false); testEncode>( folly::StringPiece("foo"), false); testEncode("foo", false); testEncode("foo"); testEncode>(1); } template void testEncodeUnsignedTypes() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testEncode>(128U); testEncode>(32768U); testEncode>(2147483648UL); testEncode>( 9223372036854775808ULL); } template void testEncodeContainers() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testEncode>>( std::vector{1, 2, 3}); testEncode>(std::set{true, false}); testEncode>( std::map{{"foo", 1}, {"bar", 2}}, false); } template void testEncodeCppType() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); { // test strongly typed integer auto result = encodeAndParseValue>( static_cast(1), TType::T_I16); EXPECT_EQ(result, asValueStruct(1)); } { // test cpp_type with list using T = std::list; auto value = T{1, 2, 3}; using Tag = type::list; auto result = encodeAndParseValue>( value, TType::T_LIST); EXPECT_EQ(result, asValueStruct(value)); } { // test cpp_type with set using T = std::unordered_set; auto value = T{"foo", "bar"}; using Tag = type::set; auto result = encodeAndParseValue>( value, TType::T_SET, false); EXPECT_EQ(result, asValueStruct(value)); } { // test cpp_type with map using T = std::unordered_map; auto value = T{{"foo", 1}, {"bar", 2}}; using Tag = type::map; auto result = encodeAndParseValue>( value, TType::T_MAP, false); EXPECT_EQ(result, asValueStruct(value)); } } // If IsAdapted is true, test op::encode with the given object with // type::adapted tag and fromThrift. template < conformance::StandardProtocol Protocol, typename Struct, typename Tag, bool IsAdapted = false, typename T> void testEncodeObject(T value) { SCOPED_TRACE(folly::pretty_name()); Struct foo; foo.field_1_ref() = value; protocol_writer_t w1, w2; folly::IOBufQueue o1, o2; w1.setOutput(&o1); w2.setOutput(&o2); if constexpr (IsAdapted) { using AdaptedTag = type::adapted; encode(w1, test::TemplatedTestAdapter::fromThrift(foo)); } else { encode(w1, foo); } foo.write(&w2); EXPECT_TRUE(folly::IOBufEqualTo{}(*o1.move(), *o2.move())); } template void testEncodeStruct() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); using Struct = test::testset::struct_with>; std::map mapValues = {{"one", 1}, {"four", 4}, {"two", 2}}; testEncodeObject>(mapValues); using Union = test::testset::union_with>; std::set setValues = {"foo", "bar", "baz"}; testEncodeObject>(setValues); using Exception = test::testset::exception_with; testEncodeObject>(1); } template void testEncodeAdapted() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); { // test op::encode with adapted struct using Struct = test::testset::struct_with; testEncodeObject, true>(1); } { // test op::encode with adapted primitive type using AdaptedTag = type::adapted; auto result = encodeAndParseValue( test::TemplatedTestAdapter::fromThrift(static_cast(1)), TType::T_I16); EXPECT_EQ(result, asValueStruct(1)); } { // test op::encode with adapted container auto value = std::map{{"foo", 1}, {"bar", 2}}; using Tag = type::map; using AdaptedTag = type::adapted; auto result = encodeAndParseValue( test::TemplatedTestAdapter::fromThrift(value), TType::T_MAP, false); EXPECT_EQ(result, asValueStruct(value)); } { // test op::encode with Adapter::encode optimization using AdaptedTag = type::adapted; test::Num value{1}; auto result = encodeAndParseValue(value, TType::T_I64); EXPECT_EQ(result, asValueStruct(1)); } } TEST(EncodeTest, EncodeBasicTypes) { testEncodeBasicTypes(); testEncodeBasicTypes(); } TEST(EncodeTest, EncodeUnsignedTypes) { testEncodeUnsignedTypes(); testEncodeUnsignedTypes(); } TEST(EncodeTest, EncodeContainers) { testEncodeContainers(); testEncodeContainers(); } TEST(EncodeTest, EncodeStruct) { testEncodeStruct(); testEncodeStruct(); } TEST(EncodeTest, EncodeCppType) { testEncodeCppType(); testEncodeCppType(); } TEST(EncodeTest, EncodeAdapted) { testEncodeAdapted(); testEncodeAdapted(); } // Encodes the given value with EncodeTag, and decodes the result with DecodeTag // to DecodeT type using the Protocol. template < conformance::StandardProtocol Protocol, typename EncodeTag, typename DecodeTag, typename DecodeT, typename EncodeT> DecodeT encodeAndDecode(EncodeT value) { protocol_writer_t writer; folly::IOBufQueue queue; writer.setOutput(&queue); encode(writer, value); protocol_reader_t reader; auto serialized = queue.move(); reader.setInput(serialized.get()); DecodeT result; decode(reader, result); return result; } template void testDecode(type::native_type value) { SCOPED_TRACE(folly::pretty_name()); EXPECT_EQ( (encodeAndDecode(value)), value); } template void testDecodeBasicTypes() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testDecode(true); testDecode((int8_t)1); testDecode((int16_t)11); testDecode((int32_t)11); testDecode((int64_t)11); testDecode(1.5f); testDecode(1.5); testDecode(std::string("foo")); testDecode(std::string("foo")); enum class MyEnum { value = 1 }; testDecode>(MyEnum::value); } template void testDecodeUnsignedTypes() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testDecode>(128U); testDecode>(32768U); testDecode>(2147483648UL); testDecode>( 9223372036854775808ULL); } template void testDecodeContainers() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); testDecode>>( std::vector{1, 2, 3}); testDecode>( std::vector{true, false, true}); testDecode>(std::set{true, false}); testDecode< Protocol, type::cpp_type, type::set>>( std::unordered_set{true, false}); testDecode>( std::map{ {std::string("foo"), 1}, {std::string("foo"), 2}}); testDecode< Protocol, type::cpp_type< std::unordered_map, type::map>>( std::unordered_map{ {std::string("foo"), 1}, {std::string("foo"), 2}}); testDecode< Protocol, type:: cpp_type, type::set>>( folly::sorted_vector_set{3, 1, 2}); testDecode< Protocol, type::cpp_type< folly::sorted_vector_map, type::map>>( folly::sorted_vector_map{ {std::string("foo"), 1}, {std::string("foo"), 2}}); // Test if it skips when value type doesn't match. { auto result = encodeAndDecode< Protocol, type::list, type::list, std::vector>(std::vector{1, 2, 3}); EXPECT_TRUE(result.empty()); } { auto result = encodeAndDecode< Protocol, type::set, type::set, std::set>(std::set{1, 2, 3}); EXPECT_TRUE(result.empty()); } { auto result = encodeAndDecode< Protocol, type::map, type::map, std::map>( std::map{{1, true}, {2, false}}); EXPECT_TRUE(result.empty()); } { auto result = encodeAndDecode< Protocol, type::map, type::map, std::map>( std::map{{"1", true}, {"2", false}}); EXPECT_TRUE(result.empty()); } } template < conformance::StandardProtocol Protocol, typename Struct, typename Tag, bool IsAdapted = false, typename T> void testDecodeObject(T value) { Struct s; s.field_1_ref() = value; if constexpr (IsAdapted) { using AdaptedTag = type::adapted; testDecode(test::TemplatedTestAdapter::fromThrift(s)); } else { testDecode(s); } } template void testDecodeStruct() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); using Struct = test::testset::struct_with>; std::map mapValues = {{"one", 1}, {"four", 4}, {"two", 2}}; testDecodeObject>(mapValues); using Union = test::testset::union_with>; std::set setValues = {"foo", "bar", "baz"}; testDecodeObject>(setValues); using Exception = test::testset::exception_with; testDecodeObject>(1); } template void testDecodeCppType() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); { // test strongly typed integer testDecode>( static_cast(1)); } { // test cpp_type with list using T = std::deque; auto value = T{1, 2, 3}; using Tag = type::list; testDecode>(value); } { // test cpp_type with set using T = std::unordered_set; auto value = T{"foo", "bar"}; using Tag = type::set; testDecode>(value); } { // test cpp_type with map using T = std::unordered_map; auto value = T{{"foo", 1}, {"bar", 2}}; using Tag = type::map; testDecode>(value); } } template < conformance::StandardProtocol Protocol, typename Struct, typename Tag, typename T> void testEncodeAndDecodeFieldAdapted(T value) { SCOPED_TRACE(folly::pretty_name()); // s is used as a placeholder. Struct s; const test::AdaptedWithContext adapted{value}; using AdaptedTag = type::adapted; using FieldTag = type::field>; protocol_writer_t writer; folly::IOBufQueue queue; writer.setOutput(&queue); encode(writer, adapted); protocol_reader_t reader; auto serialized = queue.move(); reader.setInput(serialized.get()); test::AdaptedWithContext result; decode(reader, result, s); EXPECT_EQ(adapted, result); using AdaptedTag2 = type::adapted; using FieldTag2 = type::field>; encode(writer, adapted); auto serialized2 = queue.move(); reader.setInput(serialized2.get()); decode(reader, result, s); EXPECT_EQ(adapted, result); } template void testDecodeAdapted() { SCOPED_TRACE(apache::thrift::util::enumNameSafe(Protocol)); using AdaptedTag = type::adapted; testDecode( test::TemplatedTestAdapter::fromThrift(std::string())); using Struct = test::testset::struct_with; testDecodeObject, true>(1); using Union = test::testset::union_with; testDecodeObject, true>(1); testDecode>( test::Num{1}); using Struct64 = test::testset::struct_with; testEncodeAndDecodeFieldAdapted( static_cast(42)); } TEST(DecodeTest, DecodeBasicTypes) { testDecodeBasicTypes(); testDecodeBasicTypes(); } TEST(DecodeTest, DecodeUnsignedTypes) { testDecodeBasicTypes(); testDecodeBasicTypes(); } TEST(DecodeTest, DecodeContainers) { testDecodeContainers(); testDecodeContainers(); } TEST(DecodeTest, DecodeStruct) { testDecodeStruct(); testDecodeStruct(); } TEST(DecodeTest, DecodeCppType) { testDecodeCppType(); testDecodeCppType(); } TEST(DecodeTest, DecodeAdapted) { testDecodeAdapted(); testDecodeAdapted(); } } // namespace enum { UseWrite, UseStructEncode }; struct MockStruct { static const bool __fbthrift_cpp2_gen_json = false; template uint32_t write(T&&) const { return UseWrite; } }; struct JSONMockStruct { static const bool __fbthrift_cpp2_gen_json = true; template uint32_t write(T&&) const { return UseWrite; } }; template <> struct StructEncode { template uint32_t operator()(T&&, const U&) const { return UseStructEncode; } }; template <> struct StructEncode { template uint32_t operator()(T&&, const U&) const { return UseStructEncode; } }; TEST(EncodeTest, EncodeMethod) { CompactProtocolWriter compact; BinaryProtocolWriter binary; SimpleJSONProtocolWriter simpleJson; JSONProtocolWriter json; DebugProtocolWriter debug; MockStruct mock; JSONMockStruct jsonMock; auto encodeMockStruct = op::encode>; auto encodeJSONMockStruct = op::encode>; EXPECT_EQ(encodeMockStruct(compact, mock), UseWrite); EXPECT_EQ(encodeMockStruct(binary, mock), UseWrite); EXPECT_EQ(encodeMockStruct(simpleJson, mock), UseStructEncode); EXPECT_EQ(encodeMockStruct(json, mock), UseStructEncode); EXPECT_EQ(encodeMockStruct(debug, mock), UseStructEncode); EXPECT_EQ(encodeJSONMockStruct(compact, jsonMock), UseWrite); EXPECT_EQ(encodeJSONMockStruct(binary, jsonMock), UseWrite); EXPECT_EQ(encodeJSONMockStruct(simpleJson, jsonMock), UseWrite); EXPECT_EQ(encodeJSONMockStruct(json, jsonMock), UseStructEncode); EXPECT_EQ(encodeJSONMockStruct(debug, jsonMock), UseStructEncode); } } // namespace apache::thrift::op::detail