diff --git a/examples/Calc/CMakeLists.txt b/examples/Calc/CMakeLists.txt index 7157f3ec..b89adfdb 100644 --- a/examples/Calc/CMakeLists.txt +++ b/examples/Calc/CMakeLists.txt @@ -7,6 +7,14 @@ add_library(Calc src/Calculator) if(GMOCK_FOUND) add_executable(GTestCalculatorSteps features/step_definitions/GTestCalculatorSteps) target_link_libraries(GTestCalculatorSteps Calc ${CUKE_LIBRARIES} ${CUKE_GTEST_LIBRARIES}) + + list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_variadic_templates HAS_VARIADIC_TEMPLATES) + if(HAS_VARIADIC_TEMPLATES GREATER -1) + add_executable(FuncArgsCalculatorSteps features/step_definitions/FuncArgsCalculatorSteps) + target_link_libraries(FuncArgsCalculatorSteps Calc ${CUKE_LIBRARIES} ${CUKE_GTEST_LIBRARIES}) + + target_compile_features(FuncArgsCalculatorSteps PRIVATE cxx_variadic_templates) + endif() endif() if(Boost_UNIT_TEST_FRAMEWORK_FOUND) diff --git a/examples/Calc/features/step_definitions/FuncArgsCalculatorSteps.cpp b/examples/Calc/features/step_definitions/FuncArgsCalculatorSteps.cpp new file mode 100644 index 00000000..3a5cc24c --- /dev/null +++ b/examples/Calc/features/step_definitions/FuncArgsCalculatorSteps.cpp @@ -0,0 +1,35 @@ +#include +#include + +#include + +using cucumber::ScenarioScope; + +struct CalcCtx { + Calculator calc; + double result; +}; + +GIVEN("^I have entered (\\d+) into the calculator$", (const double n)) { + ScenarioScope context; + + context->calc.push(n); +} + +WHEN("^I press add") { + ScenarioScope context; + + context->result = context->calc.add(); +} + +WHEN("^I press divide") { + ScenarioScope context; + + context->result = context->calc.divide(); +} + +THEN("^the result should be (.*) on the screen$", (const double expected)) { + ScenarioScope context; + + EXPECT_EQ(expected, context->result); +} diff --git a/include/cucumber-cpp/internal/RegistrationMacros.hpp b/include/cucumber-cpp/internal/RegistrationMacros.hpp index ac72b89a..548e814e 100644 --- a/include/cucumber-cpp/internal/RegistrationMacros.hpp +++ b/include/cucumber-cpp/internal/RegistrationMacros.hpp @@ -21,15 +21,18 @@ // ************** CUKE OBJECTS ************** // // ************************************************************************** // -#define CUKE_OBJECT_(class_name, parent_class, registration_fn) \ +#define CUKE_OBJECT_(class_name, parent_class, registration_fn, args) \ class class_name : public parent_class { \ public: \ - void body(); \ + void body() { \ + return invokeWithArgs(*this, &class_name::bodyWithArgs); \ + } \ + void bodyWithArgs args; \ private: \ static const int cukeRegId; \ }; \ const int class_name ::cukeRegId = registration_fn ; \ -void class_name ::body() \ +void class_name ::bodyWithArgs args \ /**/ #endif /* CUKE_REGISTRATIONMACROS_HPP_ */ diff --git a/include/cucumber-cpp/internal/hook/HookMacros.hpp b/include/cucumber-cpp/internal/hook/HookMacros.hpp index c285e9ec..cc89408b 100644 --- a/include/cucumber-cpp/internal/hook/HookMacros.hpp +++ b/include/cucumber-cpp/internal/hook/HookMacros.hpp @@ -11,12 +11,13 @@ BEFORE_WITH_NAME_(CUKE_GEN_OBJECT_NAME_, "" #__VA_ARGS__) \ /**/ -#define BEFORE_WITH_NAME_(step_name, tag_expression) \ -CUKE_OBJECT_( \ - step_name, \ - ::cucumber::internal::BeforeHook, \ - BEFORE_HOOK_REGISTRATION_(step_name, tag_expression) \ -) \ +#define BEFORE_WITH_NAME_(step_name, tag_expression) \ +CUKE_OBJECT_( \ + step_name, \ + ::cucumber::internal::BeforeHook, \ + BEFORE_HOOK_REGISTRATION_(step_name, tag_expression), \ + () \ +) \ /**/ #define BEFORE_HOOK_REGISTRATION_(step_name, tag_expression) \ @@ -31,12 +32,13 @@ ::cucumber::internal::registerBeforeHook(tag_expression) \ AROUND_STEP_WITH_NAME_(CUKE_GEN_OBJECT_NAME_, "" #__VA_ARGS__) \ /**/ -#define AROUND_STEP_WITH_NAME_(step_name, tag_expression) \ -CUKE_OBJECT_( \ - step_name, \ - ::cucumber::internal::AroundStepHook, \ - AROUND_STEP_HOOK_REGISTRATION_(step_name, tag_expression) \ -) \ +#define AROUND_STEP_WITH_NAME_(step_name, tag_expression) \ +CUKE_OBJECT_( \ + step_name, \ + ::cucumber::internal::AroundStepHook, \ + AROUND_STEP_HOOK_REGISTRATION_(step_name, tag_expression), \ + () \ +) \ /**/ #define AROUND_STEP_HOOK_REGISTRATION_(step_name, tag_expression) \ @@ -51,12 +53,13 @@ ::cucumber::internal::registerAroundStepHook(tag_expression) \ AFTER_STEP_WITH_NAME_(CUKE_GEN_OBJECT_NAME_, "" #__VA_ARGS__) \ /**/ -#define AFTER_STEP_WITH_NAME_(step_name, tag_expression) \ -CUKE_OBJECT_( \ - step_name, \ - ::cucumber::internal::AfterStepHook, \ - AFTER_STEP_HOOK_REGISTRATION_(step_name, tag_expression) \ -) \ +#define AFTER_STEP_WITH_NAME_(step_name, tag_expression) \ +CUKE_OBJECT_( \ + step_name, \ + ::cucumber::internal::AfterStepHook, \ + AFTER_STEP_HOOK_REGISTRATION_(step_name, tag_expression), \ + () \ +) \ /**/ #define AFTER_STEP_HOOK_REGISTRATION_(step_name, tag_expression) \ @@ -72,12 +75,13 @@ ::cucumber::internal::registerAfterStepHook(tag_expression) \ AFTER_WITH_NAME_(CUKE_GEN_OBJECT_NAME_, "" #__VA_ARGS__) \ /**/ -#define AFTER_WITH_NAME_(step_name, tag_expression) \ -CUKE_OBJECT_( \ - step_name, \ - ::cucumber::internal::AfterHook, \ - AFTER_HOOK_REGISTRATION_(step_name, tag_expression) \ -) \ +#define AFTER_WITH_NAME_(step_name, tag_expression) \ +CUKE_OBJECT_( \ + step_name, \ + ::cucumber::internal::AfterHook, \ + AFTER_HOOK_REGISTRATION_(step_name, tag_expression), \ + () \ +) \ /**/ #define AFTER_HOOK_REGISTRATION_(step_name, tag_expression) \ @@ -96,7 +100,8 @@ BEFORE_ALL_WITH_NAME_(CUKE_GEN_OBJECT_NAME_) \ CUKE_OBJECT_( \ step_name, \ ::cucumber::internal::BeforeAllHook, \ - BEFORE_ALL_HOOK_REGISTRATION_(step_name) \ + BEFORE_ALL_HOOK_REGISTRATION_(step_name), \ + () \ ) \ /**/ @@ -116,7 +121,8 @@ AFTER_ALL_WITH_NAME_(CUKE_GEN_OBJECT_NAME_) \ CUKE_OBJECT_( \ step_name, \ ::cucumber::internal::AfterAllHook, \ - AFTER_ALL_HOOK_REGISTRATION_(step_name) \ + AFTER_ALL_HOOK_REGISTRATION_(step_name), \ + () \ ) \ /**/ diff --git a/include/cucumber-cpp/internal/hook/HookRegistrar.hpp b/include/cucumber-cpp/internal/hook/HookRegistrar.hpp index b0304478..681e88cb 100644 --- a/include/cucumber-cpp/internal/hook/HookRegistrar.hpp +++ b/include/cucumber-cpp/internal/hook/HookRegistrar.hpp @@ -27,6 +27,11 @@ class Hook { virtual void body() = 0; protected: bool tagsMatch(Scenario *scenario); + + template + static R invokeWithArgs(Derived& that, R (Derived::* f)()) { + return (that.*f)(); + } private: shared_ptr tagExpression; }; diff --git a/include/cucumber-cpp/internal/step/StepMacros.hpp b/include/cucumber-cpp/internal/step/StepMacros.hpp index 67489049..58cd5a29 100644 --- a/include/cucumber-cpp/internal/step/StepMacros.hpp +++ b/include/cucumber-cpp/internal/step/StepMacros.hpp @@ -7,16 +7,24 @@ // ************** STEP ************** // // ************************************************************************** // -#define CUKE_STEP_(step_matcher) \ -CUKE_STEP_WITH_NAME_(CUKE_GEN_OBJECT_NAME_, step_matcher) \ +#define CUKE_STEP_GET_MATCHER_(step_matcher, ...) step_matcher +#define CUKE_STEP_GET_ARGS_(step_matcher, args, ...) args + +#define CUKE_STEP_(...) \ +CUKE_STEP_WITH_NAME_( \ + CUKE_GEN_OBJECT_NAME_, \ + CUKE_STEP_GET_MATCHER_(__VA_ARGS__), \ + CUKE_STEP_GET_ARGS_(__VA_ARGS__, ()) \ +) \ /**/ -#define CUKE_STEP_WITH_NAME_(step_name, step_matcher) \ -CUKE_OBJECT_( \ - step_name, \ - STEP_INHERITANCE(step_name), \ - CUKE_STEP_REGISTRATION_(step_name, step_matcher) \ -) \ +#define CUKE_STEP_WITH_NAME_(step_name, step_matcher, args) \ +CUKE_OBJECT_( \ + step_name, \ + STEP_INHERITANCE(step_name), \ + CUKE_STEP_REGISTRATION_(step_name, step_matcher), \ + args \ +) \ /**/ #define CUKE_STEP_REGISTRATION_(step_name, step_matcher) \ diff --git a/include/cucumber-cpp/internal/step/StepManager.hpp b/include/cucumber-cpp/internal/step/StepManager.hpp index acc19c60..6adcccb4 100644 --- a/include/cucumber-cpp/internal/step/StepManager.hpp +++ b/include/cucumber-cpp/internal/step/StepManager.hpp @@ -7,12 +7,18 @@ #include #include +#include #include #include #include using boost::shared_ptr; +#ifndef BOOST_NO_VARIADIC_TEMPLATES + #include +#endif + #include "../Table.hpp" +#include "../utils/IndexSequence.hpp" #include "../utils/Regex.hpp" namespace cucumber { @@ -125,6 +131,31 @@ class BasicStep { template const T getInvokeArg(); const InvokeArgs *getArgs(); + +#ifdef BOOST_NO_VARIADIC_TEMPLATES + // Special case for zero arguments, only thing we bother to support on C++98 + template + static R invokeWithArgs(Derived& that, R (Derived::* f)()) { + return (that.*f)(); + } +#else + template + static R invokeWithIndexedArgs(Derived& that, R (Derived::* f)(Args...), index_sequence) { + return (that.*f)( + that.pArgs->template getInvokeArg::type>(N)... + ); + } + + template + static R invokeWithArgs(Derived& that, R (Derived::* f)(Args...)) { + that.currentArgIndex = sizeof...(Args); + return invokeWithIndexedArgs( + that, + f, + index_sequence_for{}); + } +#endif + private: // FIXME: awful hack because of Boost::Test InvokeResult currentResult; diff --git a/include/cucumber-cpp/internal/utils/IndexSequence.hpp b/include/cucumber-cpp/internal/utils/IndexSequence.hpp new file mode 100644 index 00000000..e7828699 --- /dev/null +++ b/include/cucumber-cpp/internal/utils/IndexSequence.hpp @@ -0,0 +1,60 @@ +#ifndef CUKE_INDEXSEQ_HPP_ +#define CUKE_INDEXSEQ_HPP_ + +#include + +#ifndef BOOST_NO_VARIADIC_TEMPLATES + +#if __cplusplus < 201402L && !(_MSC_VER > 1800) + #include // for std::size_t +#else + #include +#endif + +namespace cucumber { +namespace internal { + +#if __cplusplus < 201402L && !(_MSC_VER > 1800) +// Based on https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence + +template +struct index_sequence { + typedef index_sequence type; +}; + +template +struct concat_index_sequence; + +template +struct concat_index_sequence< + index_sequence + , index_sequence + > : public index_sequence< + N1... + , (sizeof...(N1) + N2)... + > { +}; + +template +struct make_index_sequence : public concat_index_sequence< + typename make_index_sequence< N/2>::type + , typename make_index_sequence::type + > { +}; + +template <> struct make_index_sequence<0> : public index_sequence< > {}; +template <> struct make_index_sequence<1> : public index_sequence<0> {}; + +template +using index_sequence_for = make_index_sequence; +#else +using ::std::index_sequence; +using ::std::index_sequence_for; +#endif + +} +} + +#endif /* !BOOST_NO_VARIADIC_TEMPLATES */ + +#endif /* CUKE_INDEXSEQ_HPP_ */ diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bb038022..25fba729 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -36,6 +36,11 @@ if(GMOCK_FOUND) cuke_add_test(unit/TagTest) cuke_add_driver_test(integration/drivers/GTestDriverTest ${CUKE_GTEST_LIBRARIES}) + + list(FIND CMAKE_CXX_COMPILE_FEATURES cxx_variadic_templates HAS_VARIADIC_TEMPLATES) + if(HAS_VARIADIC_TEMPLATES GREATER -1) + target_compile_features(CukeCommandsTest PRIVATE cxx_variadic_templates) + endif() endif() if(Boost_UNIT_TEST_FRAMEWORK_FOUND) diff --git a/tests/unit/CukeCommandsTest.cpp b/tests/unit/CukeCommandsTest.cpp index 63553aca..7b8f9f6d 100644 --- a/tests/unit/CukeCommandsTest.cpp +++ b/tests/unit/CukeCommandsTest.cpp @@ -3,6 +3,7 @@ #include #include "../utils/CukeCommandsFixture.hpp" +#include #include #include @@ -84,6 +85,34 @@ class CheckAllParametersWithMacro : public CheckAllParameters { } }; +#ifndef BOOST_NO_VARIADIC_TEMPLATES +class CheckAllParametersWithFuncArgs : public CheckAllParameters { +public: +#define ARGS ( \ + const int got_arg_0_int \ + , const double got_arg_1_double \ + , const std::string got_arg_2_string \ + , const std::string& got_arg_3_string_with_spaces \ + ) + void bodyWithArgs ARGS { + EXPECT_EQ(arg_0_int, got_arg_0_int); + EXPECT_EQ(arg_1_double, got_arg_1_double); + EXPECT_EQ(arg_2_string, got_arg_2_string); + EXPECT_EQ(arg_3_string_with_spaces, got_arg_3_string_with_spaces); + } + + void body() { + return invokeWithArgs(*this, &CheckAllParametersWithFuncArgs::bodyWithArgs); + } +#undef ARGS +}; + +TEST_F(CukeCommandsTest, invokeHandlesParametersWithFuncArgs) { + // The real test is in TestClass::body() + runStepBodyTest(); +} +#endif + TEST_F(CukeCommandsTest, matchesCorrectly) { addStepWithMatcher(STATIC_MATCHER); MatchResult result = stepMatches(STATIC_MATCHER); @@ -91,12 +120,12 @@ TEST_F(CukeCommandsTest, matchesCorrectly) { } TEST_F(CukeCommandsTest, invokeHandlesParametersWithoutMacro) { - // The real test is in CheckAllParameters::body() + // The real test is in TestClass::body() runStepBodyTest(); } TEST_F(CukeCommandsTest, invokeHandlesParametersWithMacro) { - // The real test is in CheckAllParameters::body() + // The real test is in TestClass::body() runStepBodyTest(); } diff --git a/travis.sh b/travis.sh index 9279fec2..9c80a126 100755 --- a/travis.sh +++ b/travis.sh @@ -19,15 +19,14 @@ cmake --build build cmake --build build --target test cmake --build build --target features -GTEST=build/examples/Calc/GTestCalculatorSteps -BOOST=build/examples/Calc/BoostCalculatorSteps -if [ -f $GTEST ]; then - $GTEST >/dev/null & - cucumber examples/Calc - wait -fi -if [ -f $BOOST ]; then - $BOOST >/dev/null & - cucumber examples/Calc - wait -fi +for CALC_EXAMPLE in \ + build/examples/Calc/GTestCalculatorSteps \ + build/examples/Calc/BoostCalculatorSteps \ + build/examples/Calc/FuncArgsCalculatorSteps \ +; do + if [ -f "${CALC_EXAMPLE}" ]; then + "${CALC_EXAMPLE}" > /dev/null & + cucumber examples/Calc + wait + fi +done