diff --git a/Tests/CMakeLists.txt b/Tests/CMakeLists.txt index 366bef26927ea725ea2ece806a111a5e04c9a2e1..011147d115c425e087c53bb61b51f36d2464f963 100644 --- a/Tests/CMakeLists.txt +++ b/Tests/CMakeLists.txt @@ -87,6 +87,7 @@ ogs_add_executable(testrunner ${TEST_SOURCES}) target_sources(testrunner PRIVATE ProcessLib/Graph/TestGet.cpp + ProcessLib/Graph/TestApply.cpp ) target_link_libraries( diff --git a/Tests/ProcessLib/Graph/TestApply.cpp b/Tests/ProcessLib/Graph/TestApply.cpp new file mode 100644 index 0000000000000000000000000000000000000000..7f2576c63abc4823644a1d1fe5564f49dd5f2b56 --- /dev/null +++ b/Tests/ProcessLib/Graph/TestApply.cpp @@ -0,0 +1,816 @@ +/** + * \file + * \copyright + * Copyright (c) 2012-2024, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#include <gtest/gtest.h> + +#include "BaseLib/StrongType.h" +#include "ProcessLib/Graph/Apply.h" + +namespace +{ +void f_zero_args() {} +} // namespace + +namespace PG = ProcessLib::Graph; +namespace PGD = ProcessLib::Graph::detail; + +class ProcessLibGraphApply : public testing::Test +{ +protected: + using SInt = BaseLib::StrongType<int, struct SIntTag>; + using SDouble = BaseLib::StrongType<double, struct SDoubleTag>; + + // --- tested functions ---------------------------------------------------- + + // --- single output argument + + static constexpr auto lambda_increment = [](int& i) { ++i; }; + + struct SIncrementF + { + void f(int& i) const { ++i; } + }; + struct SIncrementEval + { + void eval(int& i) const { ++i; } + }; + + // returns a vector of functions running the code under test + template <typename Data> + static std::vector<std::function<void(Data&)>> + getRunnersSIncrementSingleTuple() + { + return {[](Data& args) { PGD::applyImpl(lambda_increment, args.t); }, + // --- + [](Data& args) + { + SIncrementF const s; + PGD::applyImpl(&SIncrementF::f, s, args.t); + }, + // --- + [](Data& args) { PG::apply(lambda_increment, args.t); }, + // --- + [](Data& args) + { + SIncrementEval const s; + PG::eval(s, args.t); + }}; + } + + // --- single argument and return value + + struct SIdentity + { + double operator()(double d) { return d; } + double eval(double d) { return d; } + double f(double d) { return d; } + }; + + // returns a vector of functions running the code under test + template <typename Data> + static std::vector<std::function<double(Data&)>> + getRunnersSIdentitySingleTuple() + { + return {[](Data& args) { return PGD::applyImpl(SIdentity{}, args.t); }, + // --- + [](Data& args) + { + SIdentity s; + return PGD::applyImpl(&SIdentity::f, s, args.t); + }, + // --- + [](Data& args) + { + SIdentity s; + return PG::apply(s, args.t); + }, + // --- + [](Data& args) + { + SIdentity s; + return PG::eval(s, args.t); + }}; + } + + // --- input and output argument + + struct SAddSecondToFirstF + { + void f(double& d, SInt const& si) const { d += *si; } + }; + struct SAddSecondToFirstFunctionObject + { + void operator()(double& d, SInt const& si) const { d += *si; } + }; + struct SAddSecondToFirstEval + { + void eval(double& d, SInt const& si) const { d += *si; } + }; + + // returns a vector of functions running the code under test + template <typename Data> + static std::vector<std::function<void(Data&)>> + getRunnersSAddSecondToFirstSingleTuple() + { + return {[](Data& args) + { + SAddSecondToFirstF const s; + PGD::applyImpl(&SAddSecondToFirstF::f, s, args.t); + }, + // --- + [](Data& args) + { + SAddSecondToFirstFunctionObject const s; + PGD::applyImpl(s, args.t); + }, + // --- + [](Data& args) + { + SAddSecondToFirstFunctionObject const s; + PG::apply(s, args.t); + }, + // --- + [](Data& args) + { + SAddSecondToFirstEval const s; + PG::eval(s, args.t); + }}; + } + + // --- multiple arguments + + struct SMultipleArguments + { + int f(SInt const& si, double const& d, SDouble& sd, int i) + { + *sd += *si + d - i; + return 2 * i; + } + int operator()(SInt const& si, double const& d, SDouble& sd, int i) + { + return f(si, d, sd, i); + } + int eval(SInt const& si, double const& d, SDouble& sd, int i) + { + return f(si, d, sd, i); + } + }; + + // returns a vector of functions running the code under test + template <typename Data> + static std::vector<std::function<int(Data&)>> + getRunnersSMultipleArgumentsSingleTuple() + { + return {[](Data& args) + { + SMultipleArguments s; + return PGD::applyImpl(&SMultipleArguments::f, s, args.t); + }, + // --- + [](Data& args) + { return PGD::applyImpl(SMultipleArguments{}, args.t); }, + // --- + [](Data& args) + { + SMultipleArguments s; + return PG::apply(s, args.t); + }, + // --- + [](Data& args) + { + SMultipleArguments s; + return PG::eval(s, args.t); + }}; + } + + // --- test data ----------------------------------------------------------- + + struct DataSingleTuple + { + std::tuple<int, double, SInt const, SDouble> t{5, 6.5, 2, 0.25}; + + // keeps original data to compute reference results for checks + std::tuple<int, double, SInt, SDouble> const init{t}; + }; + + struct DataSingleTupleWithReferences + { + double d = 6.5; + SInt si{2}; + SDouble sd{0.25}; + + std::tuple<int, double&, SInt const&, SDouble&> t{5, d, si, sd}; + + // keeps original data to compute reference results for checks + std::tuple<int, double, SInt, SDouble> const init{t}; + }; + + struct DataMultipleTuples + { + double d = 6.5; + SInt si{2}; + SDouble sd{0.25}; + + std::tuple<SDouble&> t1{sd}; + std::tuple<SInt const&> const t2{si}; + std::tuple<int, double&> t3{5, d}; + std::tuple<> t4; + std::tuple<std::string> t5{"unrelated"}; + + // keeps original data to compute reference results for checks + std::tuple<SDouble, SInt, int, double, std::string> const init{ + std::tuple_cat(t1, t2, t3, t4, t5)}; + }; +}; + +TEST_F(ProcessLibGraphApply, ZeroArgFct) +{ + struct S + { + void operator()() {} + void f() {} + int g() { return ++i; } + + private: + int i = 7; + }; + + struct S2 + { + int operator()() { return --i; } + + private: + int i = 20; + }; + + struct SConst + { + void operator()() const {} + void f() const {} + int g() const { return 41; } + }; + + auto this_should_compile = []<typename... T>(T&&... args) + { + std::tuple<> nothing; + std::tuple<char, int, double> something{'a', 0, 0.5}; + + auto const& cnothing = nothing; + auto const& csomething = something; + + // We test an implementation detail, because it serves as a "backend" to + // both apply() and eval(), but is more generic and easier to test. + PGD::applyImpl(std::forward<T>(args)...); + PGD::applyImpl(std::forward<T>(args)..., nothing); + PGD::applyImpl(std::forward<T>(args)..., cnothing); + PGD::applyImpl(std::forward<T>(args)..., something); + PGD::applyImpl(std::forward<T>(args)..., csomething); + return PGD::applyImpl(std::forward<T>(args)..., nothing, csomething, + cnothing); + }; + + { + auto lambda = []() {}; + auto lambda_mut = []() mutable {}; + auto lambda_capture = [i = 5]() { return i; }; + auto lambda_capture_mut = [i = 13]() mutable { return ++i; }; + + this_should_compile(lambda); // lambda + this_should_compile(lambda_mut); // mutable ... + EXPECT_EQ(5, this_should_compile(lambda_capture)); // ... with capture + + lambda_capture_mut(); // mutable ... with capture + EXPECT_EQ(13 + 6 + 1, this_should_compile(lambda_capture_mut)) + << "lambda_capture_mut was expected to be called six times and " + "increase its internal counter. This test guarantees that the " + "implementation does not copy the function object."; + } + + this_should_compile(f_zero_args); // free function + + { + S s; + this_should_compile(s); // function object (void) + this_should_compile(&S::f, s); // void member function + + s.g(); // non-void member function + EXPECT_EQ(7 + 6 + 1, this_should_compile(&S::g, s)) + << "S::g() was expected to be called six times and increase its " + "internal counter. This test guarantees that the implementation " + "does not copy the object."; + } + + { + S2 s2; // function object (non-void) + s2(); + EXPECT_EQ(20 - 6 - 1, this_should_compile(s2)) + << "S2::operator()() was expected to be called six times and " + "decrease its internal counter. This test guarantees that the " + "implementation does not copy the function object."; + } + + { + SConst const sconst; + this_should_compile(sconst); // const function object + this_should_compile(&SConst::f, + sconst); // const member function (void) + EXPECT_EQ(41, + this_should_compile(&SConst::g, sconst)); // ... (non-void) + } + + // temporary lambda + EXPECT_EQ(-8 - 6, this_should_compile([i = -8]() mutable { return --i; })) + << "The lambda was expected to be called six times and decrease " + "its internal counter. This test guarantees that the implementation " + "does not copy the function object."; + + // temporary function objects + this_should_compile(S{}); + EXPECT_EQ(20 - 6, this_should_compile(S2{})); + this_should_compile(SConst{}); + + // member functions and temporary objects + this_should_compile(&S::f, S{}); + EXPECT_EQ(7 + 6, this_should_compile(&S::g, S{})); + this_should_compile(&SConst::f, SConst{}); + EXPECT_EQ(41, this_should_compile(&SConst::g, SConst{})); +} + +TEST_F(ProcessLibGraphApply, SingleTupleSingleOutputArgument) +{ + auto check = [](DataSingleTuple const& args) + { + EXPECT_EQ(get<int>(args.init) + 1, get<int>(args.t)); + EXPECT_EQ(get<double>(args.init), get<double>(args.t)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble>(args.t)); + }; + + for (auto& runner : getRunnersSIncrementSingleTuple<DataSingleTuple>()) + { + DataSingleTuple args; + runner(args); + check(args); + } +} + +TEST_F(ProcessLibGraphApply, SingleTupleSingleArgumentAndReturnValue) +{ + auto check = [](DataSingleTuple const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t)); + EXPECT_EQ(get<double>(args.init), get<double>(args.t)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble>(args.t)); + }; + + for (auto& runner : getRunnersSIdentitySingleTuple<DataSingleTuple>()) + { + DataSingleTuple args; + + EXPECT_EQ(get<double>(args.init), runner(args)); + + check(args); + } +} + +TEST_F(ProcessLibGraphApply, SingleTupleSingleArgumentAndReturnReference) +{ + auto check = [](DataSingleTuple const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t)); + EXPECT_EQ(get<double>(args.init), get<double>(args.t)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble>(args.t)); + }; + + auto lambda = [](double& d) -> double& { return d; }; + auto lambda_capture = [i = 1.0](double& d) -> double& + { + d *= i; + return d; + }; + auto lambda_mut = [](double& d) mutable -> double& { return d; }; + auto lambda_capture_mut = [i = 1.0](double& d) mutable -> double& + { + d *= i; + return d; + }; + + struct S + { + double& f(double& d) { return d; } + double& operator()(double& d) { return d; } + }; + + struct SConst + { + double& f(double& d) const { return d; } + double& operator()(double& d) const { return d; } + }; + + std::function<double&(DataSingleTuple&)> runners[] = { + [&lambda](DataSingleTuple& args) -> double& + { return PGD::applyImpl(lambda, args.t); }, + // --- + [&lambda_capture](DataSingleTuple& args) -> double& + { return PGD::applyImpl(lambda_capture, args.t); }, + // --- + [&lambda_mut](DataSingleTuple& args) -> double& + { return PGD::applyImpl(lambda_mut, args.t); }, + // --- + [&lambda_capture_mut](DataSingleTuple& args) -> double& + { return PGD::applyImpl(lambda_capture_mut, args.t); }, + // --- + [](DataSingleTuple& args) -> double& + { + S s; + return PGD::applyImpl(s, args.t); + }, + // --- + [](DataSingleTuple& args) -> double& + { + S s; + return PGD::applyImpl(&S::f, s, args.t); + }, + // --- + [](DataSingleTuple& args) -> double& + { + SConst s; + return PGD::applyImpl(s, args.t); + }, + // --- + [](DataSingleTuple& args) -> double& + { + SConst s; + return PGD::applyImpl(&SConst::f, s, args.t); + }}; + + for (auto& runner : runners) + { + DataSingleTuple args; + + auto& res = runner(args); + EXPECT_EQ(get<double>(args.init), res); + + check(args); + + // check that the returned reference points to the right data + EXPECT_EQ(&get<double>(args.t), &res); + EXPECT_NE(31, res) + << "We want to modify res to that value in the next step."; + res = 31; + EXPECT_EQ(31, get<double>(args.t)); + } +} + +TEST_F(ProcessLibGraphApply, SingleTupleInputAndOutputArgument) +{ + auto check = [](DataSingleTuple const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t)); + EXPECT_EQ(get<double>(args.init) + *get<SInt>(args.init), + get<double>(args.t)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble>(args.t)); + }; + + for (auto& runner : + getRunnersSAddSecondToFirstSingleTuple<DataSingleTuple>()) + { + DataSingleTuple args; + runner(args); + check(args); + } +} + +TEST_F(ProcessLibGraphApply, SingleTupleMultipleArguments) +{ + auto check = [](DataSingleTuple const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t)); + EXPECT_EQ(get<double>(args.init), get<double>(args.t)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init) - get<int>(args.init) + + get<double>(args.init) + *get<SInt>(args.init), + *get<SDouble>(args.t)); + }; + + for (auto& runner : + getRunnersSMultipleArgumentsSingleTuple<DataSingleTuple>()) + { + DataSingleTuple args; + + EXPECT_EQ(2 * get<int>(args.init), runner(args)); + + check(args); + } +} + +TEST_F(ProcessLibGraphApply, SingleTupleWithReferencesSingleOutputArgument) +{ + auto check = [](DataSingleTupleWithReferences const& args) + { + EXPECT_EQ(get<int>(args.init) + 1, get<int>(args.t)); + EXPECT_EQ(get<double>(args.init), get<double&>(args.t)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const&>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble&>(args.t)); + }; + + for (auto& runner : + getRunnersSIncrementSingleTuple<DataSingleTupleWithReferences>()) + { + DataSingleTupleWithReferences args; + runner(args); + check(args); + } +} + +TEST_F(ProcessLibGraphApply, + SingleTupleWithReferencesSingleArgumentAndReturnValue) +{ + auto check = [](DataSingleTupleWithReferences const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t)); + EXPECT_EQ(get<double>(args.init), get<double&>(args.t)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const&>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble&>(args.t)); + }; + + for (auto& runner : + getRunnersSIdentitySingleTuple<DataSingleTupleWithReferences>()) + { + DataSingleTupleWithReferences args; + + EXPECT_EQ(get<double>(args.init), runner(args)); + + check(args); + } +} + +TEST_F(ProcessLibGraphApply, SingleTupleWithReferencesInputAndOutputArgument) +{ + auto check = [](DataSingleTupleWithReferences const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t)); + EXPECT_EQ(get<double>(args.init) + *get<SInt>(args.init), + get<double&>(args.t)); + EXPECT_EQ(get<double>(args.init) + *get<SInt>(args.init), args.d) + << "The original variable has not been modified correctly."; + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const&>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble&>(args.t)); + }; + + for (auto& runner : getRunnersSAddSecondToFirstSingleTuple< + DataSingleTupleWithReferences>()) + { + DataSingleTupleWithReferences args; + runner(args); + check(args); + } +} + +TEST_F(ProcessLibGraphApply, SingleTupleWithReferencesMultipleArguments) +{ + auto check = [](DataSingleTupleWithReferences const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t)); + EXPECT_EQ(get<double>(args.init), get<double&>(args.t)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const&>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init) - get<int>(args.init) + + get<double>(args.init) + *get<SInt>(args.init), + *get<SDouble&>(args.t)); + EXPECT_EQ(*get<SDouble>(args.init) - get<int>(args.init) + + get<double>(args.init) + *get<SInt>(args.init), + *args.sd) + << "The original variable has not been modified correctly."; + }; + + for (auto& runner : getRunnersSMultipleArgumentsSingleTuple< + DataSingleTupleWithReferences>()) + { + DataSingleTupleWithReferences args; + + EXPECT_EQ(2 * get<int>(args.init), runner(args)); + + check(args); + } +} + +TEST_F(ProcessLibGraphApply, MultipleTuplesSingleOutputArgument) +{ + auto check = [](DataMultipleTuples const& args) + { + EXPECT_EQ(get<int>(args.init) + 1, get<int>(args.t3)); + EXPECT_EQ(get<double>(args.init), get<double&>(args.t3)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const&>(args.t2)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble&>(args.t1)); + EXPECT_EQ(get<std::string>(args.init), get<std::string>(args.t5)); + }; + + std::function<void(DataMultipleTuples&)> runners[] = { + [](DataMultipleTuples& args) + { + PGD::applyImpl( + lambda_increment, args.t1, args.t2, args.t3, args.t4, args.t5); + }, + // --- + [](DataMultipleTuples& args) + { + SIncrementF const s; + PGD::applyImpl(&SIncrementF::f, + s, + args.t1, + args.t2, + args.t3, + args.t4, + args.t5); + }, + // --- + [](DataMultipleTuples& args) { + PG::apply( + lambda_increment, args.t1, args.t2, args.t3, args.t4, args.t5); + }, + // --- + [](DataMultipleTuples& args) + { + SIncrementEval const s; + PG::eval(s, args.t1, args.t2, args.t3, args.t4, args.t5); + }}; + + for (auto& runner : runners) + { + DataMultipleTuples args; + runner(args); + check(args); + } +} + +TEST_F(ProcessLibGraphApply, MultipleTuplesSingleArgumentAndReturnValue) +{ + auto check = [](DataMultipleTuples const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t3)); + EXPECT_EQ(get<double>(args.init), get<double&>(args.t3)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const&>(args.t2)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble&>(args.t1)); + EXPECT_EQ(get<std::string>(args.init), get<std::string>(args.t5)); + }; + + std::function<double(DataMultipleTuples&)> runners[] = { + [](DataMultipleTuples& args) + { + return PGD::applyImpl( + SIdentity{}, args.t2, args.t3, args.t4, args.t1, args.t5); + }, + // --- + [](DataMultipleTuples& args) + { + SIdentity s; + return PGD::applyImpl( + &SIdentity::f, s, args.t2, args.t3, args.t4, args.t1, args.t5); + }, + // --- + [](DataMultipleTuples& args) + { + SIdentity s; + return PG::apply(s, args.t2, args.t3, args.t4, args.t1, args.t5); + }, + // --- + [](DataMultipleTuples& args) + { + SIdentity s; + return PG::eval(s, args.t2, args.t3, args.t4, args.t1, args.t5); + }}; + + for (auto& runner : runners) + { + DataMultipleTuples args; + + EXPECT_EQ(get<double>(args.init), runner(args)); + + check(args); + } +} + +TEST_F(ProcessLibGraphApply, MultipleTuplesInputAndOutputArgument) +{ + auto check = [](DataMultipleTuples const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t3)); + EXPECT_EQ(get<double>(args.init) + *get<SInt>(args.init), + get<double&>(args.t3)); + EXPECT_EQ(get<double>(args.init) + *get<SInt>(args.init), args.d) + << "The original variable has not been modified correctly."; + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const&>(args.t2)); + EXPECT_EQ(*get<SDouble>(args.init), *get<SDouble&>(args.t1)); + EXPECT_EQ(get<std::string>(args.init), get<std::string>(args.t5)); + }; + + std::function<void(DataMultipleTuples&)> runners[] = { + [](DataMultipleTuples& args) + { + SAddSecondToFirstF const s; + PGD::applyImpl(&SAddSecondToFirstF::f, + s, + args.t5, + args.t4, + args.t3, + args.t2, + args.t1); + }, + // --- + [](DataMultipleTuples& args) + { + SAddSecondToFirstFunctionObject const s; + PGD::applyImpl(s, args.t5, args.t4, args.t3, args.t2, args.t1); + }, + // --- + [](DataMultipleTuples& args) + { + SAddSecondToFirstFunctionObject const s; + PG::apply(s, args.t5, args.t4, args.t3, args.t2, args.t1); + }, + // --- + [](DataMultipleTuples& args) + { + SAddSecondToFirstEval const s; + PG::eval(s, args.t5, args.t4, args.t3, args.t2, args.t1); + }}; + + for (auto& runner : runners) + { + DataMultipleTuples args; + runner(args); + check(args); + } +} + +TEST_F(ProcessLibGraphApply, MultipleTuplesMultipleArguments) +{ + auto check = [](DataMultipleTuples const& args) + { + EXPECT_EQ(get<int>(args.init), get<int>(args.t3)); + EXPECT_EQ(get<double>(args.init), get<double&>(args.t3)); + EXPECT_EQ(*get<SInt>(args.init), *get<SInt const&>(args.t2)); + EXPECT_EQ(*get<SDouble>(args.init) - get<int>(args.init) + + get<double>(args.init) + *get<SInt>(args.init), + *get<SDouble&>(args.t1)); + EXPECT_EQ(*get<SDouble>(args.init) - get<int>(args.init) + + get<double>(args.init) + *get<SInt>(args.init), + *args.sd) + << "The original variable has not been modified correctly."; + EXPECT_EQ(get<std::string>(args.init), get<std::string>(args.t5)); + }; + + std::function<int(DataMultipleTuples&)> runners[] = { + [](DataMultipleTuples& args) + { + SMultipleArguments s; + return PGD::applyImpl(&SMultipleArguments::f, + s, + args.t1, + args.t5, + args.t2, + args.t3, + args.t4); + }, + // --- + [](DataMultipleTuples& args) + { + return PGD::applyImpl(SMultipleArguments{}, + args.t1, + args.t5, + args.t2, + args.t3, + args.t4); + }, + // --- + [](DataMultipleTuples& args) + { + SMultipleArguments s; + return PG::apply(s, args.t1, args.t5, args.t2, args.t3, args.t4); + }, + // --- + [](DataMultipleTuples& args) + { + SMultipleArguments s; + return PG::eval(s, args.t1, args.t5, args.t2, args.t3, args.t4); + }}; + + for (auto& runner : runners) + { + DataMultipleTuples args; + + EXPECT_EQ(2 * get<int>(args.init), runner(args)); + + check(args); + } +}