Skip to content

Support taking regex captures as arguments to the step definition's function #159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Aug 18, 2017
Merged
8 changes: 8 additions & 0 deletions examples/Calc/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
#include <gtest/gtest.h>
#include <cucumber-cpp/autodetect.hpp>

#include <Calculator.h>

using cucumber::ScenarioScope;

struct CalcCtx {
Calculator calc;
double result;
};

GIVEN("^I have entered (\\d+) into the calculator$", (const double n)) {
ScenarioScope<CalcCtx> context;

context->calc.push(n);
}

WHEN("^I press add") {
ScenarioScope<CalcCtx> context;

context->result = context->calc.add();
}

WHEN("^I press divide") {
ScenarioScope<CalcCtx> context;

context->result = context->calc.divide();
}

THEN("^the result should be (.*) on the screen$", (const double expected)) {
ScenarioScope<CalcCtx> context;

EXPECT_EQ(expected, context->result);
}
9 changes: 6 additions & 3 deletions include/cucumber-cpp/internal/RegistrationMacros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_ */
58 changes: 32 additions & 26 deletions include/cucumber-cpp/internal/hook/HookMacros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand All @@ -31,12 +32,13 @@ ::cucumber::internal::registerBeforeHook<step_name>(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) \
Expand All @@ -51,12 +53,13 @@ ::cucumber::internal::registerAroundStepHook<step_name>(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) \
Expand All @@ -72,12 +75,13 @@ ::cucumber::internal::registerAfterStepHook<step_name>(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) \
Expand All @@ -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), \
() \
) \
/**/

Expand All @@ -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), \
() \
) \
/**/

Expand Down
5 changes: 5 additions & 0 deletions include/cucumber-cpp/internal/hook/HookRegistrar.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ class Hook {
virtual void body() = 0;
protected:
bool tagsMatch(Scenario *scenario);

template <typename Derived, typename R>
static R invokeWithArgs(Derived& that, R (Derived::* f)()) {
return (that.*f)();
}
private:
shared_ptr<TagExpression> tagExpression;
};
Expand Down
24 changes: 16 additions & 8 deletions include/cucumber-cpp/internal/step/StepMacros.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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) \
Expand Down
31 changes: 31 additions & 0 deletions include/cucumber-cpp/internal/step/StepManager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,18 @@
#include <string>
#include <vector>

#include <boost/config.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>
using boost::shared_ptr;

#ifndef BOOST_NO_VARIADIC_TEMPLATES
#include <type_traits>
#endif

#include "../Table.hpp"
#include "../utils/IndexSequence.hpp"
#include "../utils/Regex.hpp"

namespace cucumber {
Expand Down Expand Up @@ -125,6 +131,31 @@ class BasicStep {

template<class T> 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 <typename Derived, typename R>
static R invokeWithArgs(Derived& that, R (Derived::* f)()) {
return (that.*f)();
}
#else
template <typename Derived, typename R, typename... Args, std::size_t... N>
static R invokeWithIndexedArgs(Derived& that, R (Derived::* f)(Args...), index_sequence<N...>) {
return (that.*f)(
that.pArgs->template getInvokeArg<typename std::decay<Args>::type>(N)...
);
}

template <typename Derived, typename R, typename... Args>
static R invokeWithArgs(Derived& that, R (Derived::* f)(Args...)) {
that.currentArgIndex = sizeof...(Args);
return invokeWithIndexedArgs(
that,
f,
index_sequence_for<Args...>{});
}
#endif

private:
// FIXME: awful hack because of Boost::Test
InvokeResult currentResult;
Expand Down
60 changes: 60 additions & 0 deletions include/cucumber-cpp/internal/utils/IndexSequence.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#ifndef CUKE_INDEXSEQ_HPP_
#define CUKE_INDEXSEQ_HPP_

#include <boost/config.hpp>

#ifndef BOOST_NO_VARIADIC_TEMPLATES

#if __cplusplus < 201402L && !(_MSC_VER > 1800)
#include <cstddef> // for std::size_t
#else
#include <utility>
#endif

namespace cucumber {
namespace internal {

#if __cplusplus < 201402L && !(_MSC_VER > 1800)
// Based on https://stackoverflow.com/questions/17424477/implementation-c14-make-integer-sequence

template <std::size_t... N>
struct index_sequence {
typedef index_sequence type;
};

template <typename Seq1, typename Seq2>
struct concat_index_sequence;

template <std::size_t... N1, std::size_t... N2>
struct concat_index_sequence<
index_sequence<N1...>
, index_sequence<N2...>
> : public index_sequence<
N1...
, (sizeof...(N1) + N2)...
> {
};

template <std::size_t N>
struct make_index_sequence : public concat_index_sequence<
typename make_index_sequence< N/2>::type
, typename make_index_sequence<N-N/2>::type
> {
};

template <> struct make_index_sequence<0> : public index_sequence< > {};
template <> struct make_index_sequence<1> : public index_sequence<0> {};

template <typename... Ts>
using index_sequence_for = make_index_sequence<sizeof...(Ts)>;
#else
using ::std::index_sequence;
using ::std::index_sequence_for;
#endif

}
}

#endif /* !BOOST_NO_VARIADIC_TEMPLATES */

#endif /* CUKE_INDEXSEQ_HPP_ */
5 changes: 5 additions & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
33 changes: 31 additions & 2 deletions tests/unit/CukeCommandsTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <cucumber-cpp/internal/step/StepMacros.hpp>
#include "../utils/CukeCommandsFixture.hpp"

#include <boost/config.hpp>
#include <boost/make_shared.hpp>
#include <boost/shared_ptr.hpp>

Expand Down Expand Up @@ -84,19 +85,47 @@ 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<CheckAllParametersWithFuncArgs>();
}
#endif

TEST_F(CukeCommandsTest, matchesCorrectly) {
addStepWithMatcher(STATIC_MATCHER);
MatchResult result = stepMatches(STATIC_MATCHER);
EXPECT_EQ(stepInfoPtr->id, result.getResultSet().at(0).stepInfo->id);
}

TEST_F(CukeCommandsTest, invokeHandlesParametersWithoutMacro) {
// The real test is in CheckAllParameters::body()
// The real test is in TestClass::body()
runStepBodyTest<CheckAllParametersWithoutMacro>();
}

TEST_F(CukeCommandsTest, invokeHandlesParametersWithMacro) {
// The real test is in CheckAllParameters::body()
// The real test is in TestClass::body()
runStepBodyTest<CheckAllParametersWithMacro>();
}

Expand Down
Loading