From b881514ca210482fd55577409eafb202ef4086c9 Mon Sep 17 00:00:00 2001 From: Alexander Romanenko Date: Wed, 9 Jul 2025 23:24:53 +0300 Subject: [PATCH 1/3] Add gherkin doc string support --- .../features/test_doc_strings.feature | 8 +++++++ cucumber_cpp/acceptance_test/steps/Steps.cpp | 6 +++++ cucumber_cpp/library/StepRegistry.cpp | 2 +- cucumber_cpp/library/StepRegistry.hpp | 19 +++++++-------- .../library/engine/FeatureFactory.cpp | 24 ++++++++++++++----- .../library/engine/FeatureFactory.hpp | 2 +- cucumber_cpp/library/engine/Step.cpp | 3 ++- cucumber_cpp/library/engine/Step.hpp | 3 ++- cucumber_cpp/library/engine/StepInfo.cpp | 14 ++++++++--- cucumber_cpp/library/engine/StepInfo.hpp | 8 ++++--- cucumber_cpp/library/engine/TestExecution.cpp | 2 +- cucumber_cpp/library/engine/TestRunner.cpp | 2 +- external/cucumber/gherkin/CMakeLists.txt | 2 +- 13 files changed, 66 insertions(+), 29 deletions(-) create mode 100644 cucumber_cpp/acceptance_test/features/test_doc_strings.feature diff --git a/cucumber_cpp/acceptance_test/features/test_doc_strings.feature b/cucumber_cpp/acceptance_test/features/test_doc_strings.feature new file mode 100644 index 00000000..88659432 --- /dev/null +++ b/cucumber_cpp/acceptance_test/features/test_doc_strings.feature @@ -0,0 +1,8 @@ +Feature: Doc Strings + + Scenario: Multiline doc strings + Given Next block of text enclosed in """ characters + """ + Multiline + Docstring + """ diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index fc55018d..2d2033ac 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -54,3 +54,9 @@ THEN("this should be skipped") { FAIL(); } + +GIVEN("Next block of text enclosed in \"\"\" characters") +{ + + ASSERT_THAT(docString, testing::Eq("Multiline\nDocstring")); +} diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index 336dc5d2..0e9ef169 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -64,7 +64,7 @@ namespace cucumber_cpp::library return list; } - void StepRegistry::Register(const std::string& matcher, engine::StepType stepType, std::unique_ptr (&factory)(Context& context, const engine::Table& table)) + void StepRegistry::Register(const std::string& matcher, engine::StepType stepType, std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString)) { if (matcher.starts_with('^') || matcher.ends_with('$')) registry.emplace_back(stepType, cucumber_expression::Matcher{ std::in_place_type, matcher }, factory); diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index ab0d3b4a..e6dc4746 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -8,7 +8,6 @@ #include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/engine/Table.hpp" #include -#include #include #include #include @@ -22,20 +21,20 @@ namespace cucumber_cpp::library { template - std::unique_ptr StepBodyFactory(Context& context, const engine::Table& table) + std::unique_ptr StepBodyFactory(Context& context, const engine::Table& table, const std::string& docString) { - return std::make_unique(context, table); + return std::make_unique(context, table, docString); } struct StepMatch { - StepMatch(std::unique_ptr (&factory)(Context& context, const engine::Table& table), std::variant, std::vector> matches, std::string_view stepRegexStr) + StepMatch(std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString), std::variant, std::vector> matches, std::string_view stepRegexStr) : factory(factory) , matches(std::move(matches)) , stepRegexStr(stepRegexStr) {} - std::unique_ptr (&factory)(Context& context, const engine::Table& table); + std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString); std::variant, std::vector> matches{}; std::string_view stepRegexStr{}; }; @@ -58,7 +57,7 @@ namespace cucumber_cpp::library struct Entry { - Entry(engine::StepType type, cucumber_expression::Matcher regex, std::unique_ptr (&factory)(Context& context, const engine::Table& table)) + Entry(engine::StepType type, cucumber_expression::Matcher regex, std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString)) : type(type) , regex(std::move(regex)) , factory(factory) @@ -66,7 +65,7 @@ namespace cucumber_cpp::library engine::StepType type{}; cucumber_expression::Matcher regex; - std::unique_ptr (&factory)(Context& context, const engine::Table& table); + std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString); std::uint32_t used{ 0 }; }; @@ -91,7 +90,7 @@ namespace cucumber_cpp::library [[nodiscard]] std::vector List() const; private: - void Register(const std::string& matcher, engine::StepType stepType, std::unique_ptr (&factory)(Context& context, const engine::Table& table)); + void Register(const std::string& matcher, engine::StepType stepType, std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString)); std::vector registry; cucumber_expression::ParameterRegistry& parameterRegistry; @@ -107,7 +106,7 @@ namespace cucumber_cpp::library struct Entry { - Entry(engine::StepType type, std::string regex, std::unique_ptr (&factory)(Context& context, const engine::Table& table)) + Entry(engine::StepType type, std::string regex, std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString)) : type(type) , regex(std::move(regex)) , factory(factory) @@ -115,7 +114,7 @@ namespace cucumber_cpp::library engine::StepType type{}; std::string regex; - std::unique_ptr (&factory)(Context& context, const engine::Table& table); + std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString); }; template diff --git a/cucumber_cpp/library/engine/FeatureFactory.cpp b/cucumber_cpp/library/engine/FeatureFactory.cpp index 56dfc797..d6849ac6 100644 --- a/cucumber_cpp/library/engine/FeatureFactory.cpp +++ b/cucumber_cpp/library/engine/FeatureFactory.cpp @@ -124,7 +124,7 @@ namespace cucumber_cpp::library::engine return table; for (const auto& dataTable = optionalPickleStepArgument.value().data_table.value(); - const auto& row : dataTable.rows) + const auto& row : dataTable.rows) { table.emplace_back(); auto& back = table.back(); @@ -136,6 +136,17 @@ namespace cucumber_cpp::library::engine return table; } + std::string DocStringFactory(const std::optional& optionalPickleStepArgument) + { + if (!optionalPickleStepArgument) + return ""; + + if (!optionalPickleStepArgument.value().doc_string.has_value()) + return ""; + + return optionalPickleStepArgument.value().doc_string.value().content; + } + std::set> TagsFactory(const std::vector& tags) { const auto range = tags | std::views::transform(&cucumber::messages::tag::name); @@ -151,10 +162,11 @@ namespace cucumber_cpp::library::engine void ConstructStep(const FeatureTreeFactory& featureTreeFactory, ScenarioInfo& scenarioInfo, const cucumber::messages::step& step, const cucumber::messages::pickle_step& pickleStep) { auto table = TableFactory(pickleStep.argument); + auto docString = DocStringFactory(pickleStep.argument); try { - scenarioInfo.Children().push_back(featureTreeFactory.CreateStepInfo(stepTypeLut.at(*pickleStep.type), pickleStep.text, scenarioInfo, step.location.line, step.location.column.value_or(0), std::move(table))); + scenarioInfo.Children().push_back(featureTreeFactory.CreateStepInfo(stepTypeLut.at(*pickleStep.type), pickleStep.text, scenarioInfo, step.location.line, step.location.column.value_or(0), std::move(table), docString)); } catch (const std::out_of_range&) { @@ -287,20 +299,20 @@ namespace cucumber_cpp::library::engine : stepRegistry{ stepRegistry } {} - std::unique_ptr FeatureTreeFactory::CreateStepInfo(StepType stepType, std::string stepText, const ScenarioInfo& scenarioInfo, std::size_t line, std::size_t column, std::vector> table) const + std::unique_ptr FeatureTreeFactory::CreateStepInfo(StepType stepType, std::string stepText, const ScenarioInfo& scenarioInfo, std::size_t line, std::size_t column, std::vector> table, std::string docString) const { try { auto stepMatch = stepRegistry.Query(stepText); - return std::make_unique(scenarioInfo, std::move(stepText), stepType, line, column, std::move(table), std::move(stepMatch)); + return std::make_unique(scenarioInfo, std::move(stepText), stepType, line, column, std::move(table), std::move(docString), std::move(stepMatch)); } catch (const StepRegistry::StepNotFoundError&) { - return std::make_unique(scenarioInfo, std::move(stepText), stepType, line, column, std::move(table)); + return std::make_unique(scenarioInfo, std::move(stepText), stepType, line, column, std::move(table), std::move(docString)); } catch (StepRegistry::AmbiguousStepError& ase) { - return std::make_unique(scenarioInfo, std::move(stepText), stepType, line, column, std::move(table), std::move(ase.matches)); + return std::make_unique(scenarioInfo, std::move(stepText), stepType, line, column, std::move(table), std::move(docString), std::move(ase.matches)); } } diff --git a/cucumber_cpp/library/engine/FeatureFactory.hpp b/cucumber_cpp/library/engine/FeatureFactory.hpp index 7e71d904..a064f366 100644 --- a/cucumber_cpp/library/engine/FeatureFactory.hpp +++ b/cucumber_cpp/library/engine/FeatureFactory.hpp @@ -20,7 +20,7 @@ namespace cucumber_cpp::library::engine { explicit FeatureTreeFactory(StepRegistry& stepRegistry); - [[nodiscard]] std::unique_ptr CreateStepInfo(StepType stepType, std::string stepText, const ScenarioInfo& scenarioInfo, std::size_t line, std::size_t column, std::vector> table) const; + [[nodiscard]] std::unique_ptr CreateStepInfo(StepType stepType, std::string stepText, const ScenarioInfo& scenarioInfo, std::size_t line, std::size_t column, std::vector> table, std::string docString) const; [[nodiscard]] std::unique_ptr Create(const std::filesystem::path& path, std::string_view tagExpression) const; diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index 12022b3d..140b26eb 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -8,9 +8,10 @@ namespace cucumber_cpp::library::engine { - Step::Step(Context& context, const Table& table) + Step::Step(Context& context, const Table& table, const std::string& docString) : context{ context } , table{ table } + , docString{ docString } {} void Step::Given(const std::string& step) const diff --git a/cucumber_cpp/library/engine/Step.hpp b/cucumber_cpp/library/engine/Step.hpp index d71bc0c4..3420171f 100644 --- a/cucumber_cpp/library/engine/Step.hpp +++ b/cucumber_cpp/library/engine/Step.hpp @@ -24,7 +24,7 @@ namespace cucumber_cpp::library::engine std::source_location sourceLocation; }; - Step(Context& context, const Table& table); + Step(Context& context, const Table& table, const std::string& docString); virtual ~Step() = default; virtual void SetUp() @@ -46,6 +46,7 @@ namespace cucumber_cpp::library::engine Context& context; const Table& table; + const std::string& docString; }; } diff --git a/cucumber_cpp/library/engine/StepInfo.cpp b/cucumber_cpp/library/engine/StepInfo.cpp index 29b8c29e..f21b6f24 100644 --- a/cucumber_cpp/library/engine/StepInfo.cpp +++ b/cucumber_cpp/library/engine/StepInfo.cpp @@ -10,34 +10,37 @@ namespace cucumber_cpp::library::engine { - StepInfo::StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table) + StepInfo::StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, std::string docString) : scenarioInfo{ scenarioInfo } , text{ std::move(text) } , type{ type } , line{ line } , column{ column } , table{ std::move(table) } + , docString{ std::move(docString) } { } - StepInfo::StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, struct StepMatch stepMatch) + StepInfo::StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, std::string docString, struct StepMatch stepMatch) : scenarioInfo{ scenarioInfo } , text{ std::move(text) } , type{ type } , line{ line } , column{ column } , table{ std::move(table) } + , docString{ std::move(docString) } , stepMatch{ std::move(stepMatch) } { } - StepInfo::StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, std::vector stepMatches) + StepInfo::StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, std::string docString, std::vector stepMatches) : scenarioInfo{ scenarioInfo } , text{ std::move(text) } , type{ type } , line{ line } , column{ column } , table{ std::move(table) } + , docString{ std::move(docString) } , stepMatch{ std::move(stepMatches) } { } @@ -72,6 +75,11 @@ namespace cucumber_cpp::library::engine return table; } + const std::string& StepInfo::DocString() const + { + return docString; + } + const std::variant>& StepInfo::StepMatch() const { return stepMatch; diff --git a/cucumber_cpp/library/engine/StepInfo.hpp b/cucumber_cpp/library/engine/StepInfo.hpp index d158ca0c..2e3b841c 100644 --- a/cucumber_cpp/library/engine/StepInfo.hpp +++ b/cucumber_cpp/library/engine/StepInfo.hpp @@ -16,9 +16,9 @@ namespace cucumber_cpp::library::engine struct StepInfo { - StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table); - StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, StepMatch); - StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, std::vector); + StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, std::string docString); + StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, std::string docString, StepMatch); + StepInfo(const struct ScenarioInfo& scenarioInfo, std::string text, StepType type, std::size_t line, std::size_t column, std::vector> table, std::string docString, std::vector); [[nodiscard]] const struct ScenarioInfo& ScenarioInfo() const; @@ -29,6 +29,7 @@ namespace cucumber_cpp::library::engine [[nodiscard]] std::size_t Column() const; [[nodiscard]] const std::vector>& Table() const; + [[nodiscard]] const std::string& DocString() const; [[nodiscard]] const std::variant>& StepMatch() const; private: @@ -41,6 +42,7 @@ namespace cucumber_cpp::library::engine std::size_t column; std::vector> table; + std::string docString; std::variant> stepMatch; }; diff --git a/cucumber_cpp/library/engine/TestExecution.cpp b/cucumber_cpp/library/engine/TestExecution.cpp index d48b735c..9420a4d7 100644 --- a/cucumber_cpp/library/engine/TestExecution.cpp +++ b/cucumber_cpp/library/engine/TestExecution.cpp @@ -63,7 +63,7 @@ namespace cucumber_cpp::library::engine const auto& stepContext = contextManager.StepContext(); auto& scenarioContext = contextManager.ScenarioContext(); - stepMatch.factory(scenarioContext, stepContext.info.Table())->Execute(stepMatch.matches); + stepMatch.factory(scenarioContext, stepContext.info.Table(), stepContext.info.DocString())->Execute(stepMatch.matches); } TestExecutionImpl::TestExecutionImpl(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution, const Policy& executionPolicy) diff --git a/cucumber_cpp/library/engine/TestRunner.cpp b/cucumber_cpp/library/engine/TestRunner.cpp index 75dd32d6..c56709ca 100644 --- a/cucumber_cpp/library/engine/TestRunner.cpp +++ b/cucumber_cpp/library/engine/TestRunner.cpp @@ -50,7 +50,7 @@ namespace cucumber_cpp::library::engine void TestRunnerImpl::NestedStep(StepType type, std::string step) { - const auto nestedStep = featureTreeFactory.CreateStepInfo(type, std::move(step), *currentScenario, 0, 0, {}); + const auto nestedStep = featureTreeFactory.CreateStepInfo(type, std::move(step), *currentScenario, 0, 0, {}, ""); testExecution.RunStep(*nestedStep); } diff --git a/external/cucumber/gherkin/CMakeLists.txt b/external/cucumber/gherkin/CMakeLists.txt index 16ac4462..fbaf6e59 100644 --- a/external/cucumber/gherkin/CMakeLists.txt +++ b/external/cucumber/gherkin/CMakeLists.txt @@ -1,6 +1,6 @@ FetchContent_Declare(cucumber_gherkin GIT_REPOSITORY https://github.com/cucumber/gherkin.git - GIT_TAG "0022bb07791485cda296e1785f4d0de47a04e5c9" + GIT_TAG "7cd0304c39a51ac139a22264ebe2a7ce6c68819d" ) FetchContent_MakeAvailable(cucumber_gherkin) From 73957ea47f0c6232bc62b4dfc25ec20e97131690 Mon Sep 17 00:00:00 2001 From: Alexander Romanenko Date: Wed, 9 Jul 2025 23:47:29 +0300 Subject: [PATCH 2/3] Add gherkin doc string support - string move --- cucumber_cpp/library/engine/FeatureFactory.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/engine/FeatureFactory.cpp b/cucumber_cpp/library/engine/FeatureFactory.cpp index d6849ac6..b254e627 100644 --- a/cucumber_cpp/library/engine/FeatureFactory.cpp +++ b/cucumber_cpp/library/engine/FeatureFactory.cpp @@ -166,7 +166,7 @@ namespace cucumber_cpp::library::engine try { - scenarioInfo.Children().push_back(featureTreeFactory.CreateStepInfo(stepTypeLut.at(*pickleStep.type), pickleStep.text, scenarioInfo, step.location.line, step.location.column.value_or(0), std::move(table), docString)); + scenarioInfo.Children().push_back(featureTreeFactory.CreateStepInfo(stepTypeLut.at(*pickleStep.type), pickleStep.text, scenarioInfo, step.location.line, step.location.column.value_or(0), std::move(table), std::move(docString))); } catch (const std::out_of_range&) { From 9c7bcb60582b84a1bf55913d2a25616d42d6f28e Mon Sep 17 00:00:00 2001 From: Alexander Romanenko Date: Thu, 31 Jul 2025 22:31:06 +0300 Subject: [PATCH 3/3] Add gherkin doc string support - missed doctype parameter in tests --- cucumber_cpp/library/engine/test/TestContextManager.cpp | 2 +- cucumber_cpp/library/engine/test/TestStep.cpp | 4 +++- .../library/engine/test_helper/ContextManagerInstance.hpp | 2 +- cucumber_cpp/library/test/TestSteps.cpp | 4 ++-- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/cucumber_cpp/library/engine/test/TestContextManager.cpp b/cucumber_cpp/library/engine/test/TestContextManager.cpp index 6b9c083b..b048917a 100644 --- a/cucumber_cpp/library/engine/test/TestContextManager.cpp +++ b/cucumber_cpp/library/engine/test/TestContextManager.cpp @@ -20,7 +20,7 @@ namespace cucumber_cpp::library::engine cucumber_cpp::library::engine::FeatureInfo feature{ {}, {}, {}, {}, {}, {} }; cucumber_cpp::library::engine::RuleInfo rule{ feature, {}, {}, {}, {}, {} }; cucumber_cpp::library::engine::ScenarioInfo scenario{ rule, {}, {}, {}, {}, {} }; - cucumber_cpp::library::engine::StepInfo step{ scenario, {}, {}, {}, {}, {} }; + cucumber_cpp::library::engine::StepInfo step{ scenario, {}, {}, {}, {}, {}, {} }; }; TEST_F(TestContextManager, Construct) diff --git a/cucumber_cpp/library/engine/test/TestStep.cpp b/cucumber_cpp/library/engine/test/TestStep.cpp index 309b5c8f..a8f7e141 100644 --- a/cucumber_cpp/library/engine/test/TestStep.cpp +++ b/cucumber_cpp/library/engine/test/TestStep.cpp @@ -33,10 +33,12 @@ namespace cucumber_cpp::library::engine std::vector{ TableValue{ "value1" }, TableValue{ "value2}" } } }; + const std::string docString = "multiline \n string"; + library::engine::test_helper::ContextManagerInstance contextManager; engine::test_helper::TestRunnerMock testRunnerMock; - StepMock step{ contextManager.StepContext(), table }; + StepMock step{ contextManager.StepContext(), table, docString }; }; TEST_F(TestStep, StepProvidesAccessToSetUpFunction) diff --git a/cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp b/cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp index 6950a5fa..fecbcc0a 100644 --- a/cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp +++ b/cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp @@ -28,7 +28,7 @@ namespace cucumber_cpp::library::engine::test_helper , feature{ tags, {}, {}, {}, {}, {} } , rule{ feature, {}, {}, {}, {}, {} } , scenario{ rule, tags, {}, {}, {}, {} } - , step{ scenario, {}, {}, {}, {}, {} } + , step{ scenario, {}, {}, {}, {}, {}, {} } { } diff --git a/cucumber_cpp/library/test/TestSteps.cpp b/cucumber_cpp/library/test/TestSteps.cpp index 9abac6ba..8b4fc419 100644 --- a/cucumber_cpp/library/test/TestSteps.cpp +++ b/cucumber_cpp/library/test/TestSteps.cpp @@ -58,7 +58,7 @@ namespace cucumber_cpp::library auto contextStorage{ std::make_shared() }; Context context{ contextStorage }; - matches.factory(context, {})->Execute(matches.matches); + matches.factory(context, {}, "")->Execute(matches.matches); EXPECT_THAT(context.Contains("float"), testing::IsTrue()); EXPECT_THAT(context.Contains("std::string"), testing::IsTrue()); @@ -85,7 +85,7 @@ namespace cucumber_cpp::library auto contextStorage{ std::make_shared() }; Context context{ contextStorage }; - matches.factory(context, {})->Execute(matches.matches); + matches.factory(context, {}, {})->Execute(matches.matches); } TEST_F(TestSteps, EscapedParenthesis)