diff --git a/cucumber_cpp/Steps.hpp b/cucumber_cpp/Steps.hpp index 1db0a855..212ddd39 100644 --- a/cucumber_cpp/Steps.hpp +++ b/cucumber_cpp/Steps.hpp @@ -8,6 +8,7 @@ #include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/engine/Hook.hpp" #include "cucumber_cpp/library/util/DocString.hpp" #include "cucumber_cpp/library/util/Table.hpp" diff --git a/cucumber_cpp/acceptance_test/features/test_nested_steps.feature b/cucumber_cpp/acceptance_test/features/test_nested_steps.feature index d4228606..e91b7029 100644 --- a/cucumber_cpp/acceptance_test/features/test_nested_steps.feature +++ b/cucumber_cpp/acceptance_test/features/test_nested_steps.feature @@ -1,5 +1,10 @@ -@nested_steps Feature: Nested Steps + @nested_steps Scenario: Call other steps from within a step Given a step calls another step with "cucumber" Then the stored string is "cucumber" + + @nested_failing_steps + Scenario: Call other steps from within a step + When a step calls another step that will fail + Then this should be skipped diff --git a/cucumber_cpp/acceptance_test/features/test_step_fixture_failure.feature b/cucumber_cpp/acceptance_test/features/test_step_fixture_failure.feature new file mode 100644 index 00000000..91cee723 --- /dev/null +++ b/cucumber_cpp/acceptance_test/features/test_step_fixture_failure.feature @@ -0,0 +1,7 @@ +@fail_step_fixture +Feature: Simple feature file + Rule: Test rule + Scenario: Test scenario without failing step fixture + Given step fixture does not fail + Scenario: Test with failing step fixture + Given step fixture fails diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index a6cc3119..a5712fdc 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -1,4 +1,4 @@ -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/Steps.hpp" #include "fmt/format.h" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -107,7 +107,7 @@ THEN("the exception is caught") THEN("the next scenario is executed") { - /* do nothing */ + // empty } GIVEN("{int} and {int} are equal", (std::int32_t a, std::int32_t b)) @@ -140,3 +140,30 @@ GIVEN(R"(I attach a link to {string})", (const std::string& url)) { Link(url, "title"); } + +GIVEN(R"(step fixture does not fail)") +{ + // empty +} + +struct FailingStepFixture : cucumber_cpp::StepBase +{ + using StepBase::StepBase; + + bool nonExistentKey = context.Get("nonExistentKey"); +}; + +GIVEN_F(FailingStepFixture, R"(step fixture fails)") +{ + // empty +} + +GIVEN("a nested step that fails") +{ + ASSERT_THAT(false, testing::IsTrue()); +} + +GIVEN("a step calls another step that will fail") +{ + Step("a nested step that fails"); +} diff --git a/cucumber_cpp/acceptance_test/steps/UsedUnused.cpp b/cucumber_cpp/acceptance_test/steps/UsedUnused.cpp index 3abf8193..0d444d51 100644 --- a/cucumber_cpp/acceptance_test/steps/UsedUnused.cpp +++ b/cucumber_cpp/acceptance_test/steps/UsedUnused.cpp @@ -1,4 +1,4 @@ -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/Steps.hpp" STEP("This step is unused") { diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index d7ba7f2f..edf20970 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -222,3 +222,21 @@ teardown() { assert_output --partial "| this step is used | - |" assert_output --partial "| This step is unused | UNUSED |" } + +@test "Test failure in step fixture results in error" { + run $acceptance_test --format summary --format-options "{ \"summary\": {\"theme\":\"plain\"} }" --tags "@fail_step_fixture" -- cucumber_cpp/acceptance_test/features + assert_failure + assert_output --partial "key not found: \"nonExistentKey\"" + assert_output --partial "2 scenarios 1 passed, 1 failed" + assert_output --partial "2 steps 1 passed, 1 failed" +} + +@test "Test nested failures propagate properly" { + run $acceptance_test --format summary --format-options "{ \"summary\": {\"theme\":\"plain\"} }" --tags "@nested_failing_steps" -- cucumber_cpp/acceptance_test/features + assert_failure + assert_output --partial "FAILED nested step: \"* a nested step that fails\"" + assert_output --partial "Value of: false" + assert_output --partial "Expected: is true" + assert_output --partial "Actual: false (of type bool)" + assert_output --partial "↷ Then this should be skipped" +} diff --git a/cucumber_cpp/example/steps/Steps.cpp b/cucumber_cpp/example/steps/Steps.cpp index 76591a60..e061cb33 100644 --- a/cucumber_cpp/example/steps/Steps.cpp +++ b/cucumber_cpp/example/steps/Steps.cpp @@ -1,5 +1,5 @@ #include "cucumber_cpp/library/Steps.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/Steps.hpp" #include "cucumber_cpp/library/Context.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index 6e12e776..7b81f688 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -48,5 +48,5 @@ add_subdirectory(tag_expression) add_subdirectory(util) if (CCR_BUILD_TESTS) - # add_subdirectory(test) + add_subdirectory(test) endif() diff --git a/cucumber_cpp/library/engine/CMakeLists.txt b/cucumber_cpp/library/engine/CMakeLists.txt index 1f22fb74..83a05986 100644 --- a/cucumber_cpp/library/engine/CMakeLists.txt +++ b/cucumber_cpp/library/engine/CMakeLists.txt @@ -24,5 +24,5 @@ target_link_libraries(cucumber_cpp.library.engine PUBLIC if (CCR_BUILD_TESTS) add_subdirectory(test) - # add_subdirectory(test_helper) + add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/engine/test_helper/StepImplementations.cpp b/cucumber_cpp/library/engine/test_helper/StepImplementations.cpp index 11ab5d6e..5fa91397 100644 --- a/cucumber_cpp/library/engine/test_helper/StepImplementations.cpp +++ b/cucumber_cpp/library/engine/test_helper/StepImplementations.cpp @@ -62,7 +62,7 @@ WHEN("I call a nested step") { ASSERT_THAT(context.Contains("nested"), testing::IsFalse()); - Given("I am a nested step"); + Step("I am a nested step"); } THEN("the nested step was called") diff --git a/cucumber_cpp/library/runtime/NestedTestCaseRunner.cpp b/cucumber_cpp/library/runtime/NestedTestCaseRunner.cpp index 08e5a0d7..924ea075 100644 --- a/cucumber_cpp/library/runtime/NestedTestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/NestedTestCaseRunner.cpp @@ -70,9 +70,10 @@ namespace cucumber_cpp::library::runtime return testStep; } - void Invoke(std::size_t nesting, const std::string& step, std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args) + void Invoke(std::size_t nesting, const std::string& step, const util::BodyFactory& bodyFactory, const cucumber::messages::step_match_arguments_list& args) { - const auto status = body->ExecuteAndCatchExceptions(util::StepMatchArgumentsListToExecuteArgs(args)); + const auto status = util::ConstructAndExecute(bodyFactory, util::StepMatchArgumentsListToExecuteArgs(args)); + if (status.status != util::TestStepResultStatus::PASSED) throw util::NestedTestCaseRunnerError{ .nesting = nesting, @@ -88,7 +89,7 @@ namespace cucumber_cpp::library::runtime return supportCodeLibrary.stepRegistry.GetDefinitionById(id); }); - if (testStep.step_definition_ids->size() == 0) + if (testStep.step_definition_ids->empty()) throw util::NestedTestCaseRunnerError{ .nesting = nesting, .status = { .duration = cucumber::messages::duration{}, .status = cucumber::messages::test_step_result_status::UNDEFINED, @@ -105,7 +106,12 @@ namespace cucumber_cpp::library::runtime else { const auto& definition = stepDefinitions.front(); - Invoke(nesting, step, definition.factory(NestedTestCaseRunner{ nesting, supportCodeLibrary, broadcaster, testCaseContext, testStepStarted }, broadcaster, testCaseContext, testStepStarted, util::TransformTable(dataTable), util::TransformDocString(docString)), testStep.step_match_arguments_lists->front()); + NestedTestCaseRunner nestedTestCaseRunner{ nesting, supportCodeLibrary, broadcaster, testCaseContext, testStepStarted }; + const util::BodyFactory bodyFactory = [&nestedTestCaseRunner, &definition, &broadcaster, &testCaseContext, &testStepStarted, &dataTable, &docString] + { + return definition.factory(nestedTestCaseRunner, broadcaster, testCaseContext, testStepStarted, util::TransformTable(dataTable), util::TransformDocString(docString)); + }; + Invoke(nesting, step, bodyFactory, testStep.step_match_arguments_lists->front()); } } } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index 7de3dbf6..30b6d6b9 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -47,9 +47,9 @@ namespace cucumber_cpp::library::runtime { namespace { - cucumber::messages::test_step_result InvokeStep(std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args = {}) + cucumber::messages::test_step_result InvokeStep(const util::BodyFactory& bodyFactory, const cucumber::messages::step_match_arguments_list& args) { - return util::TransformTestStepResult(body->ExecuteAndCatchExceptions(util::StepMatchArgumentsListToExecuteArgs(args))); + return util::TransformTestStepResult(util::ConstructAndExecute(bodyFactory, util::StepMatchArgumentsListToExecuteArgs(args))); } } @@ -159,7 +159,12 @@ namespace cucumber_cpp::library::runtime .status = cucumber::messages::test_step_result_status::SKIPPED, }; - return InvokeStep(hookDefinition.factory(broadcaster, testCaseContext, util::TransformTestStepStarted(testStepStarted), hasError)); + const util::BodyFactory bodyFactory = [&hookDefinition, this, &testCaseContext, &testStepStarted, hasError] + { + return hookDefinition.factory(broadcaster, testCaseContext, util::TransformTestStepStarted(testStepStarted), hasError); + }; + + return InvokeStep(bodyFactory, {}); } std::vector TestCaseRunner::RunStepHooks(const cucumber::messages::pickle_step& /*pickleStep*/, util::HookType hookType, Context& testCaseContext, const cucumber::messages::test_step_started& testStepStarted) @@ -171,7 +176,12 @@ namespace cucumber_cpp::library::runtime for (const auto& id : ids) { const auto& definition = supportCodeLibrary.hookRegistry.GetDefinitionById(id); - results.emplace_back(InvokeStep(definition.factory(broadcaster, testCaseContext, util::TransformTestStepStarted(testStepStarted), false))); + const auto bodyFactory = [&definition, this, &testCaseContext, &testStepStarted] + { + return definition.factory(broadcaster, testCaseContext, util::TransformTestStepStarted(testStepStarted), false); + }; + + results.emplace_back(InvokeStep(bodyFactory, {})); } return results; @@ -221,11 +231,15 @@ namespace cucumber_cpp::library::runtime const auto& docString = pickleStep.argument ? pickleStep.argument->doc_string : std::nullopt; const auto& definition = stepDefinitions.front(); - const auto result = InvokeStep(definition.factory( - NestedTestCaseRunner{ 0, supportCodeLibrary, broadcaster, testCaseContext, util::TransformTestStepStarted(testStepStarted) }, - broadcaster, testCaseContext, util::TransformTestStepStarted(testStepStarted), util::TransformTable(dataTable), util::TransformDocString(docString)), - testStep.step_match_arguments_lists->front()); - stepResults.push_back(result); + + NestedTestCaseRunner nestedTestCaseRunner{ 0, supportCodeLibrary, broadcaster, testCaseContext, util::TransformTestStepStarted(testStepStarted) }; + + const util::BodyFactory bodyFactory = [this, &definition, &nestedTestCaseRunner, &testCaseContext, &testStepStarted, &dataTable, &docString] + { + return definition.factory(nestedTestCaseRunner, broadcaster, testCaseContext, util::TransformTestStepStarted(testStepStarted), util::TransformTable(dataTable), util::TransformDocString(docString)); + }; + + stepResults.push_back(InvokeStep(bodyFactory, testStep.step_match_arguments_lists->front())); } const auto afterStepHookResults = RunStepHooks(pickleStep, util::HookType::afterStep, testCaseContext, testStepStarted); diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index 2729591d..9c591dc8 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -16,6 +16,7 @@ #include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Body.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" #include "cucumber_cpp/library/util/HookData.hpp" @@ -189,9 +190,15 @@ namespace cucumber_cpp::library::runtime broadcaster.BroadcastEvent({ .test_run_hook_started = testRunHookStarted }); cucumber::messages::test_step_result result{ .duration{ .seconds = 0, .nanos = 0 }, .status = cucumber::messages::test_step_result_status::SKIPPED }; + if (!options.dryRun) { - result = util::TransformTestStepResult(definition.factory(broadcaster, context, util::TransformTestRunHookStarted(testRunHookStarted), false)->ExecuteAndCatchExceptions()); + const util::BodyFactory bodyFactory = [&definition, this, &context, &testRunHookStarted] + { + return definition.factory(broadcaster, context, util::TransformTestRunHookStarted(testRunHookStarted), false); + }; + + result = util::TransformTestStepResult(util::ConstructAndExecute(bodyFactory)); if (result.status != cucumber::messages::test_step_result_status::PASSED && options.failGlobalHookFast) throw GlobalHookError{ fmt::format("Global Hook Failed: {}\nresult:{}", util::TransformHookData(definition.data).to_string(), result.to_string()) }; diff --git a/cucumber_cpp/library/test/CMakeLists.txt b/cucumber_cpp/library/test/CMakeLists.txt index 1191772a..85bf0d30 100644 --- a/cucumber_cpp/library/test/CMakeLists.txt +++ b/cucumber_cpp/library/test/CMakeLists.txt @@ -13,5 +13,4 @@ target_link_libraries(cucumber_cpp.library.test PUBLIC target_sources(cucumber_cpp.library.test PRIVATE TestApplication.cpp TestContext.cpp - TestSteps.cpp ) diff --git a/cucumber_cpp/library/test/TestApplication.cpp b/cucumber_cpp/library/test/TestApplication.cpp index f040f65b..532d932d 100644 --- a/cucumber_cpp/library/test/TestApplication.cpp +++ b/cucumber_cpp/library/test/TestApplication.cpp @@ -2,6 +2,7 @@ #include "cucumber_cpp/library/Application.hpp" #include "cucumber_cpp/library/engine/test_helper/TemporaryFile.hpp" #include "gmock/gmock.h" +#include "gtest/gtest.h" #include #include #include @@ -34,41 +35,10 @@ namespace cucumber_cpp::library return capturedStdout; } - TEST_F(TestApplication, RunHelpWithoutArguments) - { - - const std::array args{ "application" }; - - RunWithArgs(args, static_cast>(CLI::ExitCodes::RequiredError)); - } - - TEST_F(TestApplication, RunCommandWithoutArguments) - { - - const std::array args{ "application", "run" }; - - RunWithArgs(args, static_cast>(CLI::ExitCodes::RequiredError)); - } - - TEST_F(TestApplication, RunCommand) - { - - const std::array args{ "application", "run", "--feature", "./", "--report", "console" }; - - RunWithArgs(args, static_cast>(CLI::ExitCodes::Success)); - } - - TEST_F(TestApplication, DryRunCommand) - { - const std::array args{ "application", "run", "--feature", "./", "--report", "console", "--dry" }; - - RunWithArgs(args, static_cast>(CLI::ExitCodes::Success)); - } - TEST_F(TestApplication, InvalidArgument) { - const std::array args{ "application", "run", "--feature", "./", "--report", "console", "--doesntexist" }; + const std::array args{ "application", "--doesntexist" }; RunWithArgs(args, static_cast>(CLI::ExitCodes::ExtrasError)); } @@ -83,11 +53,12 @@ namespace cucumber_cpp::library " Scenario: Test scenario1\n" " Given 5 and 5 are equal\n"; - const std::array args{ "application", "run", "--feature", path.c_str(), "--report", "console", "--dry" }; + const std::array args{ "application", "--format-options", R"({ "summary": {"theme":"plain"} })", "--dry-run", path.c_str() }; std::string stdoutString = RunWithArgs(args, static_cast>(CLI::ExitCodes::Success)); - EXPECT_THAT(stdoutString, testing::HasSubstr("1/1 passed")); + EXPECT_THAT(stdoutString, testing::HasSubstr("1 scenarios 1 skipped")); + EXPECT_THAT(stdoutString, testing::HasSubstr("1 steps 1 skipped")); } TEST_F(TestApplication, RunFeatureFile) @@ -100,44 +71,16 @@ namespace cucumber_cpp::library " Scenario: Test scenario1\n" " Given 5 and 5 are equal\n"; - const std::array args{ "application", "run", "--feature", path.c_str(), "--report", "console" }; + const std::array args{ "application", "--format-options", R"({ "summary": {"theme":"plain"} })", path.c_str() }; std::string stdoutString = RunWithArgs(args, static_cast>(CLI::ExitCodes::Success)); - EXPECT_THAT(stdoutString, testing::HasSubstr("1/1 passed")); + EXPECT_THAT(stdoutString, testing::HasSubstr("1 scenarios 1 passed")); + EXPECT_THAT(stdoutString, testing::HasSubstr("1 steps 1 passed")); } TEST_F(TestApplication, ExposeParameterRegistration) { EXPECT_THAT(&Application{}.ParameterRegistration(), testing::NotNull()); } - - TEST_F(TestApplication, UnusedParameters) - { - auto tmp = engine::test_helper::TemporaryFile{ "tmpfile.feature" }; - const auto path = tmp.Path().string(); - - tmp << "Feature: Test feature\n" - " Rule: Test rule\n" - " Scenario: Test scenario1\n" - " Given 5 and 5 are equal\n" - " And This is a GIVEN step\n" - " And This is a WHEN step\n" - " And This is a THEN step\n" - " And This is a STEP step\n"; - - const std::array args{ "application", "run", "--feature", path.c_str(), "--report", "console", "--unused" }; - - std::string stdoutString = RunWithArgs(args, static_cast>(CLI::ExitCodes::Success)); - - EXPECT_THAT(stdoutString, testing::HasSubstr("The following steps have not been used:")); - EXPECT_THAT(stdoutString, testing::HasSubstr("^This is a step with a ([0-9]+)s delay$")); - EXPECT_THAT(stdoutString, testing::HasSubstr("Step with cucumber expression syntax {float} {string} {int}")); - - EXPECT_THAT(stdoutString, testing::Not(testing::HasSubstr("{int} and {int} are equal"))); - EXPECT_THAT(stdoutString, testing::Not(testing::HasSubstr("And This is a GIVEN step"))); - EXPECT_THAT(stdoutString, testing::Not(testing::HasSubstr("And This is a WHEN step"))); - EXPECT_THAT(stdoutString, testing::Not(testing::HasSubstr("And This is a THEN step"))); - EXPECT_THAT(stdoutString, testing::Not(testing::HasSubstr("And This is a STEP step"))); - } } diff --git a/cucumber_cpp/library/test/TestSteps.cpp b/cucumber_cpp/library/test/TestSteps.cpp deleted file mode 100644 index 4a3c8ea3..00000000 --- a/cucumber_cpp/library/test/TestSteps.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/query/Query.hpp" -#include "cucumber_cpp/library/support/StepRegistry.hpp" -#include "gtest/gtest.h" -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library -{ - struct TestSteps : testing::Test - { - cucumber_expression::ParameterRegistry parameterRegistry{ {} }; - StepRegistry stepRegistry{ parameterRegistry }; - }; - - TEST_F(TestSteps, RegisterThroughPreregistration) - { - EXPECT_THAT(stepRegistry.Size(), testing::Ge(1)); - } - - TEST_F(TestSteps, GetGivenStep) - { - EXPECT_THAT(stepRegistry.Query("This is a GIVEN step").stepRegexStr, testing::StrEq("^This is a GIVEN step$")); - EXPECT_THAT(stepRegistry.Query("This is a WHEN step").stepRegexStr, testing::StrEq("^This is a WHEN step$")); - EXPECT_THAT(stepRegistry.Query("This is a THEN step").stepRegexStr, testing::StrEq("^This is a THEN step$")); - EXPECT_THAT(stepRegistry.Query("This is a STEP step").stepRegexStr, testing::StrEq("^This is a STEP step$")); - } - - TEST_F(TestSteps, GetStepWithMatches) - { - const auto matches = stepRegistry.Query("This is a step with a 10s delay"); - - EXPECT_THAT(matches.stepRegexStr, testing::StrEq("^This is a step with a ([0-9]+)s delay$")); - - EXPECT_THAT(std::get>(matches.matches).size(), testing::Eq(1)); - EXPECT_THAT(std::get>(matches.matches)[0], testing::StrEq("10")); - } - - TEST_F(TestSteps, GetInvalidStep) - { - EXPECT_THROW((void)stepRegistry.Query("This step does not exist"), support::StepRegistry::StepNotFoundError); - } - - TEST_F(TestSteps, GetAmbiguousStep) - { - EXPECT_THROW((void)stepRegistry.Query("an ambiguous step"), support::StepRegistry::AmbiguousStepError); - } - - TEST_F(TestSteps, InvokeTestWithCucumberExpressions) - { - const auto matches = stepRegistry.Query(R"(Step with cucumber expression syntax 1.5 "abcdef" 10)"); - - auto contextStorage{ std::make_shared() }; - Context context{ contextStorage }; - - matches.factory(context, {}, "")->ExecuteAndCatchExceptions(matches.matches); - - EXPECT_THAT(context.Contains("float"), testing::IsTrue()); - EXPECT_THAT(context.Contains("std::string"), testing::IsTrue()); - EXPECT_THAT(context.Contains("std::int32_t"), testing::IsTrue()); - - EXPECT_THAT(context.Get("float"), testing::FloatEq(1.5)); - EXPECT_THAT(context.Get("std::string"), testing::StrEq("abcdef")); - EXPECT_THAT(context.Get("std::int32_t"), testing::Eq(10)); - } - - TEST_F(TestSteps, EscapedCucumberExpression) - { - const auto matchesParens = stepRegistry.Query(R"(An expression with (parenthesis) should remain as is)"); - EXPECT_THAT(matchesParens.stepRegexStr, testing::StrEq(R"(^An expression with \(parenthesis\) should remain as is$)")); - - const auto matchesBrace = stepRegistry.Query(R"(An expression with {braces} should remain as is)"); - EXPECT_THAT(matchesBrace.stepRegexStr, testing::StrEq(R"(^An expression with \{braces\} should remain as is$)")); - } - - TEST_F(TestSteps, FunctionLikeStep) - { - const auto matches = stepRegistry.Query(R"(An expression that looks like a function func(1, 2) should keep its parameters)"); - - auto contextStorage{ std::make_shared() }; - Context context{ contextStorage }; - - matches.factory(context, {}, {})->ExecuteAndCatchExceptions(matches.matches); - } - - TEST_F(TestSteps, EscapedParenthesis) - { - const auto matches1 = stepRegistry.Query(R"(An expression with \(escaped parenthesis\) should keep the slash)"); - const auto matches2 = stepRegistry.Query(R"(An expression with \{escaped braces\} should keep the slash)"); - } -} diff --git a/cucumber_cpp/library/util/Body.cpp b/cucumber_cpp/library/util/Body.cpp index e3f1b35e..943dd8c4 100644 --- a/cucumber_cpp/library/util/Body.cpp +++ b/cucumber_cpp/library/util/Body.cpp @@ -1,19 +1,15 @@ - #include "cucumber_cpp/library/util/Body.hpp" -#include "cucumber/gherkin/demangle.hpp" -#include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/util/Duration.hpp" -#include "cucumber_cpp/library/util/NestedTestCaseRunnerError.hpp" -#include "cucumber_cpp/library/util/TestException.hpp" +#include "cucumber_cpp/library/util/ExecuteAndCatchExceptions.hpp" #include "cucumber_cpp/library/util/TestStepResult.hpp" #include "cucumber_cpp/library/util/TestStepResultStatus.hpp" #include "fmt/format.h" +#include "gtest/gtest-spi.h" #include "gtest/gtest.h" #include -#include #include -#include -#include +#include +#include namespace cucumber_cpp::library::util { @@ -51,69 +47,19 @@ namespace cucumber_cpp::library::util }; } - TestStepResult Body::ExecuteAndCatchExceptions(const ExecuteArgs& args) + TestStepResult ConstructAndExecute(const std::function()>& bodyFactory, const ExecuteArgs& args) { + const auto startTime = Stopwatch::Instance().Start(); TestStepResult testStepResult{ .status = TestStepResultStatus::PASSED }; CucumberResultReporter reportListener{ testStepResult }; - const auto startTime = Stopwatch::Instance().Start(); try { - Execute(args); - } - catch (const util::NestedTestCaseRunnerError& e) - { - testStepResult.status = TestStepResultStatus::FAILED; - - if (e.status.status != cucumber::messages::test_step_result_status::PASSED) - { - const auto offset = std::string(e.nesting, ' '); - - if (e.status.message.has_value()) - testStepResult.message = fmt::format(R"({0} {1} nested step: "* {2}")" - "\n{0} {3}", - offset, - cucumber::messages::to_string(e.status.status), - e.text, - e.status.message.value()); - else - testStepResult.message = fmt::format(R"({0} {1} nested step: "* {2}")", - offset, - cucumber::messages::to_string(e.status.status), - e.text); - } - } - catch (const StepSkipped& e) - { - testStepResult.status = TestStepResultStatus::SKIPPED; - if (!e.message.empty()) - testStepResult.message = e.message; - } - catch (const StepPending& e) - { - testStepResult.status = TestStepResultStatus::PENDING; - if (!e.message.empty()) - testStepResult.message = e.message; - } - catch ([[maybe_unused]] const FatalError& error) - { - testStepResult.status = TestStepResultStatus::FAILED; - } - catch (std::exception& e) - { - testStepResult.status = TestStepResultStatus::FAILED; - testStepResult.exception = TestException{ - .type = cucumber::gherkin::detail::demangle(typeid(e).name()).get(), - .message = e.what(), - }; + bodyFactory()->Execute(args); } catch (...) { - testStepResult.status = TestStepResultStatus::FAILED; - testStepResult.exception = TestException{ - .type = "unknown", - .message = "unknown exception", - }; + HandleErrors(testStepResult); } auto nanoseconds = Stopwatch::Instance().Duration(startTime); diff --git a/cucumber_cpp/library/util/Body.hpp b/cucumber_cpp/library/util/Body.hpp index 4c57b257..130ba47a 100644 --- a/cucumber_cpp/library/util/Body.hpp +++ b/cucumber_cpp/library/util/Body.hpp @@ -4,6 +4,8 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/util/TestStepResult.hpp" #include +#include +#include #include #include #include @@ -50,14 +52,19 @@ namespace cucumber_cpp::library::util using ExecuteArgs = std::vector; + struct Body; + + using BodyFactory = std::function()>; + TestStepResult ConstructAndExecute(const BodyFactory& bodyFactory, const ExecuteArgs& args = {}); + struct Body { virtual ~Body() = default; - TestStepResult ExecuteAndCatchExceptions(const ExecuteArgs& args = {}); - - protected: + private: virtual void Execute(const ExecuteArgs& args) = 0; + + friend TestStepResult ConstructAndExecute(const BodyFactory& bodyFactory, const ExecuteArgs& args); }; } diff --git a/cucumber_cpp/library/util/ExecuteAndCatchExceptions.hpp b/cucumber_cpp/library/util/ExecuteAndCatchExceptions.hpp new file mode 100644 index 00000000..7a8413e3 --- /dev/null +++ b/cucumber_cpp/library/util/ExecuteAndCatchExceptions.hpp @@ -0,0 +1,83 @@ +#ifndef UTIL_EXECUTE_AND_CATCH_EXCEPTIONS_HPP +#define UTIL_EXECUTE_AND_CATCH_EXCEPTIONS_HPP + +#include "cucumber/gherkin/demangle.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/util/Body.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" +#include "cucumber_cpp/library/util/NestedTestCaseRunnerError.hpp" +#include "cucumber_cpp/library/util/TestException.hpp" +#include "cucumber_cpp/library/util/TestStepResult.hpp" +#include "cucumber_cpp/library/util/TestStepResultStatus.hpp" +#include "fmt/format.h" +#include +#include +#include +#include + +namespace cucumber_cpp::library::util +{ + inline void HandleErrors(TestStepResult& testStepResult) + { + try + { + throw; // NOSONAR(cpp:S1039) - HandleErrors is always called from a catch(...) block, so an active exception is guaranteed + } + catch (const util::NestedTestCaseRunnerError& e) + { + testStepResult.status = TestStepResultStatus::FAILED; + + if (e.status.status != cucumber::messages::test_step_result_status::PASSED) + { + const auto offset = std::string(e.nesting, ' '); + + if (e.status.message.has_value()) + testStepResult.message = fmt::format(R"({0} {1} nested step: "* {2}")" + "\n{0} {3}", + offset, + cucumber::messages::to_string(e.status.status), + e.text, + e.status.message.value()); + else + testStepResult.message = fmt::format(R"({0} {1} nested step: "* {2}")", + offset, + cucumber::messages::to_string(e.status.status), + e.text); + } + } + catch (const cucumber_cpp::library::util::StepSkipped& e) + { + testStepResult.status = TestStepResultStatus::SKIPPED; + if (!e.message.empty()) + testStepResult.message = e.message; + } + catch (const StepPending& e) + { + testStepResult.status = TestStepResultStatus::PENDING; + if (!e.message.empty()) + testStepResult.message = e.message; + } + catch ([[maybe_unused]] const FatalError& error) + { + testStepResult.status = TestStepResultStatus::FAILED; + } + catch (const std::exception& e) + { + testStepResult.status = TestStepResultStatus::FAILED; + testStepResult.exception = TestException{ + .type = cucumber::gherkin::detail::demangle(typeid(e).name()).get(), + .message = e.what(), + }; + } + catch (...) + { + testStepResult.status = TestStepResultStatus::FAILED; + testStepResult.exception = TestException{ + .type = "unknown", + .message = "unknown exception", + }; + } + } +} + +#endif diff --git a/external/cucumber/gherkin/CMakeLists.txt b/external/cucumber/gherkin/CMakeLists.txt index da3b0381..9d2843b5 100644 --- a/external/cucumber/gherkin/CMakeLists.txt +++ b/external/cucumber/gherkin/CMakeLists.txt @@ -1,7 +1,7 @@ FetchContent_Declare( cucumber_gherkin GIT_REPOSITORY https://github.com/cucumber/gherkin.git - GIT_TAG 6fe0a3be9df9388209cca37bc3f254be10a6014e # v39.0.0 + GIT_TAG 6f0040fee2783fdb455d345e5bdb50d425515b5d # cpp-remove-cmate-use-native-cmake-and-CPM ) FetchContent_MakeAvailable(cucumber_gherkin) diff --git a/external/cucumber/messages/CMakeLists.txt b/external/cucumber/messages/CMakeLists.txt index 8d6f34ef..30df692c 100644 --- a/external/cucumber/messages/CMakeLists.txt +++ b/external/cucumber/messages/CMakeLists.txt @@ -1,7 +1,7 @@ FetchContent_Declare( cucumber_messages GIT_REPOSITORY https://github.com/cucumber/messages.git - GIT_TAG 6582661d5b1c44a628f8eab65d3aca539f99c4ab # v32.2.0 + GIT_TAG c34b8cf6412f274ac6943075326440b7e6062c81 # refactor-cpp-codegen OVERRIDE_FIND_PACKAGE )