Skip to content

Commit 87f451a

Browse files
committed
refactor hook execution error handling to allow skipping tests
1 parent 227e678 commit 87f451a

File tree

13 files changed

+112
-158
lines changed

13 files changed

+112
-158
lines changed

cucumber_cpp/acceptance_test/MainCustom.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ int main(int argc, char** argv)
55
cucumber_cpp::Application application{};
66

77
application.CliParser().add_flag("--required", *application.ProgramContext().EmplaceAt<bool>("--required"))->required();
8+
application.CliParser().add_flag("--failprogramhook", *application.ProgramContext().EmplaceAt<bool>("--failprogramhook"));
89

910
return application.Run(argc, argv);
1011
}

cucumber_cpp/acceptance_test/hooks/Hooks.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
HOOK_BEFORE_ALL()
77
{
88
std::cout << "HOOK_BEFORE_ALL\n";
9+
10+
if (context.Contains("--failprogramhook") && context.Get<bool>("--failprogramhook"))
11+
ASSERT_THAT(false, testing::IsTrue());
912
}
1013

1114
HOOK_AFTER_ALL()
@@ -47,3 +50,9 @@ HOOK_BEFORE_SCENARIO("@throw_scenariohook")
4750
{
4851
throw std::string{ "error" };
4952
}
53+
54+
HOOK_BEFORE_SCENARIO()
55+
{
56+
if (context.Contains("--failprogramhook") && context.Get<bool>("--failprogramhook"))
57+
std::cout << "should not be executed\n";
58+
}

cucumber_cpp/acceptance_test/test.bats

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,12 @@ teardown() {
176176
assert_output --partial "skipped Given a given step"
177177
assert_output --partial "tests : 0/1 passed"
178178
}
179+
180+
181+
@test "Test error program hook results in error and skipped steps" {
182+
run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test.custom run --feature cucumber_cpp/acceptance_test/features --tag "@smoke and @result:OK" --report console --required --failprogramhook
183+
assert_failure
184+
assert_output --partial "skipped Given a given step"
185+
refute_output --partial "should not be executed"
186+
# assert_output --partial "tests : 0/1 passed"
187+
}

cucumber_cpp/library/Application.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ namespace cucumber_cpp::library
224224

225225
int Application::GetExitCode() const
226226
{
227-
if (contextManager.ProgramContext().ExecutionStatus() == engine::Result::passed)
227+
if (contextManager.ProgramContext().EffectiveExecutionStatus() == engine::Result::passed)
228228
return 0;
229229
else
230230
return 1;

cucumber_cpp/library/engine/ContextManager.cpp

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "cucumber_cpp/library/engine/RuleInfo.hpp"
66
#include "cucumber_cpp/library/engine/ScenarioInfo.hpp"
77
#include "cucumber_cpp/library/engine/StepInfo.hpp"
8+
#include <algorithm>
89
#include <memory>
910
#include <string>
1011
#include <string_view>
@@ -48,11 +49,29 @@ namespace cucumber_cpp::library::engine
4849
Start();
4950
}
5051

52+
[[nodiscard]] Result CurrentContext::InheritedExecutionStatus() const
53+
{
54+
if (parent == nullptr)
55+
return executionStatus;
56+
else
57+
return std::max(executionStatus, parent->InheritedExecutionStatus());
58+
}
59+
60+
[[nodiscard]] Result CurrentContext::EffectiveExecutionStatus() const
61+
{
62+
return std::max(executionStatus, nestedExecutionStatus);
63+
}
64+
5165
[[nodiscard]] Result CurrentContext::ExecutionStatus() const
5266
{
5367
return executionStatus;
5468
}
5569

70+
[[nodiscard]] Result CurrentContext::NestedExecutionStatus() const
71+
{
72+
return nestedExecutionStatus;
73+
}
74+
5675
void CurrentContext::Start()
5776
{
5877
traceTime.Start();
@@ -69,7 +88,16 @@ namespace cucumber_cpp::library::engine
6988
executionStatus = result;
7089

7190
if (parent != nullptr)
72-
parent->ExecutionStatus(result);
91+
parent->NestedExecutionStatus(result);
92+
}
93+
94+
void CurrentContext::NestedExecutionStatus(Result result)
95+
{
96+
if (result > nestedExecutionStatus)
97+
nestedExecutionStatus = result;
98+
99+
if (parent != nullptr)
100+
parent->NestedExecutionStatus(result);
73101
}
74102

75103
ProgramContext::ProgramContext(std::shared_ptr<ContextStorageFactory> contextStorageFactory)
@@ -189,14 +217,11 @@ namespace cucumber_cpp::library::engine
189217
ContextManager::ScopedStepContext ContextManager::CreateStepContext(const StepInfo& stepInfo)
190218
{
191219
stepContext.push(std::make_shared<cucumber_cpp::library::engine::StepContext>(*scenarioContext, stepInfo));
192-
runnerContext.push(stepContext.top());
193-
194220
return ScopedStepContext{ *this };
195221
}
196222

197223
void ContextManager::DisposeStepContext()
198224
{
199-
runnerContext.pop();
200225
stepContext.pop();
201226
}
202227

@@ -212,4 +237,12 @@ namespace cucumber_cpp::library::engine
212237
{
213238
return *runnerContext.top();
214239
}
240+
241+
cucumber_cpp::library::engine::RunnerContext* ContextManager::CurrentStepContext()
242+
{
243+
if (stepContext.empty())
244+
return nullptr;
245+
246+
return stepContext.top().get();
247+
}
215248
}

cucumber_cpp/library/engine/ContextManager.hpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ namespace cucumber_cpp::library::engine
1919
explicit CurrentContext(std::shared_ptr<ContextStorageFactory> contextStorageFactory);
2020
explicit CurrentContext(CurrentContext* parent);
2121

22+
[[nodiscard]] Result InheritedExecutionStatus() const;
23+
[[nodiscard]] Result EffectiveExecutionStatus() const;
2224
[[nodiscard]] Result ExecutionStatus() const;
25+
[[nodiscard]] Result NestedExecutionStatus() const;
2326

2427
void Start();
2528
[[nodiscard]] TraceTime::Duration Duration() const;
@@ -28,9 +31,12 @@ namespace cucumber_cpp::library::engine
2831
void ExecutionStatus(Result result);
2932

3033
private:
34+
void NestedExecutionStatus(Result result);
35+
3136
CurrentContext* parent{ nullptr };
3237

3338
Result executionStatus{ Result::passed };
39+
Result nestedExecutionStatus{ Result::passed };
3440
TraceTime traceTime;
3541
};
3642

@@ -93,6 +99,7 @@ namespace cucumber_cpp::library::engine
9399
cucumber_cpp::library::engine::StepContext& StepContext();
94100

95101
cucumber_cpp::library::engine::RunnerContext& CurrentContext();
102+
cucumber_cpp::library::engine::RunnerContext* CurrentStepContext();
96103

97104
private:
98105
void DisposeFeatureContext();

cucumber_cpp/library/engine/FailureHandler.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@ namespace cucumber_cpp::library::engine
5353

5454
contextManager.CurrentContext().ExecutionStatus(cucumber_cpp::library::engine::Result::failed);
5555

56+
if (auto* stepContext = contextManager.CurrentStepContext(); stepContext != nullptr)
57+
stepContext->ExecutionStatus(cucumber_cpp::library::engine::Result::failed);
58+
5659
reportHandler.Failure(message, relativeFilePath, line);
5760
}
5861

cucumber_cpp/library/engine/HookExecutor.cpp

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,14 @@ namespace cucumber_cpp::library::engine
5656

5757
void ExecuteHook(cucumber_cpp::library::engine::RunnerContext& runnerContext, HookType hook, const std::set<std::string, std::less<>>& tags, const ThrowPolicy& throwPolicy)
5858
{
59-
for (const auto& match : HookRegistry::Instance().Query(hook, tags))
60-
{
61-
match.factory(runnerContext)->Execute();
62-
63-
if (runnerContext.ExecutionStatus() != cucumber_cpp::library::engine::Result::passed)
64-
throwPolicy.Throw();
65-
}
59+
if (runnerContext.InheritedExecutionStatus() == Result::passed)
60+
for (const auto& match : HookRegistry::Instance().Query(hook, tags))
61+
{
62+
match.factory(runnerContext)->Execute();
63+
64+
if (runnerContext.ExecutionStatus() != cucumber_cpp::library::engine::Result::passed)
65+
return;
66+
}
6667
}
6768
}
6869

cucumber_cpp/library/engine/TestExecution.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@ namespace cucumber_cpp::library::engine
8484
void TestExecutionImpl::RunStep(const cucumber_cpp::library::engine::StepInfo& stepInfo)
8585
{
8686
auto scopedContext = contextManager.CreateStepContext(stepInfo);
87-
if (contextManager.ScenarioContext().ExecutionStatus() == cucumber_cpp::library::engine::Result::passed)
87+
if (contextManager.ScenarioContext().InheritedExecutionStatus() == Result::passed &&
88+
contextManager.ScenarioContext().EffectiveExecutionStatus() == Result::passed)
8889
{
8990
const auto scopedStepReport = reportHandler.StepStart();
9091
const auto scopedStepHook = hookExecution.StepStart();

cucumber_cpp/library/engine/TestRunner.cpp

Lines changed: 13 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11

22
#include "cucumber_cpp/library/engine/TestRunner.hpp"
3-
#include "cucumber_cpp/library/BodyMacro.hpp"
43
#include "cucumber_cpp/library/engine/FeatureFactory.hpp"
54
#include "cucumber_cpp/library/engine/FeatureInfo.hpp"
65
#include "cucumber_cpp/library/engine/RuleInfo.hpp"
@@ -38,15 +37,10 @@ namespace cucumber_cpp::library::engine
3837

3938
void TestRunnerImpl::Run(const std::vector<std::unique_ptr<FeatureInfo>>& features)
4039
{
41-
auto runFeatures = [this, &features]
42-
{
43-
auto scope = testExecution.StartRun();
40+
auto scope = testExecution.StartRun();
4441

45-
for (const auto& featurePtr : features)
46-
RunFeature(*featurePtr);
47-
};
48-
49-
ASSERT_NO_THROW(runFeatures());
42+
for (const auto& featurePtr : features)
43+
RunFeature(*featurePtr);
5044
}
5145

5246
void TestRunnerImpl::NestedStep(StepType type, std::string step)
@@ -57,28 +51,20 @@ namespace cucumber_cpp::library::engine
5751

5852
void TestRunnerImpl::RunFeature(const FeatureInfo& feature)
5953
{
60-
auto runFeature = [this, &feature]
61-
{
62-
if (feature.Rules().empty() && feature.Scenarios().empty())
63-
return;
54+
if (feature.Rules().empty() && feature.Scenarios().empty())
55+
return;
6456

65-
const auto featureScope = testExecution.StartFeature(feature);
57+
const auto featureScope = testExecution.StartFeature(feature);
6658

67-
RunRules(feature.Rules());
68-
RunScenarios(feature.Scenarios());
69-
};
70-
ASSERT_NO_THROW(runFeature());
59+
RunRules(feature.Rules());
60+
RunScenarios(feature.Scenarios());
7161
}
7262

7363
void TestRunnerImpl::RunRule(const RuleInfo& rule)
7464
{
75-
auto runRule = [this, &rule]
76-
{
77-
const auto ruleScope = testExecution.StartRule(rule);
65+
const auto ruleScope = testExecution.StartRule(rule);
7866

79-
RunScenarios(rule.Scenarios());
80-
};
81-
ASSERT_NO_THROW(runRule());
67+
RunScenarios(rule.Scenarios());
8268
}
8369

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

9076
void TestRunnerImpl::RunScenario(const ScenarioInfo& scenario)
9177
{
92-
auto runScenario = [this, &scenario]
93-
{
94-
const auto scenarioScope = testExecution.StartScenario(scenario);
78+
const auto scenarioScope = testExecution.StartScenario(scenario);
9579

96-
currentScenario = &scenario;
80+
currentScenario = &scenario;
9781

98-
ExecuteSteps(scenario);
99-
};
100-
ASSERT_NO_THROW(runScenario());
82+
ExecuteSteps(scenario);
10183
}
10284

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

0 commit comments

Comments
 (0)