Skip to content

Commit 1c7fcde

Browse files
authored
fix(hooks): don't execute the current scope when an error occurs during a before hook (#174)
1 parent a76cdd4 commit 1c7fcde

18 files changed

+204
-74
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: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,9 @@
77
#include "cucumber_cpp/library/engine/StepInfo.hpp"
88
#include <functional>
99
#include <set>
10+
#include <stdexcept>
1011
#include <string>
12+
#include <utility>
1113

1214
namespace cucumber_cpp::library::engine
1315
{
@@ -20,20 +22,14 @@ namespace cucumber_cpp::library::engine
2022

2123
void ExecuteHook(cucumber_cpp::library::engine::RunnerContext& runnerContext, HookType hook, const std::set<std::string, std::less<>>& tags)
2224
{
23-
try
24-
{
25+
if (runnerContext.InheritedExecutionStatus() == Result::passed)
2526
for (const auto& match : HookRegistry::Instance().Query(hook, tags))
2627
{
2728
match.factory(runnerContext)->Execute();
2829

2930
if (runnerContext.ExecutionStatus() != cucumber_cpp::library::engine::Result::passed)
3031
return;
3132
}
32-
}
33-
catch (...)
34-
{
35-
runnerContext.ExecutionStatus(cucumber_cpp::library::engine::Result::failed);
36-
}
3733
}
3834
}
3935

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: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,11 @@ namespace cucumber_cpp::library::engine
3535
{
3636
}
3737

38-
void TestRunnerImpl::Run(const std::vector<std::unique_ptr<FeatureInfo>>& feature)
38+
void TestRunnerImpl::Run(const std::vector<std::unique_ptr<FeatureInfo>>& features)
3939
{
4040
auto scope = testExecution.StartRun();
4141

42-
for (const auto& featurePtr : feature)
42+
for (const auto& featurePtr : features)
4343
RunFeature(*featurePtr);
4444
}
4545

0 commit comments

Comments
 (0)