/* * 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 #include #include #include #include #include #include #include using apache::thrift::protocol::asValueStruct; namespace apache::thrift::conformance::data { namespace mp11 = boost::mp11; namespace { template auto toValue(const GeneratedValue& value) { if constexpr (std::is_same_v) { return *folly::IOBuf::copyBuffer(value.data()); } else { return value; } } template PatchOpTestCase makeAssignTest( const GeneratedValue& value, const AnyRegistry& registry, const Protocol& protocol) { PatchOpTestCase opTest; PatchOpRequest req; auto initialValue = value.value; op::clear(initialValue); req.value() = registry.store(asValueStruct(initialValue), protocol); auto patch = op::patch_type(); patch = toValue(value.value); req.patch() = registry.store(asValueStruct(patch.toThrift()), protocol); opTest.request() = req; opTest.result() = registry.store(asValueStruct(value.value), protocol); return opTest; } template PatchOpTestCase makeClearTest( const GeneratedValue& value, const AnyRegistry& registry, const Protocol& protocol) { PatchOpTestCase opTest; PatchOpRequest req; req.value() = registry.store(asValueStruct(value.value), protocol); auto patch = op::patch_type::createClear(); req.patch() = registry.store(asValueStruct(patch.toThrift()), protocol); auto clearValue = value.value; op::clear(clearValue); opTest.request() = req; opTest.result() = registry.store(asValueStruct(clearValue), protocol); return opTest; } template T makeAddExpectedResult(T value, T add) { if constexpr (std::is_convertible_v) { return !value; } else { return util::add_saturating(value, add); } } template PatchOpTestCase makeAddTest( const GeneratedValue& value, const AnyRegistry& registry, const Protocol& protocol, T toAdd) { PatchOpTestCase opTest; PatchOpRequest req; req.value() = registry.store(asValueStruct(value.value), protocol); auto addOp = [&](auto& patch) { if constexpr (std::is_same_v) { patch.invert(); } else { patch += toAdd; } }; auto patch = op::patch_type(); addOp(patch); req.patch() = registry.store(asValueStruct(patch.toThrift()), protocol); opTest.request() = req; opTest.result() = registry.store( asValueStruct(makeAddExpectedResult(value.value, toAdd)), protocol); return opTest; } template PatchOpTestCase makePrependTest( const GeneratedValue& value, const AnyRegistry& registry, const Protocol& protocol) { PatchOpTestCase opTest; PatchOpRequest req; req.value() = registry.store(asValueStruct(value.value), protocol); auto patch = op::patch_type(); patch.prepend(toValue(value.value)); req.patch() = registry.store(asValueStruct(patch.toThrift()), protocol); opTest.request() = req; auto expected = value.value + value.value; opTest.result() = registry.store(asValueStruct(toValue(expected)), protocol); return opTest; } template PatchOpTestCase makeAppendTest( const GeneratedValue& value, const AnyRegistry& registry, const Protocol& protocol) { PatchOpTestCase opTest; PatchOpRequest req; req.value() = registry.store(asValueStruct(value.value), protocol); auto patch = op::patch_type(); patch.append(toValue(value.value)); req.patch() = registry.store(asValueStruct(patch.toThrift()), protocol); opTest.request() = req; auto expected = value.value + value.value; opTest.result() = registry.store(asValueStruct(toValue(expected)), protocol); return opTest; } template Test createNumericPatchTest( const AnyRegistry& registry, const Protocol& protocol) { Test test; test.name() = protocol.name(); for (const auto& value : ValueGenerator::getInterestingValues()) { auto& assignCase = test.testCases()->emplace_back(); assignCase.name() = fmt::format("{}/assign.{}", type::getName(), value.name); auto& tascase = assignCase.test().emplace().objectPatch_ref().emplace(); tascase = makeAssignTest(value, registry, protocol); auto& clearCase = test.testCases()->emplace_back(); clearCase.name() = fmt::format("{}/clear.{}", type::getName(), value.name); auto& tclcase = clearCase.test().emplace().objectPatch_ref().emplace(); tclcase = makeClearTest(value, registry, protocol); using ValueType = decltype(value.value); auto addAddTestCase = [&](ValueType toAdd) { auto& addCase = test.testCases()->emplace_back(); addCase.name() = fmt::format("{}/add.{}_{}", type::getName(), value.name, toAdd); auto& tadcase = addCase.test().emplace().objectPatch_ref().emplace(); tadcase = makeAddTest(value, registry, protocol, toAdd); }; addAddTestCase(1); if constexpr (!std::is_same_v) { addAddTestCase(-1); if (value.value > 0) { addAddTestCase(-value.value); } } } return test; } template Test createStringLikePatchTest( const AnyRegistry& registry, const Protocol& protocol) { Test test; test.name() = protocol.name(); for (const auto& value : ValueGenerator::getInterestingValues()) { auto& assignCase = test.testCases()->emplace_back(); assignCase.name() = fmt::format("{}/assign.{}", type::getName(), value.name); auto& tascase = assignCase.test().emplace().objectPatch_ref().emplace(); tascase = makeAssignTest(value, registry, protocol); auto& clearCase = test.testCases()->emplace_back(); clearCase.name() = fmt::format("{}/clear", type::getName()); auto& tclcase = clearCase.test().emplace().objectPatch_ref().emplace(); tclcase = makeClearTest(value, registry, protocol); auto& prependCase = test.testCases()->emplace_back(); prependCase.name() = fmt::format("{}/prepend.{}", type::getName(), value.name); auto& tprcase = prependCase.test().emplace().objectPatch_ref().emplace(); tprcase = makePrependTest(value, registry, protocol); auto& appendCase = test.testCases()->emplace_back(); appendCase.name() = fmt::format("{}/append.{}", type::getName(), value.name); auto& tapcase = appendCase.test().emplace().objectPatch_ref().emplace(); tapcase = makeAppendTest(value, registry, protocol); } return test; } namespace { template struct container_from_class { static_assert(std::is_same_v, "Not a container class"); }; template <> struct container_from_class { template using type = type::list; }; template <> struct container_from_class { template using type = type::set; }; template using container_from_class_t = typename container_from_class::template type; template struct target_type_impl { using type = T; }; template struct target_type_impl>> { using type = type::standard_type; }; template using target_type = typename target_type_impl::type; protocol::Object setObjectMemeber( protocol::Object&& object, int16_t id, protocol::Value value) { object[FieldId{id}] = value; return std::move(object); } protocol::Value wrapObjectInValue(protocol::Object object) { protocol::Value result; result.emplace_object(object); return result; } template protocol::Object patchAddOperation( protocol::Object&& patch, op::PatchOp operation, auto value) { return setObjectMemeber( std::move(patch), static_cast(operation), asValueStruct(value)); } protocol::Object patchAddOperation( protocol::Object&& patch, op::PatchOp operation, protocol::Value value) { return setObjectMemeber( std::move(patch), static_cast(operation), value); } template protocol::Value makePatchValue(auto operation, auto value) { return wrapObjectInValue( patchAddOperation(protocol::Object{}, operation, value)); } protocol::Value makePatchValue(auto operation, protocol::Value value) { return wrapObjectInValue( patchAddOperation(protocol::Object{}, operation, value)); } template PatchOpTestCase makeAssignTC( const AnyRegistry& registry, const Protocol& protocol, Type value) { PatchOpTestCase tascase; PatchOpRequest req; using Container = target_type; Container initial; Container expected = {value}; req.value() = registry.store(asValueStruct(initial), protocol); req.patch() = registry.store( makePatchValue(op::PatchOp::Assign, expected), protocol); tascase.request() = req; tascase.result() = registry.store(asValueStruct(expected), protocol); return tascase; } template PatchOpTestCase makeClearTC( const AnyRegistry& registry, const Protocol& protocol, Type value) { PatchOpTestCase tascase; PatchOpRequest req; using Container = target_type; Container initial = {value}; Container expected; req.value() = registry.store(asValueStruct(initial), protocol); req.patch() = registry.store( makePatchValue(op::PatchOp::Clear, true), protocol); tascase.request() = req; auto expectedValue = asValueStruct(expected); if (auto obj = expectedValue.if_object()) { obj->members()->clear(); } tascase.result() = registry.store(expectedValue, protocol); return tascase; } template PatchOpTestCase makeContainerRemoveTC( const AnyRegistry& registry, const Protocol& protocol, auto value, auto toRemove) { PatchOpTestCase tascase; PatchOpRequest req; using Container = type::native_type; Container initial = {value}; Container expected; req.value() = registry.store(asValueStruct(initial), protocol); req.patch() = registry.store( makePatchValue>( op::PatchOp::Remove, std::set>{toRemove}), protocol); tascase.request() = req; tascase.result() = registry.store(asValueStruct(expected), protocol); return tascase; } template PatchOpTestCase makeValueContainerPrependTC( const AnyRegistry& registry, const Protocol& protocol, auto value) { PatchOpTestCase tascase; PatchOpRequest req; using Container = type::native_type; using PatchContainer = type::standard_type; auto patchValue = value.value; op::clear(patchValue); Container initial = {value.value}; Container expected = {patchValue, value.value}; if (patchValue == value.value && !std::is_same_v) { expected = initial; } PatchContainer patchData = {patchValue}; req.value() = registry.store(asValueStruct(initial), protocol); req.patch() = registry.store( makePatchValue(op::PatchOp::Add, patchData), protocol); tascase.request() = req; tascase.result() = registry.store(asValueStruct(expected), protocol); return tascase; } template PatchOpTestCase makeContainerAppendTC( const AnyRegistry& registry, const Protocol& protocol, auto value, auto toAppend) { PatchOpTestCase tascase; PatchOpRequest req; using Container = type::native_type; Container initial = {value}; Container expected = {value, toAppend}; Container patchData = {toAppend}; req.value() = registry.store(asValueStruct(initial), protocol); req.patch() = registry.store( makePatchValue(op::PatchOp::Put, patchData), protocol); tascase.request() = req; tascase.result() = registry.store(asValueStruct(expected), protocol); return tascase; } template PatchOpTestCase makePatchXTC( const AnyRegistry& registry, const Protocol& protocol, Type initial, op::PatchOp patchOp, protocol::Value valuePatch, Type expected) { PatchOpTestCase tascase; PatchOpRequest req; req.value() = registry.store(asValueStruct(initial), protocol); req.patch() = registry.store(makePatchValue(patchOp, valuePatch), protocol); tascase.request() = req; tascase.result() = registry.store(asValueStruct(expected), protocol); return tascase; } template type::standard_type valueToAssign() { if constexpr (std::is_convertible_v) { return 1; } else { return "test"; } } template PatchOpTestCase makeEnsureTC( const AnyRegistry& registry, const Protocol& protocol, Type initial, Type expected, op::PatchOp patchOp = op::PatchOp::EnsureStruct) { PatchOpTestCase tascase; PatchOpRequest req; req.value() = registry.store(asValueStruct(initial), protocol); req.patch() = registry.store( makePatchValue(patchOp, asValueStruct(expected)), protocol); tascase.request() = req; tascase.result() = registry.store(asValueStruct(expected), protocol); return tascase; } } // namespace template Test createListSetPatchTest( const AnyRegistry& registry, const Protocol& protocol) { Test test; test.name() = protocol.name(); using ValueContainers = boost::mp11::mp_list; mp11::mp_for_each([&](auto cc) { using CC = decltype(cc); auto makeTestName = [&](const auto& value, auto op) { return fmt::format( "{}<{}>/{}.{}", type::getName(), type::getName(), op, value.name); }; for (const auto& value : ValueGenerator::getInterestingValues()) { using ContainerTag = container_from_class_t; auto& assignCase = test.testCases()->emplace_back(); assignCase.name() = makeTestName(value, "assign"); assignCase.test().emplace().objectPatch_ref() = makeAssignTC(registry, protocol, value.value); auto& clearCase = test.testCases()->emplace_back(); clearCase.name() = makeTestName(value, "clear"); clearCase.test().emplace().objectPatch_ref() = makeClearTC(registry, protocol, value.value); if constexpr (std::is_same_v) { auto& removeCase = test.testCases()->emplace_back(); removeCase.name() = makeTestName(value, "remove"); removeCase.test().emplace().objectPatch_ref() = makeContainerRemoveTC( registry, protocol, value.value, value.value); } auto& prependCase = test.testCases()->emplace_back(); prependCase.name() = makeTestName(value, "prepend"); prependCase.test().emplace().objectPatch_ref() = makeValueContainerPrependTC( registry, protocol, value); if constexpr (std::is_same_v) { auto& prependSetCase = test.testCases()->emplace_back(); prependSetCase.name() = makeTestName(value, "prepend_set"); prependSetCase.test().emplace().objectPatch_ref() = makeValueContainerPrependTC>( registry, protocol, value); } auto patchValue = value.value; op::clear(patchValue); auto& appendCase = test.testCases()->emplace_back(); appendCase.name() = makeTestName(value, "append"); appendCase.test().emplace().objectPatch_ref() = makeContainerAppendTC( registry, protocol, value.value, patchValue); } }); return test; } template Test createMapPatchTest(const AnyRegistry& registry, const Protocol& protocol) { Test test; test.name() = protocol.name(); using KeyTypeTags = boost::mp11::mp_list< type::byte_t, type::i16_t, type::i32_t, type::i64_t, type::string_t>; mp11::mp_for_each([&](auto kk) { using KK = decltype(kk); using ContainerTag = type::map; auto makeTestName = [](const auto& key, auto op) { return fmt::format( "map<{},{}>/{}.{}", type::getName(), type::getName(), op, key.name); }; for (const auto& key : ValueGenerator::getInterestingValues()) { type::standard_type value; op::clear(value); auto& assignCase = test.testCases()->emplace_back(); assignCase.name() = makeTestName(key, "assign"); assignCase.test().emplace().objectPatch_ref() = makeAssignTC( registry, protocol, std::pair{key.value, value}); auto& clearCase = test.testCases()->emplace_back(); clearCase.name() = makeTestName(key, "clear"); clearCase.test().emplace().objectPatch_ref() = makeClearTC( registry, protocol, std::pair{key.value, value}); auto& removeCase = test.testCases()->emplace_back(); removeCase.name() = makeTestName(key, "remove"); removeCase.test().emplace().objectPatch_ref() = makeContainerRemoveTC( registry, protocol, std::pair{key.value, value}, key.value); auto patchkey = key.value; op::clear(patchkey); auto& appendCase = test.testCases()->emplace_back(); appendCase.name() = makeTestName(key, "append"); appendCase.test().emplace().objectPatch_ref() = makeContainerAppendTC( registry, protocol, std::pair{key.value, value}, std::pair{patchkey, value}); using Container = type::native_type; Container initial = {{key.value, value}}; auto newValue = valueToAssign(); auto keyValue = asValueStruct(key.value); auto keyValuePatch = asValueStruct( (op::patch_type() = toValue(newValue)).toThrift()); Container expected = {{key.value, newValue}}; protocol::Value patchValue; patchValue.ensure_map()[keyValue] = keyValuePatch; auto& patchPriorCase = test.testCases()->emplace_back(); patchPriorCase.name() = makeTestName(key, "patch_prior"); patchPriorCase.test().emplace().objectPatch_ref() = makePatchXTC( registry, protocol, initial, op::PatchOp::PatchPrior, patchValue, expected); auto& patchCase = test.testCases()->emplace_back(); patchCase.name() = makeTestName(key, "patch"); patchCase.test().emplace().objectPatch_ref() = makePatchXTC( registry, protocol, initial, op::PatchOp::PatchAfter, patchValue, expected); initial = {}; auto& ensureCase = test.testCases()->emplace_back(); ensureCase.name() = makeTestName(key, "ensure"); ensureCase.test().emplace().objectPatch_ref() = makeEnsureTC(registry, protocol, initial, expected); } }); return test; } template Test createStructPatchTest( const AnyRegistry& registry, const Protocol& protocol) { Test test; test.name() = protocol.name(); using Struct = test::testset::struct_with; auto makeTestName = [](auto op) { return fmt::format("struct<{}>/{}", type::getName(), op); }; type::standard_type value; op::clear(value); Struct initial; initial.field_1_ref() = value; auto& assignCase = test.testCases()->emplace_back(); assignCase.name() = makeTestName("assign"); assignCase.test().emplace().objectPatch_ref() = makeAssignTC(registry, protocol, initial); auto& clearCase = test.testCases()->emplace_back(); clearCase.name() = makeTestName("clear"); clearCase.test().emplace().objectPatch_ref() = makeClearTC(registry, protocol, initial); Struct expected = initial; expected.field_1_ref() = valueToAssign(); auto patch = op::patch_type(); patch = toValue(*expected.field_1_ref()); auto patchValue = wrapObjectInValue(setObjectMemeber( protocol::Object{}, 1, asValueStruct(patch.toThrift()))); auto& patchPriorCase = test.testCases()->emplace_back(); patchPriorCase.name() = makeTestName("patch_prior"); patchPriorCase.test().emplace().objectPatch_ref() = makePatchXTC( registry, protocol, initial, op::PatchOp::PatchPrior, patchValue, expected); auto& patchCase = test.testCases()->emplace_back(); patchCase.name() = makeTestName("patch"); patchCase.test().emplace().objectPatch_ref() = makePatchXTC( registry, protocol, initial, op::PatchOp::PatchAfter, patchValue, expected); using StructOptional = test::testset::struct_with; StructOptional initialOpt; StructOptional expectedOpt; expectedOpt.field_1_ref() = valueToAssign(); auto& ensureCase = test.testCases()->emplace_back(); ensureCase.name() = makeTestName("ensureStruct"); ensureCase.test().emplace().objectPatch_ref() = makeEnsureTC(registry, protocol, initialOpt, expectedOpt); using UnionStruct = test::testset::union_with; UnionStruct initialUnion; UnionStruct expectedUnion; expectedUnion.field_1_ref() = valueToAssign(); auto& ensureUnionCase = test.testCases()->emplace_back(); ensureUnionCase.name() = makeTestName("ensureUnion"); ensureUnionCase.test().emplace().objectPatch_ref() = makeEnsureTC( registry, protocol, initialUnion, expectedUnion, op::PatchOp::EnsureUnion); return test; } void addPatchToSuite( const AnyRegistry& registry, const Protocol& protocol, TestSuite& suite) { mp11::mp_for_each([&](auto tt) { using TT = decltype(tt); if constexpr ( !std::is_same_v && !std::is_same_v) { suite.tests()->emplace_back( createNumericPatchTest(registry, protocol)); } else { suite.tests()->emplace_back( createStringLikePatchTest(registry, protocol)); } if constexpr (!std::is_same_v) { suite.tests()->emplace_back( createListSetPatchTest(registry, protocol)); } suite.tests()->emplace_back(createMapPatchTest(registry, protocol)); suite.tests()->emplace_back(createStructPatchTest(registry, protocol)); }); } } // namespace TestSuite createPatchSuite( const std::set& protocols, const AnyRegistry& registry) { TestSuite suite; suite.name() = "PatchTest"; for (const auto& protocol : protocols) { addPatchToSuite(registry, protocol, suite); } return suite; } } // namespace apache::thrift::conformance::data