Skip to content

Commit 7d4bb7c

Browse files
committed
feat(hooks): implement throw policies for hook execution and enhance error handling
1 parent e9896b4 commit 7d4bb7c

File tree

10 files changed

+236
-75
lines changed

10 files changed

+236
-75
lines changed

cucumber_cpp/library/engine/HookExecutor.cpp

Lines changed: 36 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -24,14 +24,46 @@ namespace cucumber_cpp::library::engine
2424
using runtime_error::runtime_error;
2525
};
2626

27-
void ExecuteHook(cucumber_cpp::library::engine::RunnerContext& runnerContext, HookType hook, const std::set<std::string, std::less<>>& tags)
27+
struct ThrowPolicy
28+
{
29+
protected:
30+
~ThrowPolicy() = default;
31+
32+
public:
33+
virtual void Throw() const = 0;
34+
};
35+
36+
struct ThrowExceptionPolicy : ThrowPolicy
37+
{
38+
ThrowExceptionPolicy(std::string message)
39+
: message{ message }
40+
{}
41+
42+
void Throw() const override
43+
{
44+
throw HookFailed{ message };
45+
}
46+
47+
private:
48+
std::string message;
49+
};
50+
51+
struct NoThrowExceptionPolicy : ThrowPolicy
52+
{
53+
void Throw() const override
54+
{
55+
// No exception thrown
56+
}
57+
};
58+
59+
void ExecuteHook(cucumber_cpp::library::engine::RunnerContext& runnerContext, HookType hook, const std::set<std::string, std::less<>>& tags, const ThrowPolicy& throwPolicy)
2860
{
2961
for (const auto& match : HookRegistry::Instance().Query(hook, tags))
3062
{
3163
match.factory(runnerContext)->Execute();
3264

3365
if (runnerContext.ExecutionStatus() != cucumber_cpp::library::engine::Result::passed)
34-
throw HookFailed{ "hook failed" };
66+
throwPolicy.Throw();
3567
}
3668
}
3769
}
@@ -41,31 +73,12 @@ namespace cucumber_cpp::library::engine
4173
, hookPair{ hookPair }
4274
, tags{ tags }
4375
{
44-
try
45-
{
46-
ExecuteHook(runnerContext, hookPair.before, tags);
47-
}
48-
catch (HookFailed&)
49-
{
50-
/* This throw ensures no tests are executed in and below the current scope. */
51-
throw HookFailed{ "Error during BEFORE_x_HOOK" };
52-
}
76+
ExecuteHook(runnerContext, hookPair.before, tags, ThrowExceptionPolicy{ "Hook failed" });
5377
}
5478

5579
HookExecutor::ScopedHook::~ScopedHook()
5680
{
57-
try
58-
{
59-
ExecuteHook(runnerContext, hookPair.after, tags);
60-
}
61-
catch (HookFailed&)
62-
{
63-
/*
64-
Can't throw from a destructor. Error state will be handled by calling functions.
65-
66-
Errors during AFTER_ALL hook is not a problem, because no other hooks will be executed.
67-
*/
68-
}
81+
ExecuteHook(runnerContext, hookPair.after, tags, NoThrowExceptionPolicy{});
6982
}
7083

7184
HookExecutor::ProgramScope::ProgramScope(cucumber_cpp::library::engine::ContextManager& contextManager)

cucumber_cpp/library/engine/TestRunner.cpp

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,17 @@ namespace cucumber_cpp::library::engine
3636
{
3737
}
3838

39-
void TestRunnerImpl::Run(const std::vector<std::unique_ptr<FeatureInfo>>& feature)
39+
void TestRunnerImpl::Run(const std::vector<std::unique_ptr<FeatureInfo>>& features)
4040
{
41-
auto run = [this, &feature]
41+
auto runFeatures = [this, &features]
4242
{
4343
auto scope = testExecution.StartRun();
4444

45-
for (const auto& featurePtr : feature)
45+
for (const auto& featurePtr : features)
4646
RunFeature(*featurePtr);
4747
};
4848

49-
ASSERT_NO_THROW(run());
49+
ASSERT_NO_THROW(runFeatures());
5050
}
5151

5252
void TestRunnerImpl::NestedStep(StepType type, std::string step)
@@ -57,7 +57,7 @@ namespace cucumber_cpp::library::engine
5757

5858
void TestRunnerImpl::RunFeature(const FeatureInfo& feature)
5959
{
60-
auto run = [this, &feature]
60+
auto runFeature = [this, &feature]
6161
{
6262
if (feature.Rules().empty() && feature.Scenarios().empty())
6363
return;
@@ -67,18 +67,18 @@ namespace cucumber_cpp::library::engine
6767
RunRules(feature.Rules());
6868
RunScenarios(feature.Scenarios());
6969
};
70-
ASSERT_NO_THROW(run());
70+
ASSERT_NO_THROW(runFeature());
7171
}
7272

7373
void TestRunnerImpl::RunRule(const RuleInfo& rule)
7474
{
75-
auto run = [this, &rule]
75+
auto runRule = [this, &rule]
7676
{
7777
const auto ruleScope = testExecution.StartRule(rule);
7878

7979
RunScenarios(rule.Scenarios());
8080
};
81-
ASSERT_NO_THROW(run());
81+
ASSERT_NO_THROW(runRule());
8282
}
8383

8484
void TestRunnerImpl::RunRules(const std::vector<std::unique_ptr<RuleInfo>>& rules)
@@ -89,15 +89,15 @@ namespace cucumber_cpp::library::engine
8989

9090
void TestRunnerImpl::RunScenario(const ScenarioInfo& scenario)
9191
{
92-
auto run = [this, &scenario]
92+
auto runScenario = [this, &scenario]
9393
{
9494
const auto scenarioScope = testExecution.StartScenario(scenario);
9595

9696
currentScenario = &scenario;
9797

9898
ExecuteSteps(scenario);
9999
};
100-
ASSERT_NO_THROW(run());
100+
ASSERT_NO_THROW(runScenario());
101101
}
102102

103103
void TestRunnerImpl::RunScenarios(const std::vector<std::unique_ptr<ScenarioInfo>>& scenarios)

cucumber_cpp/library/engine/test/TestFailureHandler.cpp

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,9 @@
22
#include "cucumber_cpp/library/engine/FailureHandler.hpp"
33
#include "cucumber_cpp/library/engine/Result.hpp"
44
#include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp"
5-
#include "cucumber_cpp/library/report/Report.hpp"
6-
#include <cstddef>
7-
#include <filesystem>
5+
#include "cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp"
86
#include <gmock/gmock.h>
97
#include <gtest/gtest.h>
10-
#include <optional>
11-
#include <string>
128

139
namespace cucumber_cpp::library::engine
1410
{
@@ -29,20 +25,11 @@ namespace cucumber_cpp::library::engine
2925
CucumberAssertHelper{ testing::TestPartResult::kNonFatalFailure, "custom_file.cpp", 0, failure } = testing::Message() << user;
3026
}
3127

32-
struct ReportForwarderMock : report::ReportForwarderImpl
33-
{
34-
using ReportForwarderImpl::ReportForwarderImpl;
35-
virtual ~ReportForwarderMock() = default;
36-
37-
MOCK_METHOD(void, Failure, (const std::string& error, std::optional<std::filesystem::path> path, std::optional<std::size_t> line, std::optional<std::size_t> column), (override));
38-
MOCK_METHOD(void, Error, (const std::string& error, std::optional<std::filesystem::path> path, std::optional<std::size_t> line, std::optional<std::size_t> column), (override));
39-
};
40-
4128
struct TestFailureHandler : testing::Test
4229
{
4330

4431
test_helper::ContextManagerInstance contextManager;
45-
ReportForwarderMock reportHandler{ contextManager };
32+
report::test_helper::ReportForwarderMock reportHandler{ contextManager };
4633
TestAssertionHandlerImpl testAssertionHandler{ contextManager, reportHandler };
4734
};
4835
}
Lines changed: 40 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
1+
#include "cucumber_cpp/library/engine/FailureHandler.hpp"
12
#include "cucumber_cpp/library/engine/HookExecutor.hpp"
23
#include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp"
4+
#include "cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp"
5+
#include <functional>
36
#include <gtest/gtest.h>
7+
#include <optional>
8+
#include <set>
9+
#include <string>
10+
#include <utility>
411

512
namespace cucumber_cpp::library::engine
613
{
714
struct TestHookExecutor : testing::Test
815
{
9-
test_helper::ContextManagerInstance contextManagerInstance;
10-
HookExecutorImpl hookExecutor{ contextManagerInstance };
16+
std::optional<test_helper::ContextManagerInstance> contextManagerInstance{ std::in_place };
17+
std::optional<HookExecutorImpl> hookExecutor{ *contextManagerInstance };
1118
};
1219

1320
TEST_F(TestHookExecutor, Construct)
@@ -16,49 +23,60 @@ namespace cucumber_cpp::library::engine
1623

1724
TEST_F(TestHookExecutor, ExecuteProgramHooks)
1825
{
19-
ASSERT_FALSE(contextManagerInstance.ProgramContext().Contains("hookBeforeAll"));
20-
ASSERT_FALSE(contextManagerInstance.ProgramContext().Contains("hookAfterAll"));
26+
ASSERT_FALSE(contextManagerInstance->ProgramContext().Contains("hookBeforeAll"));
27+
ASSERT_FALSE(contextManagerInstance->ProgramContext().Contains("hookAfterAll"));
2128

2229
{
23-
auto scope = hookExecutor.BeforeAll();
24-
EXPECT_TRUE(contextManagerInstance.ProgramContext().Contains("hookBeforeAll"));
30+
auto scope = hookExecutor->BeforeAll();
31+
EXPECT_TRUE(contextManagerInstance->ProgramContext().Contains("hookBeforeAll"));
2532
}
26-
EXPECT_TRUE(contextManagerInstance.ProgramContext().Contains("hookAfterAll"));
33+
EXPECT_TRUE(contextManagerInstance->ProgramContext().Contains("hookAfterAll"));
2734
}
2835

2936
TEST_F(TestHookExecutor, ExecuteBeforeFeature)
3037
{
31-
ASSERT_FALSE(contextManagerInstance.FeatureContext().Contains("hookBeforeFeature"));
32-
ASSERT_FALSE(contextManagerInstance.FeatureContext().Contains("hookAfterFeature"));
38+
ASSERT_FALSE(contextManagerInstance->FeatureContext().Contains("hookBeforeFeature"));
39+
ASSERT_FALSE(contextManagerInstance->FeatureContext().Contains("hookAfterFeature"));
3340

3441
{
35-
auto scope = hookExecutor.FeatureStart();
36-
EXPECT_TRUE(contextManagerInstance.FeatureContext().Contains("hookBeforeFeature"));
42+
auto scope = hookExecutor->FeatureStart();
43+
EXPECT_TRUE(contextManagerInstance->FeatureContext().Contains("hookBeforeFeature"));
3744
}
38-
EXPECT_TRUE(contextManagerInstance.FeatureContext().Contains("hookAfterFeature"));
45+
EXPECT_TRUE(contextManagerInstance->FeatureContext().Contains("hookAfterFeature"));
3946
}
4047

4148
TEST_F(TestHookExecutor, ExecuteBeforeScenario)
4249
{
43-
ASSERT_FALSE(contextManagerInstance.ScenarioContext().Contains("hookBeforeScenario"));
44-
ASSERT_FALSE(contextManagerInstance.ScenarioContext().Contains("hookAfterScenario"));
50+
ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookBeforeScenario"));
51+
ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookAfterScenario"));
4552

4653
{
47-
auto scope = hookExecutor.ScenarioStart();
48-
EXPECT_TRUE(contextManagerInstance.ScenarioContext().Contains("hookBeforeScenario"));
54+
auto scope = hookExecutor->ScenarioStart();
55+
EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookBeforeScenario"));
4956
}
50-
EXPECT_TRUE(contextManagerInstance.ScenarioContext().Contains("hookAfterScenario"));
57+
EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookAfterScenario"));
5158
}
5259

5360
TEST_F(TestHookExecutor, ExecuteBeforeStep)
5461
{
55-
ASSERT_FALSE(contextManagerInstance.ScenarioContext().Contains("hookBeforeStep"));
56-
ASSERT_FALSE(contextManagerInstance.ScenarioContext().Contains("hookAfterStep"));
62+
ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookBeforeStep"));
63+
ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookAfterStep"));
5764

5865
{
59-
auto scope = hookExecutor.StepStart();
60-
EXPECT_TRUE(contextManagerInstance.ScenarioContext().Contains("hookBeforeStep"));
66+
auto scope = hookExecutor->StepStart();
67+
EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookBeforeStep"));
6168
}
62-
EXPECT_TRUE(contextManagerInstance.ScenarioContext().Contains("hookAfterStep"));
69+
EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookAfterStep"));
70+
}
71+
72+
TEST_F(TestHookExecutor, ThrowOnBeforeHookError)
73+
{
74+
contextManagerInstance.emplace(std::set<std::string, std::less<>>{ "@errorbefore" });
75+
hookExecutor.emplace(*contextManagerInstance);
76+
77+
report::test_helper::ReportForwarderMock reportHandler{ *contextManagerInstance };
78+
TestAssertionHandlerImpl assertionHandler{ *contextManagerInstance, reportHandler };
79+
80+
EXPECT_ANY_THROW(auto scope = hookExecutor->StepStart());
6381
}
6482
}

cucumber_cpp/library/engine/test/TestHookExecutorHooks.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ namespace cucumber_cpp::library::engine
4343
{
4444
context.InsertAt("hookAfterStep", std::string{ "hookAfterStep" });
4545
}
46+
47+
HOOK_BEFORE_STEP("@errorbefore")
48+
{
49+
ASSERT_FALSE(true);
50+
}
4651
}

0 commit comments

Comments
 (0)