/* * 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 using output_result = std::false_type; namespace test_cpp2 { namespace cpp_reflection { std::string adjust(std::string input) { return folly::rtrimWhitespace(folly::stripLeftMargin(std::move(input))).str(); } #define TEST_IMPL_TC(TC, Expected, ...) \ do { \ std::ostringstream out; \ if constexpr (std::is_void_v) { \ apache::thrift::detail::pretty_print(out, __VA_ARGS__); \ } else { \ apache::thrift::detail::pretty_print(out, __VA_ARGS__); \ } \ const auto actual = out.str(); \ \ if (output_result::value) { \ std::cout << "actual output: " << actual << std::endl \ << "expected output: " << Expected << std::endl; \ } \ \ EXPECT_EQ(Expected, actual); \ } while (false) #define TEST_IMPL(Expected, ...) TEST_IMPL_TC(void, Expected, __VA_ARGS__) TEST(FatalPrettyPrint, pretty_print) { structA a1; *a1.a() = 99; *a1.b() = "abc"; structA a2; *a2.a() = 1001; *a2.b() = "foo"; structA a3; *a3.a() = 654; *a3.b() = "bar"; structA a4; *a4.a() = 9791; *a4.b() = "baz"; structA a5; *a5.a() = 111; *a5.b() = "gaz"; structB b1; *b1.c() = 1.23; *b1.d() = true; structB b2; *b2.c() = 9.8; *b2.d() = false; structB b3; *b3.c() = 10.01; *b3.d() = true; structB b4; *b4.c() = 159.73; *b4.d() = false; structB b5; *b5.c() = 468.02; *b5.d() = true; structC c1; *c1.a() = 47; *c1.b() = "hello, world"; *c1.c() = 132.98; *c1.d() = true; *c1.e() = enum1::field1; *c1.f() = enum2::field0_2; c1.g()->set_us("this is a test"); // c1.h intentionally left empty c1.i()->set_a(a1); // c1.j intentionally left empty *c1.j1() = {2, 4, 6, 8}; *c1.j2() = {enum1::field0, enum1::field1, enum1::field2}; *c1.j3() = {a1, a2, a3, a4}; // c1.k intentionally left empty *c1.k1() = {3, 5, 7, 9}; *c1.k2() = {enum2::field0_2, enum2::field1_2, enum2::field2_2}; *c1.k3() = {b1, b2, b3, b4}; // c1.l intentionally left empty *c1.l1() = {{2, 3}, {4, 5}, {6, 7}, {8, 9}}; *c1.l2() = {{12, enum1::field0}, {34, enum1::field1}, {56, enum1::field2}}; *c1.l3() = {{89, b1}, {78, b2}, {67, b3}, {56, b4}}; *c1.m1() = {{enum1::field0, 3}, {enum1::field1, 5}, {enum1::field2, 7}}; *c1.m2() = { {enum1::field0, enum2::field0_2}, {enum1::field1, enum2::field1_2}, {enum1::field2, enum2::field2_2}, }; *c1.m3() = {{enum1::field0, b1}, {enum1::field1, b2}, {enum1::field2, b3}}; c1.n1()["abc"] = 3; c1.n1()["def"] = 5; c1.n1()["ghi"] = 7; c1.n1()["jkl"] = 9; c1.n2()["mno"] = enum1::field0; c1.n2()["pqr"] = enum1::field1; c1.n2()["stu"] = enum1::field2; c1.n3()["vvv"] = b1; c1.n3()["www"] = b2; c1.n3()["xxx"] = b3; c1.n3()["yyy"] = b4; c1.o1()[a1] = 3; c1.o1()[a2] = 5; c1.o1()[a3] = 7; c1.o1()[a4] = 9; c1.o2()[a1] = enum1::field0; c1.o2()[a2] = enum1::field1; c1.o2()[a3] = enum1::field2; c1.o3()[a1] = b1; c1.o3()[a2] = b2; c1.o3()[a3] = b3; c1.o3()[a4] = b4; TEST_IMPL( "{\n" " a: 47,\n" " b: \"hello, world\",\n" " c: 132.98,\n" " d: true,\n" " e: field1,\n" " f: field0_2,\n" " g: {\n" " us: \"this is a test\"\n" " },\n" " h: {},\n" " i: {\n" " a: {\n" " a: 99,\n" " b: \"abc\"\n" " }\n" " },\n" " j: [],\n" " j1: [\n" " 0: 2,\n" " 1: 4,\n" " 2: 6,\n" " 3: 8\n" " ],\n" " j2: [\n" " 0: field0,\n" " 1: field1,\n" " 2: field2\n" " ],\n" " j3: [\n" " 0: {\n" " a: 99,\n" " b: \"abc\"\n" " },\n" " 1: {\n" " a: 1001,\n" " b: \"foo\"\n" " },\n" " 2: {\n" " a: 654,\n" " b: \"bar\"\n" " },\n" " 3: {\n" " a: 9791,\n" " b: \"baz\"\n" " }\n" " ],\n" " k: {},\n" " k1: {\n" " 3,\n" " 5,\n" " 7,\n" " 9\n" " },\n" " k2: {\n" " field0_2,\n" " field1_2,\n" " field2_2\n" " },\n" " k3: {\n" " {\n" " c: 1.23,\n" " d: true\n" " },\n" " {\n" " c: 9.8,\n" " d: false\n" " },\n" " {\n" " c: 10.01,\n" " d: true\n" " },\n" " {\n" " c: 159.73,\n" " d: false\n" " }\n" " },\n" " l: {},\n" " l1: {\n" " 2: 3,\n" " 4: 5,\n" " 6: 7,\n" " 8: 9\n" " },\n" " l2: {\n" " 12: field0,\n" " 34: field1,\n" " 56: field2\n" " },\n" " l3: {\n" " 56: {\n" " c: 159.73,\n" " d: false\n" " },\n" " 67: {\n" " c: 10.01,\n" " d: true\n" " },\n" " 78: {\n" " c: 9.8,\n" " d: false\n" " },\n" " 89: {\n" " c: 1.23,\n" " d: true\n" " }\n" " },\n" " m1: {\n" " field0: 3,\n" " field1: 5,\n" " field2: 7\n" " },\n" " m2: {\n" " field0: field0_2,\n" " field1: field1_2,\n" " field2: field2_2\n" " },\n" " m3: {\n" " field0: {\n" " c: 1.23,\n" " d: true\n" " },\n" " field1: {\n" " c: 9.8,\n" " d: false\n" " },\n" " field2: {\n" " c: 10.01,\n" " d: true\n" " }\n" " },\n" " n1: {\n" " \"abc\": 3,\n" " \"def\": 5,\n" " \"ghi\": 7,\n" " \"jkl\": 9\n" " },\n" " n2: {\n" " \"mno\": field0,\n" " \"pqr\": field1,\n" " \"stu\": field2\n" " },\n" " n3: {\n" " \"vvv\": {\n" " c: 1.23,\n" " d: true\n" " },\n" " \"www\": {\n" " c: 9.8,\n" " d: false\n" " },\n" " \"xxx\": {\n" " c: 10.01,\n" " d: true\n" " },\n" " \"yyy\": {\n" " c: 159.73,\n" " d: false\n" " }\n" " },\n" " o1: {\n" " {\n" " a: 99,\n" " b: \"abc\"\n" " }: 3,\n" " {\n" " a: 654,\n" " b: \"bar\"\n" " }: 7,\n" " {\n" " a: 1001,\n" " b: \"foo\"\n" " }: 5,\n" " {\n" " a: 9791,\n" " b: \"baz\"\n" " }: 9\n" " },\n" " o2: {\n" " {\n" " a: 99,\n" " b: \"abc\"\n" " }: field0,\n" " {\n" " a: 654,\n" " b: \"bar\"\n" " }: field2,\n" " {\n" " a: 1001,\n" " b: \"foo\"\n" " }: field1\n" " },\n" " o3: {\n" " {\n" " a: 99,\n" " b: \"abc\"\n" " }: {\n" " c: 1.23,\n" " d: true\n" " },\n" " {\n" " a: 654,\n" " b: \"bar\"\n" " }: {\n" " c: 10.01,\n" " d: true\n" " },\n" " {\n" " a: 1001,\n" " b: \"foo\"\n" " }: {\n" " c: 9.8,\n" " d: false\n" " },\n" " {\n" " a: 9791,\n" " b: \"baz\"\n" " }: {\n" " c: 159.73,\n" " d: false\n" " }\n" " }\n" "}", c1); TEST_IMPL( "===>{\n" "===>*-=.|a: 47,\n" "===>*-=.|b: \"hello, world\",\n" "===>*-=.|c: 132.98,\n" "===>*-=.|d: true,\n" "===>*-=.|e: field1,\n" "===>*-=.|f: field0_2,\n" "===>*-=.|g: {\n" "===>*-=.|*-=.|us: \"this is a test\"\n" "===>*-=.|},\n" "===>*-=.|h: {},\n" "===>*-=.|i: {\n" "===>*-=.|*-=.|a: {\n" "===>*-=.|*-=.|*-=.|a: 99,\n" "===>*-=.|*-=.|*-=.|b: \"abc\"\n" "===>*-=.|*-=.|}\n" "===>*-=.|},\n" "===>*-=.|j: [],\n" "===>*-=.|j1: [\n" "===>*-=.|*-=.|0: 2,\n" "===>*-=.|*-=.|1: 4,\n" "===>*-=.|*-=.|2: 6,\n" "===>*-=.|*-=.|3: 8\n" "===>*-=.|],\n" "===>*-=.|j2: [\n" "===>*-=.|*-=.|0: field0,\n" "===>*-=.|*-=.|1: field1,\n" "===>*-=.|*-=.|2: field2\n" "===>*-=.|],\n" "===>*-=.|j3: [\n" "===>*-=.|*-=.|0: {\n" "===>*-=.|*-=.|*-=.|a: 99,\n" "===>*-=.|*-=.|*-=.|b: \"abc\"\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|1: {\n" "===>*-=.|*-=.|*-=.|a: 1001,\n" "===>*-=.|*-=.|*-=.|b: \"foo\"\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|2: {\n" "===>*-=.|*-=.|*-=.|a: 654,\n" "===>*-=.|*-=.|*-=.|b: \"bar\"\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|3: {\n" "===>*-=.|*-=.|*-=.|a: 9791,\n" "===>*-=.|*-=.|*-=.|b: \"baz\"\n" "===>*-=.|*-=.|}\n" "===>*-=.|],\n" "===>*-=.|k: {},\n" "===>*-=.|k1: {\n" "===>*-=.|*-=.|3,\n" "===>*-=.|*-=.|5,\n" "===>*-=.|*-=.|7,\n" "===>*-=.|*-=.|9\n" "===>*-=.|},\n" "===>*-=.|k2: {\n" "===>*-=.|*-=.|field0_2,\n" "===>*-=.|*-=.|field1_2,\n" "===>*-=.|*-=.|field2_2\n" "===>*-=.|},\n" "===>*-=.|k3: {\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|c: 1.23,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|c: 9.8,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|c: 10.01,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|c: 159.73,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|}\n" "===>*-=.|},\n" "===>*-=.|l: {},\n" "===>*-=.|l1: {\n" "===>*-=.|*-=.|2: 3,\n" "===>*-=.|*-=.|4: 5,\n" "===>*-=.|*-=.|6: 7,\n" "===>*-=.|*-=.|8: 9\n" "===>*-=.|},\n" "===>*-=.|l2: {\n" "===>*-=.|*-=.|12: field0,\n" "===>*-=.|*-=.|34: field1,\n" "===>*-=.|*-=.|56: field2\n" "===>*-=.|},\n" "===>*-=.|l3: {\n" "===>*-=.|*-=.|56: {\n" "===>*-=.|*-=.|*-=.|c: 159.73,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|67: {\n" "===>*-=.|*-=.|*-=.|c: 10.01,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|78: {\n" "===>*-=.|*-=.|*-=.|c: 9.8,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|89: {\n" "===>*-=.|*-=.|*-=.|c: 1.23,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|}\n" "===>*-=.|},\n" "===>*-=.|m1: {\n" "===>*-=.|*-=.|field0: 3,\n" "===>*-=.|*-=.|field1: 5,\n" "===>*-=.|*-=.|field2: 7\n" "===>*-=.|},\n" "===>*-=.|m2: {\n" "===>*-=.|*-=.|field0: field0_2,\n" "===>*-=.|*-=.|field1: field1_2,\n" "===>*-=.|*-=.|field2: field2_2\n" "===>*-=.|},\n" "===>*-=.|m3: {\n" "===>*-=.|*-=.|field0: {\n" "===>*-=.|*-=.|*-=.|c: 1.23,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|field1: {\n" "===>*-=.|*-=.|*-=.|c: 9.8,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|field2: {\n" "===>*-=.|*-=.|*-=.|c: 10.01,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|}\n" "===>*-=.|},\n" "===>*-=.|n1: {\n" "===>*-=.|*-=.|\"abc\": 3,\n" "===>*-=.|*-=.|\"def\": 5,\n" "===>*-=.|*-=.|\"ghi\": 7,\n" "===>*-=.|*-=.|\"jkl\": 9\n" "===>*-=.|},\n" "===>*-=.|n2: {\n" "===>*-=.|*-=.|\"mno\": field0,\n" "===>*-=.|*-=.|\"pqr\": field1,\n" "===>*-=.|*-=.|\"stu\": field2\n" "===>*-=.|},\n" "===>*-=.|n3: {\n" "===>*-=.|*-=.|\"vvv\": {\n" "===>*-=.|*-=.|*-=.|c: 1.23,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|\"www\": {\n" "===>*-=.|*-=.|*-=.|c: 9.8,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|\"xxx\": {\n" "===>*-=.|*-=.|*-=.|c: 10.01,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|\"yyy\": {\n" "===>*-=.|*-=.|*-=.|c: 159.73,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|}\n" "===>*-=.|},\n" "===>*-=.|o1: {\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 99,\n" "===>*-=.|*-=.|*-=.|b: \"abc\"\n" "===>*-=.|*-=.|}: 3,\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 654,\n" "===>*-=.|*-=.|*-=.|b: \"bar\"\n" "===>*-=.|*-=.|}: 7,\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 1001,\n" "===>*-=.|*-=.|*-=.|b: \"foo\"\n" "===>*-=.|*-=.|}: 5,\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 9791,\n" "===>*-=.|*-=.|*-=.|b: \"baz\"\n" "===>*-=.|*-=.|}: 9\n" "===>*-=.|},\n" "===>*-=.|o2: {\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 99,\n" "===>*-=.|*-=.|*-=.|b: \"abc\"\n" "===>*-=.|*-=.|}: field0,\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 654,\n" "===>*-=.|*-=.|*-=.|b: \"bar\"\n" "===>*-=.|*-=.|}: field2,\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 1001,\n" "===>*-=.|*-=.|*-=.|b: \"foo\"\n" "===>*-=.|*-=.|}: field1\n" "===>*-=.|},\n" "===>*-=.|o3: {\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 99,\n" "===>*-=.|*-=.|*-=.|b: \"abc\"\n" "===>*-=.|*-=.|}: {\n" "===>*-=.|*-=.|*-=.|c: 1.23,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 654,\n" "===>*-=.|*-=.|*-=.|b: \"bar\"\n" "===>*-=.|*-=.|}: {\n" "===>*-=.|*-=.|*-=.|c: 10.01,\n" "===>*-=.|*-=.|*-=.|d: true\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 1001,\n" "===>*-=.|*-=.|*-=.|b: \"foo\"\n" "===>*-=.|*-=.|}: {\n" "===>*-=.|*-=.|*-=.|c: 9.8,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|},\n" "===>*-=.|*-=.|{\n" "===>*-=.|*-=.|*-=.|a: 9791,\n" "===>*-=.|*-=.|*-=.|b: \"baz\"\n" "===>*-=.|*-=.|}: {\n" "===>*-=.|*-=.|*-=.|c: 159.73,\n" "===>*-=.|*-=.|*-=.|d: false\n" "===>*-=.|*-=.|}\n" "===>*-=.|}\n" "===>}", c1, "*-=.|", "===>"); } namespace { struct UniqueHelper { template static std::unique_ptr build(Args&&... args) { return std::make_unique(std::forward(args)...); } }; struct SharedHelper { template static std::shared_ptr build(Args&&... args) { return std::make_shared(std::forward(args)...); } }; struct SharedConstHelper { template static std::shared_ptr build(Args&&... args) { return std::make_shared(std::forward(args)...); } }; } // namespace template void ref_test() { Structure v; const char* expectedStr1 = R"( { aStruct: null, aList: null, aSet: null, aMap: null, aUnion: null, anOptionalStruct: null, anOptionalList: null, anOptionalSet: null, anOptionalMap: null, anOptionalUnion: null })"; v.aStruct_ref() = nullptr; v.aList_ref() = nullptr; v.aSet_ref() = nullptr; v.aMap_ref() = nullptr; v.aUnion_ref() = nullptr; v.anOptionalStruct_ref() = nullptr; v.anOptionalList_ref() = nullptr; v.anOptionalSet_ref() = nullptr; v.anOptionalMap_ref() = nullptr; v.anOptionalUnion_ref() = nullptr; TEST_IMPL(adjust(expectedStr1), v); using namespace std::string_literals; std::initializer_list hello = {"Hello"s}; std::unordered_map helloWorld{{"Hello"s, "World"s}}; v.aStruct_ref() = Helper::template build(); v.anOptionalStruct_ref() = Helper::template build(); v.aList_ref() = Helper::template build>(hello); v.anOptionalList_ref() = Helper::template build>(hello); v.aSet_ref() = Helper::template build>(hello); v.anOptionalSet_ref() = Helper::template build>(hello); v.aMap_ref() = Helper::template build>( helloWorld); v.anOptionalMap_ref() = Helper::template build>( helloWorld); v.aUnion_ref() = Helper::template build(); v.anOptionalUnion_ref() = Helper::template build(); const char* expectedStr2 = R"( { aStruct: { a: 0, b: "" }, aList: [ 0: "Hello" ], aSet: { "Hello" }, aMap: { "Hello": "World" }, aUnion: {}, anOptionalStruct: { a: 0, b: "" }, anOptionalList: [ 0: "Hello" ], anOptionalSet: { "Hello" }, anOptionalMap: { "Hello": "World" }, anOptionalUnion: {} })"; TEST_IMPL(adjust(expectedStr2), v); } TEST(FatalPrettyPrint, struct_ref_unique) { ref_test(); } TEST(FatalPrettyPrint, ref_shared) { ref_test(); } TEST(FatalPrettyPrint, ref_shared_const) { ref_test(); } TEST(FatalPrettyPrint, optional_member) { struct1 v; v.field1() = 4; const char* expectedStr = R"( { field0: 0, field1: "\004", field2: field0, field3: field0_2, field5: {} })"; TEST_IMPL(adjust(expectedStr), v); } TEST(FatalPrettyPrint, escape_strings) { structA s; s.b() = "foo\nbar"; const char* expectedStr = R"( { a: 0, b: "foo\nbar" })"; TEST_IMPL(adjust(expectedStr), s); } TEST(FatalPrettyPrint, variant_ref_unique) { variantHasRefUnique v; const char* expectedStr1 = R"( { aStruct: null })"; v.set_aStruct() = nullptr; TEST_IMPL(adjust(expectedStr1), v); v.set_aStruct(); const char* expectedStr2 = R"( { aStruct: { a: 0, b: "" } })"; TEST_IMPL(adjust(expectedStr2), v); } TEST(FatalPrettyPrint, tc_binary_iobuf) { using tc = apache::thrift::type_class::binary; std::string x = "hello"; std::string y = "world"; auto xb = folly::IOBuf::wrapBuffer(x.data(), x.size()); auto yb = folly::IOBuf::wrapBuffer(y.data(), y.size()); xb->prependChain(std::move(yb)); const char* expectedStr = R"("0x68656c6c6f776f726c64")"; // hex("helloworld") TEST_IMPL_TC(tc, expectedStr, xb->to()); TEST_IMPL_TC(tc, expectedStr, xb->to()); TEST_IMPL_TC(tc, expectedStr, xb); TEST_IMPL_TC(tc, expectedStr, *xb); TEST_IMPL_TC(tc, R"()", std::unique_ptr()); TEST_IMPL_TC(tc, R"("0x")", std::make_unique()); } } // namespace cpp_reflection } // namespace test_cpp2