From 1457b95697275f25f4b0e311e5aa8f517ddc3d0b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 8 Dec 2025 12:44:18 +0000 Subject: [PATCH 001/196] feat!: rewrite to use cucumber messages --- .vscode/launch.json | 42 +- CMakeLists.txt | 3 + cucumber_cpp/CMakeLists.txt | 2 + cucumber_cpp/CucumberCpp.hpp | 2 - cucumber_cpp/acceptance_test/steps/Steps.cpp | 3 +- cucumber_cpp/acceptance_test/test.bats | 41 +- cucumber_cpp/devkit/CMakeLists.txt | 1 + cucumber_cpp/devkit/empty/CMakeLists.txt | 6 + .../devkit/empty/features/empty.feature | 11 + .../devkit/empty/features/empty.ndjson | 9 + cucumber_cpp/example/features/2simple.feature | 4 + cucumber_cpp/example/features/3simple.feature | 44 ++ cucumber_cpp/example/features/debug.feature | 24 +- cucumber_cpp/example/features/rule.feature | 12 + cucumber_cpp/example/features/substep.feature | 4 + cucumber_cpp/example/hooks/Hooks.cpp | 25 + cucumber_cpp/example/steps/Steps.cpp | 47 +- cucumber_cpp/library/Application.cpp | 177 +++-- cucumber_cpp/library/Application.hpp | 33 +- cucumber_cpp/library/Body.cpp | 89 +++ cucumber_cpp/library/Body.hpp | 36 +- cucumber_cpp/library/BodyMacro.hpp | 4 +- cucumber_cpp/library/CMakeLists.txt | 18 +- cucumber_cpp/library/Errors.hpp | 5 - cucumber_cpp/library/HookRegistry.cpp | 78 ++- cucumber_cpp/library/HookRegistry.hpp | 114 +++- cucumber_cpp/library/Hooks.hpp | 2 +- cucumber_cpp/library/Query.cpp | 404 +++++++++++ cucumber_cpp/library/Query.hpp | 170 +++++ cucumber_cpp/library/StepRegistry.cpp | 88 ++- cucumber_cpp/library/StepRegistry.hpp | 67 +- cucumber_cpp/library/TagExpression.cpp | 74 --- cucumber_cpp/library/TagExpression.hpp | 14 - cucumber_cpp/library/TagsToSet.hpp | 21 - cucumber_cpp/library/api/CMakeLists.txt | 23 + cucumber_cpp/library/api/Gherkin.cpp | 78 +++ cucumber_cpp/library/api/Gherkin.hpp | 14 + cucumber_cpp/library/api/RunCucumber.cpp | 92 +++ cucumber_cpp/library/api/RunCucumber.hpp | 14 + .../library/assemble/AssembleTestSuites.cpp | 94 +++ .../library/assemble/AssembleTestSuites.hpp | 23 + .../library/assemble/AssembledTestCase.hpp | 16 + .../library/assemble/AssembledTestSuite.hpp | 17 + cucumber_cpp/library/assemble/CMakeLists.txt | 23 + .../cucumber_expression/CMakeLists.txt | 7 + .../cucumber_expression/Expression.cpp | 70 +- .../cucumber_expression/Expression.hpp | 7 +- .../cucumber_expression/MatchRange.cpp | 23 + .../cucumber_expression/MatchRange.hpp | 20 + .../library/cucumber_expression/Matcher.hpp | 11 + .../cucumber_expression/ParameterRegistry.cpp | 75 ++- .../cucumber_expression/ParameterRegistry.hpp | 33 +- .../cucumber_expression/RegularExpression.hpp | 17 + .../test/TestExpression.cpp | 53 +- .../test/TestTransformation.cpp | 10 +- cucumber_cpp/library/engine/CMakeLists.txt | 30 +- .../library/engine/ContextManager.cpp | 225 ------- .../library/engine/ContextManager.hpp | 152 ----- .../library/engine/FailureHandler.cpp | 43 -- .../library/engine/FailureHandler.hpp | 41 -- .../library/engine/FeatureFactory.cpp | 346 ---------- .../library/engine/FeatureFactory.hpp | 32 - cucumber_cpp/library/engine/FeatureInfo.cpp | 75 --- cucumber_cpp/library/engine/FeatureInfo.hpp | 50 -- cucumber_cpp/library/engine/HookExecutor.cpp | 85 --- cucumber_cpp/library/engine/HookExecutor.hpp | 88 --- cucumber_cpp/library/engine/NewRuntime.cpp | 1 + cucumber_cpp/library/engine/NewRuntime.hpp | 46 ++ cucumber_cpp/library/engine/RuleInfo.cpp | 67 -- cucumber_cpp/library/engine/RuleInfo.hpp | 50 -- cucumber_cpp/library/engine/ScenarioInfo.cpp | 96 --- cucumber_cpp/library/engine/ScenarioInfo.hpp | 54 -- cucumber_cpp/library/engine/Step.cpp | 10 +- cucumber_cpp/library/engine/StepInfo.cpp | 87 --- cucumber_cpp/library/engine/StepInfo.hpp | 80 --- cucumber_cpp/library/engine/TestExecution.cpp | 154 ----- cucumber_cpp/library/engine/TestExecution.hpp | 153 ----- cucumber_cpp/library/engine/TestRunner.cpp | 104 --- cucumber_cpp/library/engine/TestRunner.hpp | 73 -- .../library/engine/test/CMakeLists.txt | 1 - .../engine/test/TestFeatureFactory.cpp | 628 +++++++++--------- .../library/engine/test/TestHookExecutor.cpp | 196 +++--- cucumber_cpp/library/engine/test/TestStep.cpp | 35 +- .../library/engine/test/TestTestRunner.cpp | 223 ------- .../library/engine/test_helper/CMakeLists.txt | 1 - .../engine/test_helper/TestRunnerMock.hpp | 23 - cucumber_cpp/library/formatter/CMakeLists.txt | 33 + cucumber_cpp/library/formatter/Formatter.cpp | 22 + cucumber_cpp/library/formatter/Formatter.hpp | 30 + .../library/formatter/GetColorFunctions.cpp | 85 +++ .../library/formatter/GetColorFunctions.hpp | 25 + .../library/formatter/PrettyPrinter.cpp | 43 ++ .../library/formatter/PrettyPrinter.hpp | 25 + .../library/formatter/SummaryFormatter.cpp | 84 +++ .../library/formatter/SummaryFormatter.hpp | 28 + .../library/formatter/helper/CMakeLists.txt | 37 ++ .../formatter/helper/EventDataCollector.cpp | 138 ++++ .../formatter/helper/EventDataCollector.hpp | 72 ++ .../helper/GherkinDocumentParser.cpp | 112 ++++ .../helper/GherkinDocumentParser.hpp | 22 + .../library/formatter/helper/IndentString.cpp | 30 + .../library/formatter/helper/IndentString.hpp | 12 + .../library/formatter/helper/IssueHelpers.cpp | 38 ++ .../library/formatter/helper/IssueHelpers.hpp | 15 + .../library/formatter/helper/KeywordType.cpp | 47 ++ .../library/formatter/helper/KeywordType.hpp | 19 + .../library/formatter/helper/PickleParser.cpp | 37 ++ .../library/formatter/helper/PickleParser.hpp | 18 + .../formatter/helper/SummaryHelpers.cpp | 90 +++ .../formatter/helper/SummaryHelpers.hpp | 14 + .../helper/TestCaseAttemptFormatter.cpp | 94 +++ .../helper/TestCaseAttemptFormatter.hpp | 13 + .../helper/TestCaseAttemptParser.cpp | 140 ++++ .../helper/TestCaseAttemptParser.hpp | 53 ++ cucumber_cpp/library/report/StdOutReport.cpp | 1 - cucumber_cpp/library/runtime/CMakeLists.txt | 29 + cucumber_cpp/library/runtime/Coordinator.cpp | 101 +++ cucumber_cpp/library/runtime/Coordinator.hpp | 93 +++ cucumber_cpp/library/runtime/MakeRuntime.cpp | 37 ++ cucumber_cpp/library/runtime/MakeRuntime.hpp | 20 + .../library/runtime/SerialRuntimeAdapter.cpp | 53 ++ .../library/runtime/SerialRuntimeAdapter.hpp | 36 + .../library/runtime/TestCaseRunner.cpp | 291 ++++++++ .../library/runtime/TestCaseRunner.hpp | 114 ++++ cucumber_cpp/library/runtime/Worker.cpp | 241 +++++++ cucumber_cpp/library/runtime/Worker.hpp | 103 +++ cucumber_cpp/library/support/CMakeLists.txt | 26 + cucumber_cpp/library/support/Duration.cpp | 46 ++ cucumber_cpp/library/support/Duration.hpp | 21 + cucumber_cpp/library/support/Join.cpp | 27 + cucumber_cpp/library/support/Join.hpp | 14 + cucumber_cpp/library/support/Polyfill.hpp | 18 + .../library/support/SupportCodeLibrary.hpp | 18 + cucumber_cpp/library/support/Timestamp.cpp | 41 ++ cucumber_cpp/library/support/Timestamp.hpp | 24 + cucumber_cpp/library/support/Types.hpp | 61 ++ .../library/tag_expression/CMakeLists.txt | 4 + cucumber_cpp/library/tag_expression/Model.cpp | 54 ++ cucumber_cpp/library/tag_expression/Model.hpp | 21 + cucumber_cpp/library/test/CMakeLists.txt | 1 - cucumber_cpp/library/test/TestSteps.cpp | 8 +- .../library/test/TestTagExpression.cpp | 84 --- cucumber_cpp/library/util/Broadcaster.cpp | 41 ++ cucumber_cpp/library/util/Broadcaster.hpp | 36 + cucumber_cpp/library/util/CMakeLists.txt | 12 +- .../library/util/GetWorstTestStepResult.cpp | 30 + .../library/util/GetWorstTestStepResult.hpp | 12 + external/CMakeLists.txt | 2 + external/agauniyal/CMakeLists.txt | 1 + external/agauniyal/rang/CMakeLists.txt | 2 + external/agauniyal/rang/include/rang.hpp | 562 ++++++++++++++++ external/cucumber/gherkin/CMakeLists.txt | 2 +- external/cucumber/messages/CMakeLists.txt | 2 +- external/jupyter-xeus/CMakeLists.txt | 1 + .../jupyter-xeus/cpp-terminal/CMakeLists.txt | 14 + 155 files changed, 6010 insertions(+), 3313 deletions(-) create mode 100644 cucumber_cpp/devkit/CMakeLists.txt create mode 100644 cucumber_cpp/devkit/empty/CMakeLists.txt create mode 100644 cucumber_cpp/devkit/empty/features/empty.feature create mode 100644 cucumber_cpp/devkit/empty/features/empty.ndjson create mode 100644 cucumber_cpp/example/features/3simple.feature create mode 100644 cucumber_cpp/example/features/rule.feature create mode 100644 cucumber_cpp/example/features/substep.feature create mode 100644 cucumber_cpp/library/Body.cpp create mode 100644 cucumber_cpp/library/Query.cpp create mode 100644 cucumber_cpp/library/Query.hpp delete mode 100644 cucumber_cpp/library/TagExpression.cpp delete mode 100644 cucumber_cpp/library/TagExpression.hpp delete mode 100644 cucumber_cpp/library/TagsToSet.hpp create mode 100644 cucumber_cpp/library/api/CMakeLists.txt create mode 100644 cucumber_cpp/library/api/Gherkin.cpp create mode 100644 cucumber_cpp/library/api/Gherkin.hpp create mode 100644 cucumber_cpp/library/api/RunCucumber.cpp create mode 100644 cucumber_cpp/library/api/RunCucumber.hpp create mode 100644 cucumber_cpp/library/assemble/AssembleTestSuites.cpp create mode 100644 cucumber_cpp/library/assemble/AssembleTestSuites.hpp create mode 100644 cucumber_cpp/library/assemble/AssembledTestCase.hpp create mode 100644 cucumber_cpp/library/assemble/AssembledTestSuite.hpp create mode 100644 cucumber_cpp/library/assemble/CMakeLists.txt create mode 100644 cucumber_cpp/library/cucumber_expression/MatchRange.cpp create mode 100644 cucumber_cpp/library/cucumber_expression/MatchRange.hpp delete mode 100644 cucumber_cpp/library/engine/ContextManager.cpp delete mode 100644 cucumber_cpp/library/engine/ContextManager.hpp delete mode 100644 cucumber_cpp/library/engine/FailureHandler.cpp delete mode 100644 cucumber_cpp/library/engine/FailureHandler.hpp delete mode 100644 cucumber_cpp/library/engine/FeatureFactory.cpp delete mode 100644 cucumber_cpp/library/engine/FeatureFactory.hpp delete mode 100644 cucumber_cpp/library/engine/FeatureInfo.cpp delete mode 100644 cucumber_cpp/library/engine/FeatureInfo.hpp delete mode 100644 cucumber_cpp/library/engine/HookExecutor.cpp delete mode 100644 cucumber_cpp/library/engine/HookExecutor.hpp create mode 100644 cucumber_cpp/library/engine/NewRuntime.cpp create mode 100644 cucumber_cpp/library/engine/NewRuntime.hpp delete mode 100644 cucumber_cpp/library/engine/RuleInfo.cpp delete mode 100644 cucumber_cpp/library/engine/RuleInfo.hpp delete mode 100644 cucumber_cpp/library/engine/ScenarioInfo.cpp delete mode 100644 cucumber_cpp/library/engine/ScenarioInfo.hpp delete mode 100644 cucumber_cpp/library/engine/StepInfo.cpp delete mode 100644 cucumber_cpp/library/engine/StepInfo.hpp delete mode 100644 cucumber_cpp/library/engine/TestExecution.cpp delete mode 100644 cucumber_cpp/library/engine/TestExecution.hpp delete mode 100644 cucumber_cpp/library/engine/TestRunner.cpp delete mode 100644 cucumber_cpp/library/engine/TestRunner.hpp delete mode 100644 cucumber_cpp/library/engine/test/TestTestRunner.cpp delete mode 100644 cucumber_cpp/library/engine/test_helper/TestRunnerMock.hpp create mode 100644 cucumber_cpp/library/formatter/CMakeLists.txt create mode 100644 cucumber_cpp/library/formatter/Formatter.cpp create mode 100644 cucumber_cpp/library/formatter/Formatter.hpp create mode 100644 cucumber_cpp/library/formatter/GetColorFunctions.cpp create mode 100644 cucumber_cpp/library/formatter/GetColorFunctions.hpp create mode 100644 cucumber_cpp/library/formatter/PrettyPrinter.cpp create mode 100644 cucumber_cpp/library/formatter/PrettyPrinter.hpp create mode 100644 cucumber_cpp/library/formatter/SummaryFormatter.cpp create mode 100644 cucumber_cpp/library/formatter/SummaryFormatter.hpp create mode 100644 cucumber_cpp/library/formatter/helper/CMakeLists.txt create mode 100644 cucumber_cpp/library/formatter/helper/EventDataCollector.cpp create mode 100644 cucumber_cpp/library/formatter/helper/EventDataCollector.hpp create mode 100644 cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp create mode 100644 cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp create mode 100644 cucumber_cpp/library/formatter/helper/IndentString.cpp create mode 100644 cucumber_cpp/library/formatter/helper/IndentString.hpp create mode 100644 cucumber_cpp/library/formatter/helper/IssueHelpers.cpp create mode 100644 cucumber_cpp/library/formatter/helper/IssueHelpers.hpp create mode 100644 cucumber_cpp/library/formatter/helper/KeywordType.cpp create mode 100644 cucumber_cpp/library/formatter/helper/KeywordType.hpp create mode 100644 cucumber_cpp/library/formatter/helper/PickleParser.cpp create mode 100644 cucumber_cpp/library/formatter/helper/PickleParser.hpp create mode 100644 cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp create mode 100644 cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp create mode 100644 cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp create mode 100644 cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp create mode 100644 cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp create mode 100644 cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp create mode 100644 cucumber_cpp/library/runtime/CMakeLists.txt create mode 100644 cucumber_cpp/library/runtime/Coordinator.cpp create mode 100644 cucumber_cpp/library/runtime/Coordinator.hpp create mode 100644 cucumber_cpp/library/runtime/MakeRuntime.cpp create mode 100644 cucumber_cpp/library/runtime/MakeRuntime.hpp create mode 100644 cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp create mode 100644 cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp create mode 100644 cucumber_cpp/library/runtime/TestCaseRunner.cpp create mode 100644 cucumber_cpp/library/runtime/TestCaseRunner.hpp create mode 100644 cucumber_cpp/library/runtime/Worker.cpp create mode 100644 cucumber_cpp/library/runtime/Worker.hpp create mode 100644 cucumber_cpp/library/support/CMakeLists.txt create mode 100644 cucumber_cpp/library/support/Duration.cpp create mode 100644 cucumber_cpp/library/support/Duration.hpp create mode 100644 cucumber_cpp/library/support/Join.cpp create mode 100644 cucumber_cpp/library/support/Join.hpp create mode 100644 cucumber_cpp/library/support/Polyfill.hpp create mode 100644 cucumber_cpp/library/support/SupportCodeLibrary.hpp create mode 100644 cucumber_cpp/library/support/Timestamp.cpp create mode 100644 cucumber_cpp/library/support/Timestamp.hpp create mode 100644 cucumber_cpp/library/support/Types.hpp delete mode 100644 cucumber_cpp/library/test/TestTagExpression.cpp create mode 100644 cucumber_cpp/library/util/Broadcaster.cpp create mode 100644 cucumber_cpp/library/util/Broadcaster.hpp create mode 100644 cucumber_cpp/library/util/GetWorstTestStepResult.cpp create mode 100644 cucumber_cpp/library/util/GetWorstTestStepResult.hpp create mode 100644 external/agauniyal/CMakeLists.txt create mode 100644 external/agauniyal/rang/CMakeLists.txt create mode 100644 external/agauniyal/rang/include/rang.hpp create mode 100644 external/jupyter-xeus/CMakeLists.txt create mode 100644 external/jupyter-xeus/cpp-terminal/CMakeLists.txt diff --git a/.vscode/launch.json b/.vscode/launch.json index 32bb5ea0..a3b4b9e1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -24,7 +24,10 @@ "type": "pickString", "options": [ "cucumber_cpp/example/features", - "cucumber_cpp/acceptance_test/features" + "cucumber_cpp/example/features/rule.feature", + "cucumber_cpp/example/features/substep.feature", + "cucumber_cpp/acceptance_test/features", + "cucumber_cpp/devkit/empty/features" ] } ], @@ -40,10 +43,10 @@ "${input:features}", "--report", "console", - "junit-xml", - // "--com", - // "COMx", - // "--nordic", + // "junit-xml", + "--com", + "COMx", + "--nordic", "--tag", "${input:tag}" ], @@ -60,6 +63,35 @@ } ] }, + { + "name": "(gdb) Launch - no tags", + "type": "cppdbg", + "request": "launch", + "program": "${command:cmake.launchTargetPath}", + "args": [ + "run", + "--feature", + "${input:features}", + "--report", + "console", + // "junit-xml", + "--com", + "COMx", + "--nordic" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, { "name": "(gdb) Debug Test", "type": "cppdbg", diff --git a/CMakeLists.txt b/CMakeLists.txt index c30a33e2..190f3f9d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,9 @@ option(CCR_FETCH_DEPS "Fetch dependencies via FetchContent." On ) option(CCR_BUILD_TESTS "Enable build of the tests" ${CCR_DEFAULTOPT}) option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements" Off) +add_compile_options(-fsanitize=address) +add_link_options(-fsanitize=address) + if (CCR_BUILD_TESTS) ccr_enable_testing() endif() diff --git a/cucumber_cpp/CMakeLists.txt b/cucumber_cpp/CMakeLists.txt index eca2965c..0f567251 100644 --- a/cucumber_cpp/CMakeLists.txt +++ b/cucumber_cpp/CMakeLists.txt @@ -18,3 +18,5 @@ target_include_directories(cucumber_cpp INTERFACE target_link_libraries(cucumber_cpp INTERFACE cucumber_cpp.library ) + +add_subdirectory(devkit) diff --git a/cucumber_cpp/CucumberCpp.hpp b/cucumber_cpp/CucumberCpp.hpp index 1bad8e67..378a7ab0 100644 --- a/cucumber_cpp/CucumberCpp.hpp +++ b/cucumber_cpp/CucumberCpp.hpp @@ -6,7 +6,6 @@ #include "cucumber_cpp/library/Hooks.hpp" #include "cucumber_cpp/library/Steps.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" -#include "cucumber_cpp/library/report/Report.hpp" namespace cucumber_cpp { @@ -14,7 +13,6 @@ namespace cucumber_cpp using cucumber_cpp::library::Context; using cucumber_cpp::library::engine::Step; using cucumber_cpp::library::engine::StringTo; - using cucumber_cpp::library::report::ReportHandlerV2; } #endif diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index 063e60b3..5d126be1 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -2,6 +2,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include +#include #include #include #include @@ -33,7 +34,7 @@ STEP("a step step") WHEN("I print {string}", (const std::string& str)) { - std::cout << "print: " << str; + std::cout << std::format("print: {}\n", str); } THEN("an assertion is raised") diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index ed9ccbdf..30ab9af4 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -15,22 +15,23 @@ teardown() { } @test "Parse tag expression" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag @smoke @result:OK --feature cucumber_cpp/acceptance_test/features --report console + run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@smoke and @result:OK" --feature cucumber_cpp/acceptance_test/features --report console assert_success } @test "Failed tests" { run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@result:FAILED" --feature cucumber_cpp/acceptance_test/features --report console assert_failure - assert_output --partial "failed \"cucumber_cpp/acceptance_test/features/test_scenarios.feature\"" + assert_output --partial "[ FAILED ] Simple feature file/3.A failing scenario" assert_output --partial "skipped Then a then step" } @test "Undefined tests" { run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@result:UNDEFINED" --feature cucumber_cpp/acceptance_test/features --report console assert_failure - assert_output --partial "undefined \"cucumber_cpp/acceptance_test/features/test_scenarios.feature\"" + assert_output --partial "missing Given a missing step" assert_output --partial "skipped Then this should be skipped" + assert_output --partial "[ FAILED ] Simple feature file/3.A scenario with undefined step" } @test "No tests" { @@ -144,57 +145,59 @@ teardown() { assert_output --partial "--but--" } -@test "Test the asterisk keyword - will fail" { +@test "Test the asterisk keyword" { run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@keyword-asterisk" --report console - assert_failure + assert_output --partial "print: --when--" + assert_output --partial "print: --asterisk--" + assert_success } @test "Test passing scenario after failed scenario reports feature as failed" { run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_feature" --report console assert_failure - assert_output --partial "tests : 1/2 passed" + assert_output --partial "[==========] Running 2 tests from 1 test suite." + assert_output --partial "1 FAILED TEST" } @test "Test failing hook before results in error" { run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_scenariohook_before" --report console assert_failure - assert_output --partial "skipped Given a given step" - assert_output --partial "tests : 0/1 passed" + assert_output --partial "[ PASSED ] 0 tests" + assert_output --partial "[ FAILED ] Test scenario and step hook bindings.Run failing Scenario hooks before" } @test "Test failing hook after results in error" { run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_scenariohook_after" --report console assert_failure - assert_output --partial "Given a given step" - assert_output --partial "done" - assert_output --partial "failed" - assert_output --partial "tests : 0/1 passed" + assert_output --partial "[ PASSED ] 0 tests" + assert_output --partial "[ FAILED ] Test scenario and step hook bindings.Run failing Scenario hooks after" } @test "Test throwing hook results in error" { run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@throw_scenariohook" --report console assert_failure - assert_output --partial "skipped Given a given step" - assert_output --partial "tests : 0/1 passed" + assert_output --partial "[ PASSED ] 0 tests" + assert_output --partial "[ FAILED ] Test scenario and step hook bindings.Run throwing Scenario hooks" } @test "Test error program hook results in error and skipped steps" { 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 assert_failure - refute_output --partial "skipped Given a given step" - refute_output --partial "should not be executed" - assert_output --partial "tests : 0/0 passed" + assert_output --partial "[ PASSED ] 0 tests" + assert_output --partial "[ SKIPPED ] 1 test, listed below:" + assert_output --partial "[ SKIPPED ] Simple feature file.An OK scenario" + assert_output --partial "[ FAILED ] 0 tests, listed below:" } @test "Test unicode" { run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@unicode" --report console assert_success - assert_output --partial "tests : 1/1 passed" + assert_output --partial "[ OK ] Test for unicode characters.Can match unicode characters" } @test "Test unused step reporting" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@unused_steps" --report console --unused + run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@unused_steps" --report console --unused assert_success assert_output --regexp ".*The following steps have not been used:.*this step is not being used.*" refute_output --regexp ".*The following steps have not been used:.*this step is being used.*" diff --git a/cucumber_cpp/devkit/CMakeLists.txt b/cucumber_cpp/devkit/CMakeLists.txt new file mode 100644 index 00000000..4e40f5f0 --- /dev/null +++ b/cucumber_cpp/devkit/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(empty) diff --git a/cucumber_cpp/devkit/empty/CMakeLists.txt b/cucumber_cpp/devkit/empty/CMakeLists.txt new file mode 100644 index 00000000..19b3509d --- /dev/null +++ b/cucumber_cpp/devkit/empty/CMakeLists.txt @@ -0,0 +1,6 @@ +add_executable(cucumber_cpp.devkit.empty ${CCR_EXCLUDE_FROM_ALL}) + +target_link_libraries(cucumber_cpp.devkit.empty PRIVATE + cucumber_cpp + cucumber_cpp.runner +) diff --git a/cucumber_cpp/devkit/empty/features/empty.feature b/cucumber_cpp/devkit/empty/features/empty.feature new file mode 100644 index 00000000..2adb6d85 --- /dev/null +++ b/cucumber_cpp/devkit/empty/features/empty.feature @@ -0,0 +1,11 @@ +# language: nl +Functionaliteit: Empty Scenarios + Sometimes we want to quickly jot down a new scenario without specifying any actual steps + for what should be executed. + + In this instance we want to stipulate what should / shouldn't run and what the output is. + + Voorbeeld: Blank Scenario + Stel abc + Wanneer ik dit doe + Dan gebeurt er niets diff --git a/cucumber_cpp/devkit/empty/features/empty.ndjson b/cucumber_cpp/devkit/empty/features/empty.ndjson new file mode 100644 index 00000000..f90ca578 --- /dev/null +++ b/cucumber_cpp/devkit/empty/features/empty.ndjson @@ -0,0 +1,9 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Empty Scenarios\n Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.\n\n Scenario: Blank Scenario\n","uri":"samples/empty/empty.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Empty Scenarios","description":" Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.","children":[{"scenario":{"id":"0","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"Blank Scenario","description":"","steps":[],"examples":[]}}]},"comments":[],"uri":"samples/empty/empty.feature"}} +{"pickle":{"id":"1","uri":"samples/empty/empty.feature","location":{"line":7,"column":3},"astNodeIds":["0"],"tags":[],"name":"Blank Scenario","language":"en","steps":[]}} +{"testRunStarted":{"id":"2","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"3","pickleId":"1","testSteps":[],"testRunStartedId":"2"}} +{"testCaseStarted":{"id":"4","testCaseId":"3","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testCaseFinished":{"testCaseStartedId":"4","timestamp":{"seconds":0,"nanos":2000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"2","timestamp":{"seconds":0,"nanos":3000000},"success":true}} diff --git a/cucumber_cpp/example/features/2simple.feature b/cucumber_cpp/example/features/2simple.feature index 10edc3d6..cbb81569 100644 --- a/cucumber_cpp/example/features/2simple.feature +++ b/cucumber_cpp/example/features/2simple.feature @@ -32,6 +32,10 @@ Feature: Simple feature file | 10 | 4 | 5 | | 11 | 3 | 6 | + @result:FAILED + Scenario: Assert and Expect + Given expect and assert + @result:UNDEFINED Scenario: a scenario with a missing step Given there are cucumbers diff --git a/cucumber_cpp/example/features/3simple.feature b/cucumber_cpp/example/features/3simple.feature new file mode 100644 index 00000000..3dc56101 --- /dev/null +++ b/cucumber_cpp/example/features/3simple.feature @@ -0,0 +1,44 @@ +@smoke +Feature: Simple feature file + This is a Simple feature file + + Background: + Given a background step + + @result:OK @printme + Scenario Outline: Can substract + Given there are cucumbers + | foo | bar | asdasd | ad | + | boz | boo | asd | asdasd | + When I eat cucumbers + Then I should have cucumbers + + Examples: + | x | y | z | + | 10 | 5 | 5 | + | 11 | 3 | 8 | + + @result:FAILED + Scenario Outline: Is a dingus + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + And this step should be skipped + | foo | bar | + | boz | boo | + + Examples: + | x | y | z | + | 10 | 4 | 5 | + | 11 | 3 | 6 | + + @result:UNDEFINED + Scenario: a scenario with a missing step + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers left + And this step should be skipped + Examples: + | x | y | z | + | 10 | 5 | 5 | + | 11 | 3 | 8 | diff --git a/cucumber_cpp/example/features/debug.feature b/cucumber_cpp/example/features/debug.feature index ba4cf7b1..1c4981f3 100644 --- a/cucumber_cpp/example/features/debug.feature +++ b/cucumber_cpp/example/features/debug.feature @@ -1,42 +1,42 @@ @debug -Feature: Simple feature file +Feature: Debug simple feature file - Background: feature background + Background: background Given a feature background 1 Given a feature background 2 - Scenario: feature scenario 1 + Scenario: scenario 1 Given a step - Scenario: feature scenario 2 + Scenario: scenario 2 Given a step @rule1 - Rule: feature rule 1 + Rule: rule 1 - Background: feature rule 1 background + Background: background Given a feature rule 1 background 1 Given a feature rule 1 background 2 @rule1scenario1 - Scenario: feature rule 1 scenario 1 + Scenario: scenario 1 Given a step @rule1scenario2 - Scenario: feature rule 1 scenario 2 + Scenario: scenario 2 Given a step @rule2 - Rule: feature rule 2 + Rule: rule 2 - Background: feature rule 2 background + Background: background Given a feature rule 2 background 1 Given a feature rule 2 background 2 @rule2scenario1 - Scenario: feature rule 2 scenario 1 + Scenario: scenario 1 Given a step @rule2scenario2 - Scenario: feature rule 2 scenario 2 + Scenario: scenario 2 Given a step diff --git a/cucumber_cpp/example/features/rule.feature b/cucumber_cpp/example/features/rule.feature new file mode 100644 index 00000000..5b54bbef --- /dev/null +++ b/cucumber_cpp/example/features/rule.feature @@ -0,0 +1,12 @@ +Feature: Feature with a rule + In order to group related scenarios and rules + As a Cucumber user + I want a clear, human-readable feature description + + Rule: Example rule + In order to demonstrate rules in Cucumber + As a developer + I want to see how rules work in feature files + + Scenario Outline: Scenario under a rule + Given a step diff --git a/cucumber_cpp/example/features/substep.feature b/cucumber_cpp/example/features/substep.feature new file mode 100644 index 00000000..e3098254 --- /dev/null +++ b/cucumber_cpp/example/features/substep.feature @@ -0,0 +1,4 @@ +Feature: Feature with a step that will call another step + + Scenario: Scenario under a rule + Given call another step diff --git a/cucumber_cpp/example/hooks/Hooks.cpp b/cucumber_cpp/example/hooks/Hooks.cpp index d4e631f1..da66a4a3 100644 --- a/cucumber_cpp/example/hooks/Hooks.cpp +++ b/cucumber_cpp/example/hooks/Hooks.cpp @@ -16,3 +16,28 @@ HOOK_BEFORE_SCENARIO("@dingus") { std::cout << "running only for dingus tests\n"; } + +HOOK_BEFORE_SCENARIO() +{ + std::cout << "running before every test case\n"; +} + +HOOK_BEFORE_SCENARIO("@result:OK") +{ + std::cout << "running only for result:OK test cases\n"; +} + +HOOK_AFTER_SCENARIO() +{ + std::cout << "running after every test case\n"; +} + +HOOK_BEFORE_STEP() +{ + std::cout << "running before every test step\n"; +} + +HOOK_AFTER_STEP() +{ + std::cout << "running after every test step\n"; +} diff --git a/cucumber_cpp/example/steps/Steps.cpp b/cucumber_cpp/example/steps/Steps.cpp index 9210568e..4e422292 100644 --- a/cucumber_cpp/example/steps/Steps.cpp +++ b/cucumber_cpp/example/steps/Steps.cpp @@ -8,42 +8,48 @@ GIVEN(R"(a background step)") { - std::cout << "\nthis is a background step"; + std::cout << "this is a background step\n"; } GIVEN(R"(a simple data table)") { - std::cout << "row0.col0: " << table[0][0].As() << "\n"; - std::cout << "row0.col1: " << table[0][1].As() << "\n"; + // std::cout << "row0.col0: " << table[0][0].As() << "\n"; + // std::cout << "row0.col1: " << table[0][1].As() << "\n"; - std::cout << "row1.col0: " << table[1][0].As() << "\n"; - std::cout << "row1.col1: " << table[1][1].As() << "\n"; + // std::cout << "row1.col0: " << table[1][0].As() << "\n"; + // std::cout << "row1.col1: " << table[1][1].As() << "\n"; } -GIVEN(R"(there are {int} cucumbers)", (std::uint32_t num)) +GIVEN(R"(there are {int} cucumbers)", (std::int32_t num)) { context.InsertAt("cucumbers_before", num); } -STEP(R"(I eat {int} cucumbers)", (std::uint32_t num)) +STEP(R"(I eat {int} cucumbers)", (std::int32_t num)) { context.InsertAt("cucumbers_eaten", num); } -THEN(R"(^I should have ([0-9]+) cucumbers$)", (std::uint32_t num)) +STEP("expect and assert") { - const auto& before = context.Get("cucumbers_before"); - const auto& eaten = context.Get("cucumbers_eaten"); + EXPECT_THAT(false, testing::Eq(true)); + ASSERT_THAT(true, testing::Eq(false)); +} + +THEN(R"(^I should have ([0-9]+) cucumbers$)", (std::int32_t num)) +{ + const auto& before = context.Get("cucumbers_before"); + const auto& eaten = context.Get("cucumbers_eaten"); const auto actual = before - eaten; ASSERT_THAT(actual, testing::Eq(num)); } -THEN(R"(I should have ([0-9]+) cucumbers left)", (std::uint32_t num)) +THEN(R"(^I should have ([0-9]+) cucumbers left$)", (std::int32_t num)) { - const auto& before = context.Get("cucumbers_before"); - const auto& eaten = context.Get("cucumbers_eaten"); + const auto& before = context.Get("cucumbers_before"); + const auto& eaten = context.Get("cucumbers_eaten"); const auto actual = before - eaten; @@ -69,3 +75,18 @@ STEP(R"(a data table with comments and newlines inside)") { /* no body, example only */ } + +STEP(R"(a step)") +{ + std::cout << "--a step--\n"; +} + +STEP(R"(call another step)") +{ + Given(R"(a step)"); +} + +STEP("this step should be skipped") +{ + /* no body, example only */ +} diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 934700eb..4fdc6604 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -1,40 +1,36 @@ #include "cucumber_cpp/library/Application.hpp" +#include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/Errors.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FeatureFactory.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/HookExecutor.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/TestExecution.hpp" -#include "cucumber_cpp/library/engine/TestRunner.hpp" -#include "cucumber_cpp/library/report/JunitReport.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include "cucumber_cpp/library/report/StdOutReport.hpp" +#include "cucumber_cpp/library/engine/NewRuntime.hpp" +#include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include #include #include -#include #include #include +#include #include +#include #include #include #include #include -#include #include #include #include #include -#include -#include #include #include @@ -79,30 +75,23 @@ namespace cucumber_cpp::library } } - ReportHandlerValidator::ReportHandlerValidator(const report::Reporters& reporters) - : CLI::Validator("ReportHandler", [&reporters, cachedAvailableReporters = std::optional>{}](const std::string& str) mutable - { - if (!cachedAvailableReporters) - cachedAvailableReporters = reporters.AvailableReporters(); + // ReportHandlerValidator::ReportHandlerValidator(const report::Reporters& reporters) + // : CLI::Validator("ReportHandler", [&reporters, cachedAvailableReporters = std::optional>{}](const std::string& str) mutable + // { + // if (!cachedAvailableReporters) + // cachedAvailableReporters = reporters.AvailableReporters(); - if (std::ranges::find(*cachedAvailableReporters, str) == cachedAvailableReporters->end()) - return std::string{ "'" + str + "' is not a reporter" }; - else - return std::string{}; - }) - {} + // if (std::ranges::find(*cachedAvailableReporters, str) == cachedAvailableReporters->end()) + // return std::string{ "'" + str + "' is not a reporter" }; + // else + // return std::string{}; + // }) + // {} Application::Application(std::shared_ptr contextStorageFactory, bool removeDefaultGoogleTestListener) - : contextManager{ std::move(contextStorageFactory) } - , reporters{ contextManager } - , reportHandlerValidator{ reporters } + : contextStorageFactory{ contextStorageFactory } , removeDefaultGoogleTestListener{ removeDefaultGoogleTestListener } - { - gherkin.include_source(false); - gherkin.include_ast(true); - gherkin.include_pickles(true); - cli.require_subcommand(1); runCommand = cli.add_subcommand("run")->parse_complete_callback([this] @@ -113,14 +102,14 @@ namespace cucumber_cpp::library runCommand->add_option("-t,--tag", options.tags, "Cucumber tag expression"); runCommand->add_option("-f,--feature", options.features, "Feature file or folder with feature files")->required()->check(CLI::ExistingPath); - runCommand->add_option("-r,--report", options.reporters, "Name of the report generator: ")->required()->group("report generation")->check(reportHandlerValidator); + runCommand->add_option("-r,--report", options.reporters, "Name of the report generator: ")->required()->group("report generation"); //->check(reportHandlerValidator); runCommand->add_option("--outputfolder", options.outputfolder, "Specifies the output folder for generated report files")->group("report generation"); runCommand->add_option("--reportfile", options.reportfile, "Specifies the output name for generated report files")->group("report generation"); runCommand->add_flag("--dry", options.dryrun, "Generate report without running tests"); runCommand->add_flag("--unused", options.printStepsNotUsed, "Show step definitions that were not used"); - reporters.Add("console", std::make_unique()); - reporters.Add("junit-xml", std::make_unique(options.outputfolder, options.reportfile)); + // reporters.Add("console", std::make_unique()); + // reporters.Add("junit-xml", std::make_unique(options.outputfolder, options.reportfile)); ProgramContext().InsertRef(options); } @@ -130,7 +119,7 @@ namespace cucumber_cpp::library try { const auto reportDescription = runCommand->get_option("--report")->get_description(); - const auto joinedReporters = reportDescription + Join(reporters.AvailableReporters(), ", "); + const auto joinedReporters = reportDescription; // + Join(reporters.AvailableReporters(), ", "); runCommand->get_option("--report")->description(joinedReporters); cli.parse(argc, argv); @@ -141,32 +130,23 @@ namespace cucumber_cpp::library } catch (const InternalError& error) { - std::cout << "Internal error:\n\n"; - std::cout << error.what() << std::endl; - return GetExitCode(engine::Result::failed); - } - catch (const UnsupportedAsteriskError& error) - { - std::cout << "UnsupportedAsteriskError error:\n\n"; - std::cout << error.what() << std::endl; - return GetExitCode(engine::Result::failed); + std::cout << std::format("InternalError error:\n{}\n", error.what()); + return 1; } catch (const cucumber_expression::Error& error) { - std::cout << "Cucumber Expression error:\n\n"; - std::cout << error.what() << std::endl; - return GetExitCode(engine::Result::failed); + std::cout << std::format("Cucumber Expression error:\n{}\n", error.what()); + return 1; } catch (const std::exception& error) { - std::cout << "Generic error:\n\n"; - std::cout << error.what() << std::endl; - return GetExitCode(engine::Result::failed); + std::cout << std::format("Generic error:\n{}\n", error.what()); + return 1; } catch (...) { std::cout << "Unknown error"; - return GetExitCode(engine::Result::failed); + return 1; } return GetExitCode(); @@ -179,7 +159,7 @@ namespace cucumber_cpp::library Context& Application::ProgramContext() { - return contextManager.ProgramContext(); + return programContextRef; } cucumber_expression::ParameterRegistration& Application::ParameterRegistration() @@ -187,37 +167,58 @@ namespace cucumber_cpp::library return parameterRegistry; } - void Application::AddReportHandler(const std::string& name, std::unique_ptr&& reporter) - { - reporters.Add(name, std::move(reporter)); - } + // void Application::AddReportHandler(const std::string& name, std::unique_ptr&& reporter) + // { + // reporters.Add(name, std::move(reporter)); + // } void Application::RunFeatures() { - for (const auto& selectedReporter : options.reporters) - reporters.Use(selectedReporter); + struct BroadcastListener + { + explicit BroadcastListener(util::Broadcaster& broadcaster) + : listener(broadcaster, [this](const cucumber::messages::envelope& envelope) + { + OnEvent(envelope); + }) + {} + + void OnEvent(const cucumber::messages::envelope& envelope) + { + std::cout << envelope.to_json() << "\n"; + } - auto tagExpression = Join(options.tags, " "); - engine::HookExecutorImpl hookExecution{ contextManager }; + private: + util::Listener listener; + }; - const auto& runPolicy = (options.dryrun) ? static_cast(engine::dryRunPolicy) - : static_cast(engine::executeRunPolicy); + // BroadcastListener broadcastListener{ broadcaster }; - std::unique_ptr testExecution; - if (removeDefaultGoogleTestListener) - testExecution = std::make_unique(contextManager, reporters, hookExecution, runPolicy); - else - testExecution = std::make_unique(contextManager, reporters, hookExecution, runPolicy); + // for (const auto& selectedReporter : options.reporters) + // reporters.Use(selectedReporter); - StepRegistry stepRegistry{ parameterRegistry }; - engine::FeatureTreeFactory featureTreeFactory{ stepRegistry }; + const auto tagExpression = Join(options.tags, " "); + const auto featureFiles = GetFeatureFiles(options); - engine::TestRunnerImpl testRunner{ featureTreeFactory, *testExecution }; + support::RunOptions runOptions{ + .sources = { + .paths = featureFiles, + .tagExpression = tagExpression, + }, + .runtime = { + .retry = 1, + }, + }; - testRunner.Run(GetFeatureTree(featureTreeFactory, tagExpression)); + auto& listeners = testing::UnitTest::GetInstance()->listeners(); + auto* defaultEventListener = listeners.Release(listeners.default_result_printer()); - if (options.printStepsNotUsed) - PrintStepsNotUsed(stepRegistry); + api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster); + + listeners.Append(defaultEventListener); + + // if (options.printStepsNotUsed) + // PrintStepsNotUsed(stepRegistry); std::cout << '\n' << std::flush; @@ -242,26 +243,16 @@ namespace cucumber_cpp::library } } - std::vector> Application::GetFeatureTree(const engine::FeatureTreeFactory& featureTreeFactory, std::string_view tagExpression) - { - - const auto featureFiles = GetFeatureFiles(options); - std::vector> vec; - vec.reserve(featureFiles.size()); - - for (const auto& featurePath : featureFiles) - vec.push_back(featureTreeFactory.Create(featurePath, tagExpression)); - - return vec; - } - int Application::GetExitCode() const { - return GetExitCode(contextManager.ProgramContext().ExecutionStatus()); + return 0; + // if (testing::UnitTest::GetInstance()->Failed()) + // return GetExitCode(engine::Result::failed); + // return GetExitCode(engine::Result::passed); } - int Application::GetExitCode(engine::Result result) const - { - return static_cast>(result) - static_cast>(engine::Result::passed); - } + // int Application::GetExitCode(engine::Result result) const + // { + // return static_cast>(result) - static_cast>(engine::Result::passed); + // } } diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 989baea1..0a448e88 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -4,30 +4,23 @@ // IWYU pragma: private, include "cucumber_cpp/CucumberCpp.hpp" // IWYU pragma: friend cucumber_cpp/.* -#include "cucumber/gherkin/app.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FeatureFactory.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/report/Report.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include -#include #include #include -#include #include namespace cucumber_cpp::library { - struct ReportHandlerValidator : public CLI::Validator - { - explicit ReportHandlerValidator(const report::Reporters& reporters); - }; + // struct ReportHandlerValidator : public CLI::Validator + // { + // explicit ReportHandlerValidator(const report::Reporters& reporters); + // }; struct Application { @@ -52,30 +45,28 @@ namespace cucumber_cpp::library Context& ProgramContext(); cucumber_expression::ParameterRegistration& ParameterRegistration(); - void AddReportHandler(const std::string& name, std::unique_ptr&& reporter); + // void AddReportHandler(const std::string& name, std::unique_ptr&& reporter); private: void DryRunFeatures(); void RunFeatures(); - [[nodiscard]] engine::Result RunFeature(const std::filesystem::path& path, std::string_view tagExpression, report::ReportHandlerV2& reportHandler); void PrintStepsNotUsed(const StepRegistry& stepRegistry) const; - [[nodiscard]] std::vector> GetFeatureTree(const engine::FeatureTreeFactory& featureTreeFactory, std::string_view tagExpression); [[nodiscard]] int GetExitCode() const; - [[nodiscard]] int GetExitCode(engine::Result result) const; Options options; CLI::App cli; CLI::App* runCommand; - engine::ContextManager contextManager; + std::shared_ptr contextStorageFactory; + std::unique_ptr programContext{ std::make_unique(contextStorageFactory) }; + Context& programContextRef{ *programContext }; - report::ReportForwarderImpl reporters; - ReportHandlerValidator reportHandlerValidator; + util::Broadcaster broadcaster; - cucumber::gherkin::app gherkin; + // ReportHandlerValidator reportHandlerValidator; - cucumber_expression::ParameterRegistry parameterRegistry; + cucumber_expression::ParameterRegistry parameterRegistry{}; bool removeDefaultGoogleTestListener; }; } diff --git a/cucumber_cpp/library/Body.cpp b/cucumber_cpp/library/Body.cpp new file mode 100644 index 00000000..4363485e --- /dev/null +++ b/cucumber_cpp/library/Body.cpp @@ -0,0 +1,89 @@ +#include "cucumber_cpp/library/Body.hpp" +#include "cucumber/messages/exception.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/TraceTime.hpp" +#include "gtest/gtest.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library +{ + EventListener::EventListener(cucumber::messages::test_step_result& testStepResult) + : testStepResult{ testStepResult } + { + testing::UnitTest::GetInstance()->listeners().Append(this); + } + + EventListener::~EventListener() + { + testing::UnitTest::GetInstance()->listeners().Release(this); + } + + void EventListener::OnTestPartResult(const testing::TestPartResult& testPartResult) + { + if (testPartResult.failed()) + { + testStepResult.status = cucumber::messages::test_step_result_status::FAILED; + + auto fileName = std::filesystem::relative(testPartResult.file_name(), std::filesystem::current_path()).string(); + + if (testStepResult.message) + testStepResult.message = std::format("{}\n{}:{}: Failure\n{}", testStepResult.message.value(), fileName, testPartResult.line_number(), testPartResult.message()); + else + testStepResult.message = std::format("{}:{}: Failure\n{}", fileName, testPartResult.line_number(), testPartResult.message()); + } + + if (testPartResult.fatally_failed()) + throw FatalError{ testPartResult.message() }; + } + + cucumber::messages::test_step_result Body::ExecuteAndCatchExceptions(const ExecuteArgs& args) + { + TraceTime traceTime; + cucumber::messages::test_step_result testStepResult{ .status = cucumber::messages::test_step_result_status::PASSED }; + EventListener eventListener{ testStepResult }; + + try + { + traceTime.Start(); + Execute(args); + } + catch (const FatalError& error) + { + testStepResult.status = cucumber::messages::test_step_result_status::FAILED; + } + catch (std::exception& e) + { + testStepResult.status = cucumber::messages::test_step_result_status::FAILED; + testStepResult.exception = cucumber::messages::exception{ + .type = typeid(e).name(), + .message = e.what(), + }; + } + catch (...) + { + testStepResult.status = cucumber::messages::test_step_result_status::FAILED; + testStepResult.exception = cucumber::messages::exception{ + .type = "unknown", + .message = "unknown exception", + }; + } + + auto delta = traceTime.Delta(); + auto nanoseconds = std::chrono::duration_cast(delta); + static constexpr std::size_t nanosecondsPerSecond = 1e9; + testStepResult.duration = { + .seconds = static_cast(nanoseconds.count() / static_cast(nanosecondsPerSecond)), + .nanos = static_cast(nanoseconds.count() % static_cast(nanosecondsPerSecond)), + }; + + return testStepResult; + } +} diff --git a/cucumber_cpp/library/Body.hpp b/cucumber_cpp/library/Body.hpp index 49c0892a..a2fb4f69 100644 --- a/cucumber_cpp/library/Body.hpp +++ b/cucumber_cpp/library/Body.hpp @@ -1,18 +1,52 @@ #ifndef CUCUMBER_CPP_BODY_HPP #define CUCUMBER_CPP_BODY_HPP +#include "cucumber/messages/exception.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/TraceTime.hpp" #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include #include namespace cucumber_cpp::library { + using ExecuteArgs = std::variant, std::vector>; + + struct FatalError : std::runtime_error + { + using std::runtime_error::runtime_error; + }; + + struct EventListener : testing::EmptyTestEventListener + { + explicit EventListener(cucumber::messages::test_step_result& testStepResult); + ~EventListener(); + + void OnTestPartResult(const testing::TestPartResult& testPartResult) override; + + private: + cucumber::messages::test_step_result& testStepResult; + }; + struct Body { virtual ~Body() = default; - virtual void Execute(const std::variant, std::vector>& args = {}) = 0; + cucumber::messages::test_step_result ExecuteAndCatchExceptions(const ExecuteArgs& args = {}); + + protected: + virtual void Execute(const ExecuteArgs& args) = 0; }; template diff --git a/cucumber_cpp/library/BodyMacro.hpp b/cucumber_cpp/library/BodyMacro.hpp index 12609db3..4d46e3bb 100644 --- a/cucumber_cpp/library/BodyMacro.hpp +++ b/cucumber_cpp/library/BodyMacro.hpp @@ -2,7 +2,6 @@ #define CUCUMBER_CPP_BODYMACRO_HPP #include "cucumber_cpp/library/Body.hpp" -#include "cucumber_cpp/library/engine/FailureHandler.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" #include #include @@ -29,7 +28,8 @@ void Execute(const std::variant, std::vector>& args) override \ { \ cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ - ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); \ + /* ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); */ \ + ExecuteWithArgs(args, static_cast(nullptr)); \ } \ \ template \ diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index e7ccea79..c3c0f9fa 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -5,6 +5,7 @@ add_library(cucumber_cpp.library STATIC ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library PRIVATE Application.cpp Application.hpp + Body.cpp Body.hpp BodyMacro.hpp Context.hpp @@ -12,14 +13,13 @@ target_sources(cucumber_cpp.library PRIVATE HookRegistry.cpp HookRegistry.hpp Hooks.hpp + Query.cpp + Query.hpp Rtrim.cpp Rtrim.hpp StepRegistry.cpp StepRegistry.hpp Steps.hpp - TagExpression.cpp - TagExpression.hpp - TagsToSet.hpp TraceTime.cpp TraceTime.hpp ) @@ -32,10 +32,11 @@ target_link_libraries(cucumber_cpp.library PUBLIC GTest::gtest GTest::gmock cucumber_gherkin_lib - cucumber_cpp.library.report + cucumber_cpp.library.api cucumber_cpp.library.engine cucumber_cpp.library.tag_expression cucumber_cpp.library.util + cucumber_cpp.library.formatter CLI11 ) @@ -47,12 +48,17 @@ target_compile_options(cucumber_cpp.library $<$:/Zc:preprocessor> ) +add_subdirectory(assemble) +add_subdirectory(api) +add_subdirectory(support) +add_subdirectory(runtime) add_subdirectory(cucumber_expression) +add_subdirectory(formatter) add_subdirectory(tag_expression) add_subdirectory(engine) -add_subdirectory(report) +# add_subdirectory(report) add_subdirectory(util) if (CCR_BUILD_TESTS) - add_subdirectory(test) + # add_subdirectory(test) endif() diff --git a/cucumber_cpp/library/Errors.hpp b/cucumber_cpp/library/Errors.hpp index 08a66120..57444af1 100644 --- a/cucumber_cpp/library/Errors.hpp +++ b/cucumber_cpp/library/Errors.hpp @@ -9,11 +9,6 @@ namespace cucumber_cpp::library { using runtime_error::runtime_error; }; - - struct UnsupportedAsteriskError : std::runtime_error - { - using runtime_error::runtime_error; - }; } #endif diff --git a/cucumber_cpp/library/HookRegistry.cpp b/cucumber_cpp/library/HookRegistry.cpp index 529761ff..0dbd1935 100644 --- a/cucumber_cpp/library/HookRegistry.cpp +++ b/cucumber_cpp/library/HookRegistry.cpp @@ -1,15 +1,15 @@ #include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber/messages/pickle_tag.hpp" +#include "cucumber/messages/tag.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/TagExpression.hpp" #include #include -#include -#include #include -#include +#include +#include #include -#include +#include #include namespace cucumber_cpp::library @@ -18,17 +18,25 @@ namespace cucumber_cpp::library { auto TypeFilter(HookType hookType) { - return [hookType](const HookRegistry::Entry& entry) + return [hookType](const auto& keyValue) { - return entry.type == hookType; + return keyValue.second.type == hookType; }; }; - auto Matches(const std::set>& tags) + auto Matches(std::span tags) { - return [&tags](const HookRegistryBase::Entry& entry) + return [tags](const auto& keyValue) { - return entry.tagExpression->Evaluate(tags); + return keyValue.second.tagExpression->Evaluate(tags); + }; + } + + auto Matches(std::span tags) + { + return [tags](const auto& keyValue) + { + return keyValue.second.tagExpression->Evaluate(tags); }; } } @@ -37,29 +45,57 @@ namespace cucumber_cpp::library : context{ context } {} - std::vector HookRegistryBase::Query(HookType hookType, const std::set>& tags) const + HookRegistry::HookRegistry() { - std::vector matches; + for (const auto& matcher : HookRegistration::Instance().GetEntries()) + Register(matcher.type, matcher.expression, matcher.factory, matcher.sourceLocation); + } - for (const Entry& entry : registry | std::views::filter(TypeFilter(hookType)) | std::views::filter(Matches(tags))) - matches.emplace_back(entry.factory); + std::vector HookRegistry::FindIds(HookType hookType, std::span tags) const + { + auto ids = registry | std::views::filter(TypeFilter(hookType)) | std::views::filter(Matches(tags)) | std::views::keys; + return { ids.begin(), ids.end() }; + } - return matches; + std::vector HookRegistry::FindIds(HookType hookType, std::span tags) const + { + auto ids = registry | std::views::filter(TypeFilter(hookType)) | std::views::filter(Matches(tags)) | std::views::keys; + return { ids.begin(), ids.end() }; } - std::size_t HookRegistryBase::Size() const + std::size_t HookRegistry::Size() const { return registry.size(); } - std::size_t HookRegistryBase::Size(HookType hookType) const + std::size_t HookRegistry::Size(HookType hookType) const + { + return std::ranges::count(registry | std::views::values, hookType, &Definition::type); + } + + HookFactory HookRegistry::GetFactoryById(std::string id) const + { + return registry.at(id).factory; + } + + const HookRegistry::Definition& HookRegistry::GetDefinitionById(std::string id) const + { + return registry.at(id); + } + + void HookRegistry::Register(HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) + { + auto id = std::to_string(nextId++); + registry.emplace(id, Definition{ id, type, expression, factory, sourceLocation }); + } + + std::span HookRegistration::GetEntries() { - return std::ranges::count(registry, hookType, &Entry::type); + return registry; } - HookRegistry& HookRegistry::Instance() + std::span HookRegistration::GetEntries() const { - static HookRegistry instance; - return instance; + return registry; } } diff --git a/cucumber_cpp/library/HookRegistry.hpp b/cucumber_cpp/library/HookRegistry.hpp index 973e2e28..798a0eb5 100644 --- a/cucumber_cpp/library/HookRegistry.hpp +++ b/cucumber_cpp/library/HookRegistry.hpp @@ -1,20 +1,34 @@ #ifndef CUCUMBER_CPP_HOOKREGISTRY_HPP #define CUCUMBER_CPP_HOOKREGISTRY_HPP +#include "cucumber/messages/hook.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/pickle_tag.hpp" +#include "cucumber/messages/source_reference.hpp" +#include "cucumber/messages/tag.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/tag_expression/Model.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" #include -#include +#include +#include #include -#include +#include +#include #include #include #include namespace cucumber_cpp::library { + + template + std::unique_ptr HookBodyFactory(Context& context) + { + return std::make_unique(context); + } + enum struct HookType { beforeAll, @@ -47,56 +61,96 @@ namespace cucumber_cpp::library Context& context; }; + using HookFactory = std::unique_ptr (&)(Context& context); + struct HookMatch { - explicit HookMatch(std::unique_ptr (&factory)(Context& context)) + explicit HookMatch(HookFactory factory) : factory(factory) {} - std::unique_ptr (&factory)(Context& context); + HookFactory factory; }; - struct HookRegistryBase + struct HookRegistry { - struct Entry + struct Definition { - Entry(HookType type, std::string_view expression, std::unique_ptr (&factory)(Context& context)) - : type(type) + Definition(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) + : type{ type } , tagExpression{ tag_expression::Parse(expression) } - , factory(factory) + , factory{ factory } + , hook{ + .id = id, + .source_reference = cucumber::messages::source_reference{ + .uri = sourceLocation.file_name(), + .location = cucumber::messages::location{ + .line = sourceLocation.line(), + }, + }, + .tag_expression = std::string{ expression }, + } {} HookType type; std::unique_ptr tagExpression; - std::unique_ptr (&factory)(Context& context); + HookFactory factory; + cucumber::messages::hook hook; }; - [[nodiscard]] std::vector Query(HookType hookType, const std::set>& tags) const; + explicit HookRegistry(); + + std::vector FindIds(HookType hookType, std::span tags = {}) const; + std::vector FindIds(HookType hookType, std::span tags) const; [[nodiscard]] std::size_t Size() const; [[nodiscard]] std::size_t Size(HookType hookType) const; - protected: - template - std::size_t Register(const std::string& tagExpression, HookType hookType); + HookFactory GetFactoryById(std::string id) const; + const Definition& GetDefinitionById(std::string id) const; private: - template - static std::unique_ptr Construct(Context& context); + void Register(HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation); - std::vector registry; + std::uint32_t nextId{ 1 }; + std::map registry; }; - struct HookRegistry : HookRegistryBase + struct HookRegistration { private: - HookRegistry() = default; + HookRegistration() = default; public: - static HookRegistry& Instance(); + static inline HookRegistration& Instance() + { + static HookRegistration instance; + return instance; + } + + struct Entry + { + Entry(HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) + : type(type) + , expression{ expression } + , factory(factory) + , sourceLocation{ sourceLocation } + {} + + HookType type; + std::string_view expression; + HookFactory factory; + std::source_location sourceLocation; + }; template - static std::size_t Register(const std::string& tagExpression, HookType hookType); + static std::size_t Register(std::string_view tagExpression, HookType hookType, std::source_location sourceLocation = std::source_location::current()); + + std::span GetEntries(); + [[nodiscard]] std::span GetEntries() const; + + private: + std::vector registry; }; ////////////////////////// @@ -104,22 +158,10 @@ namespace cucumber_cpp::library ////////////////////////// template - std::size_t HookRegistryBase::Register(const std::string& tagExpression, HookType hookType) - { - registry.emplace_back(hookType, tagExpression, Construct); - return registry.size(); - } - - template - std::unique_ptr HookRegistryBase::Construct(Context& context) - { - return std::make_unique(context); - } - - template - std::size_t HookRegistry::Register(const std::string& tagExpression, HookType hookType) + std::size_t HookRegistration::Register(std::string_view tagExpression, HookType hookType, std::source_location sourceLocation) { - return Instance().HookRegistryBase::Register(tagExpression, hookType); + Instance().registry.emplace_back(hookType, tagExpression, HookBodyFactory, sourceLocation); + return Instance().registry.size(); } } diff --git a/cucumber_cpp/library/Hooks.hpp b/cucumber_cpp/library/Hooks.hpp index eaa4098a..de5f0733 100644 --- a/cucumber_cpp/library/Hooks.hpp +++ b/cucumber_cpp/library/Hooks.hpp @@ -7,7 +7,7 @@ #include "cucumber_cpp/library/BodyMacro.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" -#define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::HookRegistry::Register, cucumber_cpp::library::HookBase) +#define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::HookRegistration::Register, cucumber_cpp::library::HookBase) #define HOOK_BEFORE_ALL() \ HOOK_( \ diff --git a/cucumber_cpp/library/Query.cpp b/cucumber_cpp/library/Query.cpp new file mode 100644 index 00000000..d64af71b --- /dev/null +++ b/cucumber_cpp/library/Query.cpp @@ -0,0 +1,404 @@ +#include "cucumber_cpp/library/Query.hpp" +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/background.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/examples.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/hook.hpp" +#include "cucumber/messages/meta.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/rule.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/suggestion.hpp" +#include "cucumber/messages/table_row.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_run_hook_finished.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_run_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber/messages/undefined_parameter_type.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library +{ + Lineage Lineage::operator+(std::shared_ptr gherkinDocument) const + { + Lineage copy = *this; + copy.gherkinDocument = gherkinDocument; + return copy; + } + + Lineage Lineage::operator+(std::shared_ptr feature) const + { + Lineage copy = *this; + copy.feature = feature; + return copy; + } + + Lineage Lineage::operator+(std::shared_ptr rule) const + { + Lineage copy = *this; + copy.rule = rule; + return copy; + } + + Lineage Lineage::operator+(std::shared_ptr scenario) const + { + Lineage copy = *this; + copy.scenario = scenario; + return copy; + } + + Lineage Lineage::operator+(std::shared_ptr examples) const + { + Lineage copy = *this; + copy.examples = examples; + return copy; + } + + Lineage Lineage::operator+(std::shared_ptr tableRow) const + { + Lineage copy = *this; + copy.tableRow = tableRow; + return copy; + } + + Lineage Lineage::operator+(std::uint32_t featureIndex) const + { + Lineage copy = *this; + copy.featureIndex = featureIndex; + return copy; + } + + std::string Lineage::GetUniqueFeatureName() const + { + return std::format("{}/{}", feature->name, featureIndex); + } + + std::string Lineage::GetScenarioAndOrRuleName() const + { + if (rule) + return std::format("{}/{}", rule->name, scenario->name); + return scenario->name; + } + + Query& Query::operator+=(const cucumber::messages::envelope& envelope) + { + if (envelope.meta) + meta = std::make_unique(*envelope.meta); + + if (envelope.gherkin_document) + *this += *envelope.gherkin_document; + + if (envelope.pickle) + *this += *envelope.pickle; + + if (envelope.hook) + *this += *envelope.hook; + + if (envelope.step_definition) + *this += *envelope.step_definition; + + if (envelope.test_run_started) + *this += *envelope.test_run_started; + + if (envelope.test_run_hook_started) + *this += *envelope.test_run_hook_started; + + if (envelope.test_run_hook_finished) + *this += *envelope.test_run_hook_finished; + + if (envelope.test_case) + *this += *envelope.test_case; + + if (envelope.test_case_started) + *this += *envelope.test_case_started; + + if (envelope.test_step_started) + *this += *envelope.test_step_started; + + if (envelope.attachment) + *this += *envelope.attachment; + + if (envelope.test_step_finished) + *this += *envelope.test_step_finished; + + if (envelope.test_case_finished) + *this += *envelope.test_case_finished; + + if (envelope.test_run_finished) + *this += *envelope.test_run_finished; + + if (envelope.suggestion) + *this += *envelope.suggestion; + + if (envelope.undefined_parameter_type) + *this += *envelope.undefined_parameter_type; + + if (envelope.parameter_type) + *this += *envelope.parameter_type; + + return *this; + } + + const Lineage& Query::FindLineageByPickle(const cucumber::messages::pickle& pickle) const + { + return lineageById.at(pickle.ast_node_ids[0]); + } + + const Lineage& Query::FindLineageByUri(std::string uri) const + { + return lineageByUri.at(uri); + } + + const cucumber::messages::parameter_type& Query::FindParameterTypeById(const std::string& id) const + { + return parameterTypeById.at(id); + } + + const cucumber::messages::parameter_type& Query::FindParameterTypeByName(const std::string& name) const + { + return parameterTypeByName.at(name); + } + + bool Query::ContainsParameterTypeByName(const std::string& name) const + { + return parameterTypeByName.contains(name); + } + + const cucumber::messages::test_case& Query::FindTestCaseById(const std::string& id) const + { + return testCaseById.at(id); + } + + const cucumber::messages::pickle& Query::FindPickleById(const std::string& id) const + { + return pickleById.at(id); + } + + const cucumber::messages::pickle_step& Query::FindPickleStepById(const std::string& id) const + { + return pickleStepById.at(id); + } + + const cucumber::messages::step_definition& Query::FindStepDefinitionById(const std::string& id) const + { + return stepDefinitionById.at(id); + } + + const std::map& Query::TestCaseStarted() const + { + return testCaseStartedById; + } + + const std::map& Query::TestCaseFinishedByTestCaseStartedId() const + { + return testCaseFinishedByTestCaseStartedId; + } + + void + Query::operator+=(const cucumber::messages::gherkin_document& gherkinDocument) + { + if (gherkinDocument.feature) + *this += { *gherkinDocument.feature, Lineage{ std::make_unique(gherkinDocument) } }; + } + + void Query::operator+=(const cucumber::messages::pickle& pickle) + { + pickleById.emplace(pickle.id, pickle); + for (const auto& pickleStep : pickle.steps) + pickleStepById.emplace(pickleStep.id, pickleStep); + } + + void Query::operator+=(const cucumber::messages::hook& hook) + { + hooksById.emplace(hook.id, hook); + } + + void Query::operator+=(const cucumber::messages::step_definition& stepDefinition) + { + stepDefinitionById.emplace(stepDefinition.id, stepDefinition); + } + + void Query::operator+=(const cucumber::messages::test_run_started& testRunStarted) + { + this->testRunStarted = std::make_unique(testRunStarted); + } + + void Query::operator+=(const cucumber::messages::test_run_hook_started& testRunHookStarted) + { + testRunHookStartedById.emplace(testRunHookStarted.id, testRunHookStarted); + } + + void Query::operator+=(const cucumber::messages::test_run_hook_finished& testRunHookFinished) + { + testRunHookFinishedByTestRunHookStartedId.emplace(testRunHookFinished.test_run_hook_started_id, testRunHookFinished); + } + + void Query::operator+=(const cucumber::messages::test_case& testCase) + { + auto& testCaseRef = testCaseById.emplace(testCase.id, testCase).first->second; + testCaseByPickleId.emplace(testCase.pickle_id, testCaseRef); + + for (const auto& testStep : testCase.test_steps) + { + testStepById.emplace(testStep.id, testStep); + pickleIdByTestStepId.emplace(testStep.id, testCase.pickle_id); + + if (testStep.pickle_step_id) + { + pickleStepIdByTestStepId[testStep.id] = testStep.pickle_step_id.value(); + testStepIdsByPickleStepId[testStep.pickle_step_id.value()].push_front(testStep.id); + + if (testStep.step_match_arguments_lists) + stepMatchArgumentsListsByPickleStepId[testStep.pickle_step_id.value()] = testStep.step_match_arguments_lists.value(); + } + } + } + + void Query::operator+=(const cucumber::messages::test_case_started& testCaseStarted) + { + testCaseStartedById.emplace(testCaseStarted.id, testCaseStarted); + + /* reset data? https://github.dev/cucumber/query/blob/f31732e5972c1815614f1d83928a7065e3080dc4/javascript/src/Query.ts#L249 */ + } + + void Query::operator+=(const cucumber::messages::test_step_started& testStepStarted) + { + testStepStartedByTestCaseStartedId[testStepStarted.test_case_started_id].push_front(testStepStarted); + } + + void Query::operator+=(const cucumber::messages::attachment& attachment) + { + auto* ptr = &attachments.emplace_front(attachment); + if (attachment.test_step_id) + attachmentsByTestStepId[attachment.test_step_id.value()].push_front(ptr); + if (attachment.test_case_started_id) + attachmentsByTestCaseStartedId[attachment.test_case_started_id.value()].push_front(ptr); + } + + void Query::operator+=(const cucumber::messages::test_step_finished& testStepFinished) + { + auto* testStepResultPtr = &testStepResults.emplace_front(testStepFinished.test_step_result); + + testStepFinishedByTestCaseStartedId[testStepFinished.test_case_started_id].push_front(testStepFinished); + + const auto& pickleId = pickleIdByTestStepId.at(testStepFinished.test_step_id); + testStepResultByPickleId[pickleId].push_front(testStepResultPtr); + + const auto& testStep = testStepById.at(testStepFinished.test_step_id); + testStepResultsbyTestStepId[testStep.id].push_front(testStepResultPtr); + if (testStep.pickle_step_id) + testStepResultsByPickleStepId[testStep.pickle_step_id.value()].push_front(testStepResultPtr); + } + + void Query::operator+=(const cucumber::messages::test_case_finished& testCaseFinished) + { + testCaseFinishedByTestCaseStartedId.emplace(testCaseFinished.test_case_started_id, testCaseFinished); + } + + void Query::operator+=(const cucumber::messages::test_run_finished& testRunFinished) + { + this->testRunFinished = std::make_unique(testRunFinished); + } + + void Query::operator+=(const cucumber::messages::suggestion& suggestion) + { + suggestionsByPickleStepId[suggestion.pickle_step_id].push_front(suggestion); + } + + void Query::operator+=(const cucumber::messages::undefined_parameter_type& undefinedParameterType) + { + undefinedParameterTypes.emplace_front(undefinedParameterType); + } + + void Query::operator+=(std::pair feature) + { + auto featurePtr = std::make_shared(feature.first); + + ++featureCountByName[feature.first.name]; + + auto linaege = feature.second + featurePtr + featureCountByName[feature.first.name]; + lineageByUri[*linaege.gherkinDocument->uri] = linaege; + + for (const auto& child : feature.first.children) + { + if (child.background) + *this += child.background->steps; + if (child.scenario) + *this += { *child.scenario, linaege }; + if (child.rule) + *this += { *child.rule, linaege }; + } + } + + void Query::operator+=(std::pair scenario) + { + auto scenarioPtr = std::make_shared(scenario.first); + lineageByUri[*scenario.second.gherkinDocument->uri] = scenario.second; + + lineageById[scenarioPtr->id] = scenario.second + scenarioPtr; + + for (const auto& examples : scenario.first.examples) + { + auto examplesPtr = std::make_shared(examples); + + lineageById[examples.id] = scenario.second + + scenarioPtr + + examplesPtr; + + for (const auto& tableRow : examples.table_body) + { + auto tableRowPtr = std::make_shared(tableRow); + + lineageById[tableRow.id] = scenario.second + + scenarioPtr + + examplesPtr + + tableRowPtr; + } + } + + *this += scenarioPtr->steps; + } + + void Query::operator+=(std::pair rule) + { + auto rulePtr = std::make_shared(rule.first); + + for (const auto& child : rule.first.children) + { + if (child.background) + *this += child.background->steps; + if (child.scenario) + *this += { *child.scenario, rule.second + rulePtr }; + } + } + + void Query::operator+=(std::span steps) + { + for (const auto& step : steps) + stepById.emplace(step.id, step); + } + + void Query::operator+=(const cucumber::messages::parameter_type& parameterType) + { + auto& ref = parameterTypeById.emplace(parameterType.id, parameterType).first->second; + parameterTypeByName.emplace(parameterType.name, ref); + } +} diff --git a/cucumber_cpp/library/Query.hpp b/cucumber_cpp/library/Query.hpp new file mode 100644 index 00000000..da0f4310 --- /dev/null +++ b/cucumber_cpp/library/Query.hpp @@ -0,0 +1,170 @@ +#ifndef LIBRARY_QUERY_HPP +#define LIBRARY_QUERY_HPP + +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/examples.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/hook.hpp" +#include "cucumber/messages/meta.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/rule.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" +#include "cucumber/messages/suggestion.hpp" +#include "cucumber/messages/table_row.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_run_hook_finished.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_run_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber/messages/undefined_parameter_type.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library +{ + struct Lineage + { + Lineage operator+(std::shared_ptr gherkinDocument) const; + Lineage operator+(std::shared_ptr feature) const; + Lineage operator+(std::shared_ptr rule) const; + Lineage operator+(std::shared_ptr scenario) const; + Lineage operator+(std::shared_ptr examples) const; + Lineage operator+(std::shared_ptr tableRow) const; + Lineage operator+(std::uint32_t featureIndex) const; + + std::string GetUniqueFeatureName() const; + std::string GetScenarioAndOrRuleName() const; + + std::shared_ptr gherkinDocument; + std::shared_ptr feature; + std::shared_ptr rule; + std::shared_ptr scenario; + std::shared_ptr examples; + std::shared_ptr tableRow; + + std::uint32_t featureIndex{ 0 }; + }; + + struct Query + { + Query& operator+=(const cucumber::messages::envelope& envelope); + + auto GetPickles() const + { + return pickleById | std::views::values; + } + + const Lineage& FindLineageByPickle(const cucumber::messages::pickle& pickle) const; + const Lineage& FindLineageByUri(std::string) const; + + const cucumber::messages::parameter_type& FindParameterTypeById(const std::string& id) const; + const cucumber::messages::parameter_type& FindParameterTypeByName(const std::string& name) const; + bool ContainsParameterTypeByName(const std::string& name) const; + + const cucumber::messages::test_case& FindTestCaseById(const std::string& id) const; + + const cucumber::messages::pickle& FindPickleById(const std::string& id) const; + const cucumber::messages::pickle_step& FindPickleStepById(const std::string& id) const; + + const cucumber::messages::step_definition& FindStepDefinitionById(const std::string& id) const; + + const std::map& TestCaseStarted() const; + const std::map& TestCaseFinishedByTestCaseStartedId() const; + + private: + void + operator+=(const cucumber::messages::gherkin_document& gherkinDocument); + void operator+=(const cucumber::messages::pickle& pickle); + void operator+=(const cucumber::messages::hook& hook); + void operator+=(const cucumber::messages::step_definition& stepDefinition); + void operator+=(const cucumber::messages::test_run_started& testRunStarted); + void operator+=(const cucumber::messages::test_run_hook_started& testRunHookStarted); + void operator+=(const cucumber::messages::test_run_hook_finished& testRunHookFinished); + void operator+=(const cucumber::messages::test_case& testCase); + void operator+=(const cucumber::messages::test_case_started& testCaseStarted); + void operator+=(const cucumber::messages::test_step_started& testStepStarted); + void operator+=(const cucumber::messages::attachment& attachment); + void operator+=(const cucumber::messages::test_step_finished& testStepFinished); + void operator+=(const cucumber::messages::test_case_finished& testCaseFinished); + void operator+=(const cucumber::messages::test_run_finished& testRunFinished); + void operator+=(const cucumber::messages::suggestion& suggestion); + void operator+=(const cucumber::messages::undefined_parameter_type& undefinedParameterType); + + void operator+=(std::pair feature); + void operator+=(std::pair scenario); + void operator+=(std::pair rule); + void operator+=(std::span steps); + + void operator+=(const cucumber::messages::parameter_type& parameterType); + + std::map featureCountByName; + + std::forward_list testStepResults; + std::map> testStepResultByPickleId; + std::map> testStepResultsByPickleStepId; + std::map> testStepResultsbyTestStepId; + + std::map testCaseById; + std::map testCaseByPickleId; + + std::map pickleIdByTestStepId; + std::map pickleStepIdByTestStepId; + std::map> testStepIdsByPickleStepId; + std::map hooksById; + std::forward_list attachments; + std::map> attachmentsByTestStepId; + std::map> attachmentsByTestCaseStartedId; + // std::map>> attachmentsByTestRunHookStartedId; + + std::map> stepMatchArgumentsListsByPickleStepId; + + std::unique_ptr meta; + std::unique_ptr testRunStarted; + std::unique_ptr testRunFinished; + + std::map testCaseStartedById; + std::map testCaseFinishedByTestCaseStartedId; + + std::map lineageById; + std::map lineageByUri; + + std::map stepById; + std::map pickleById; + std::map pickleStepById; + std::map stepDefinitionById; + std::map testStepById; + std::map testRunHookStartedById; + std::map testRunHookFinishedByTestRunHookStartedId; + std::map> testStepStartedByTestCaseStartedId; + std::map> testStepFinishedByTestCaseStartedId; + + std::map> suggestionsByPickleStepId; + std::forward_list undefinedParameterTypes; + + std::map parameterTypeById; + std::map parameterTypeByName; + }; +} + +#endif diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index 0e9ef169..33d42193 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -1,6 +1,14 @@ #include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/group.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern_type.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" @@ -8,7 +16,10 @@ #include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/engine/Table.hpp" #include +#include #include +#include +#include #include #include #include @@ -17,18 +28,19 @@ namespace cucumber_cpp::library { - StepRegistry::StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry) + StepRegistry::StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, cucumber::gherkin::id_generator_ptr idGenerator) : parameterRegistry{ parameterRegistry } + , idGenerator{ idGenerator } { for (const auto& matcher : StepStringRegistration::Instance().GetEntries()) - Register(matcher.regex, matcher.type, matcher.factory); + Register(matcher.regex, matcher.type, matcher.factory, matcher.sourceLocation); } StepMatch StepRegistry::Query(const std::string& expression) { std::vector matches; - for (Entry& entry : registry) + for (auto& [id, entry] : registry) { auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, entry.regex); if (match) @@ -47,6 +59,22 @@ namespace cucumber_cpp::library return std::move(matches.front()); } + [[nodiscard]] std::pair, std::vector> StepRegistry::FindDefinitions(const std::string& expression) + { + std::pair, std::vector> result; + + for (auto& [id, entry] : registry) + { + const auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, entry.regex); + if (match) + { + result.first.push_back(id); + } + } + + return result; + } + std::size_t StepRegistry::Size() const { return registry.size(); @@ -58,18 +86,64 @@ namespace cucumber_cpp::library list.reserve(registry.size()); - for (const Entry& entry : registry) + for (const auto& [id, entry] : registry) list.emplace_back(entry.regex, entry.used); return list; } - void StepRegistry::Register(const std::string& matcher, engine::StepType stepType, std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString)) + StepFactory StepRegistry::GetFactoryById(std::string id) const + { + return registry.at(id).factory; + } + + StepRegistry::Definition StepRegistry::GetDefinitionById(std::string id) const { + return registry.at(id); + } + + const std::map& StepRegistry::StepDefinitions() const + { + return registry; + } + + void StepRegistry::Register(const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) + { + auto id = idGenerator->next_id(); + if (matcher.starts_with('^') || matcher.ends_with('$')) - registry.emplace_back(stepType, cucumber_expression::Matcher{ std::in_place_type, matcher }, factory); + { + registry.emplace(id, Definition{ + factory, + "id", + sourceLocation.line(), + sourceLocation.file_name(), + stepType, + matcher, + cucumber_expression::Matcher{ + std::in_place_type, + matcher, + }, + cucumber::messages::step_definition_pattern_type::REGULAR_EXPRESSION, + }); + } else - registry.emplace_back(stepType, cucumber_expression::Matcher{ std::in_place_type, matcher, parameterRegistry }, factory); + { + registry.emplace(id, Definition{ + factory, + "id", + sourceLocation.line(), + sourceLocation.file_name(), + stepType, + matcher, + cucumber_expression::Matcher{ + std::in_place_type, + matcher, + parameterRegistry, + }, + cucumber::messages::step_definition_pattern_type::CUCUMBER_EXPRESSION, + }); + } } StepStringRegistration& StepStringRegistration::Instance() diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index e6dc4746..ab681d91 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -1,16 +1,25 @@ #ifndef CUCUMBER_CPP_STEPREGISTRY_HPP #define CUCUMBER_CPP_STEPREGISTRY_HPP +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern_type.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/engine/Table.hpp" #include +#include #include #include +#include +#include #include +#include #include #include #include @@ -20,6 +29,9 @@ namespace cucumber_cpp::library { + + using StepFactory = std::unique_ptr (&)(Context&, const engine::Table&, const std::string&); + template std::unique_ptr StepBodyFactory(Context& context, const engine::Table& table, const std::string& docString) { @@ -28,13 +40,13 @@ namespace cucumber_cpp::library struct StepMatch { - StepMatch(std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString), std::variant, std::vector> matches, std::string_view stepRegexStr) + StepMatch(StepFactory factory, 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, const std::string& docString); + StepFactory factory; std::variant, std::vector> matches{}; std::string_view stepRegexStr{}; }; @@ -55,17 +67,20 @@ namespace cucumber_cpp::library std::vector matches; }; - struct Entry + struct StepDefinition { - 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) - {} + StepFactory factory; + std::string id; + std::size_t line; + std::filesystem::path uri; + }; - engine::StepType type{}; + struct Definition : StepDefinition + { + engine::StepType type; + std::string pattern; cucumber_expression::Matcher regex; - std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString); + cucumber::messages::step_definition_pattern_type patternType; std::uint32_t used{ 0 }; }; @@ -81,19 +96,27 @@ namespace cucumber_cpp::library const std::uint32_t& used; }; - explicit StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry); + explicit StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, cucumber::gherkin::id_generator_ptr idGenerator); [[nodiscard]] StepMatch Query(const std::string& expression); + [[nodiscard]] std::pair, std::vector> FindDefinitions(const std::string& expression); [[nodiscard]] std::size_t Size() const; [[nodiscard]] std::vector List() const; + StepFactory GetFactoryById(std::string id) const; + Definition GetDefinitionById(std::string id) const; + + const std::map& StepDefinitions() const; + private: - void Register(const std::string& matcher, engine::StepType stepType, std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString)); + void Register(const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); - std::vector registry; cucumber_expression::ParameterRegistry& parameterRegistry; + cucumber::gherkin::id_generator_ptr idGenerator; + + std::map registry; }; struct StepStringRegistration @@ -106,19 +129,21 @@ namespace cucumber_cpp::library struct Entry { - 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) + Entry(engine::StepType type, std::string regex, StepFactory factory, std::source_location sourceLocation) + : type{ type } + , regex{ std::move(regex) } + , factory{ factory } + , sourceLocation{ sourceLocation } {} engine::StepType type{}; std::string regex; - std::unique_ptr (&factory)(Context& context, const engine::Table& table, const std::string& docString); + StepFactory factory; + std::source_location sourceLocation; }; template - static std::size_t Register(const std::string& matcher, engine::StepType stepType); + static std::size_t Register(const std::string& matcher, engine::StepType stepType, std::source_location sourceLocation = std::source_location::current()); std::span GetEntries(); [[nodiscard]] std::span GetEntries() const; @@ -132,9 +157,9 @@ namespace cucumber_cpp::library ////////////////////////// template - std::size_t StepStringRegistration::Register(const std::string& matcher, engine::StepType stepType) + std::size_t StepStringRegistration::Register(const std::string& matcher, engine::StepType stepType, std::source_location sourceLocation) { - Instance().registry.emplace_back(stepType, matcher, StepBodyFactory); + Instance().registry.emplace_back(stepType, matcher, StepBodyFactory, sourceLocation); return Instance().registry.size(); } diff --git a/cucumber_cpp/library/TagExpression.cpp b/cucumber_cpp/library/TagExpression.cpp deleted file mode 100644 index 42370d77..00000000 --- a/cucumber_cpp/library/TagExpression.cpp +++ /dev/null @@ -1,74 +0,0 @@ -#include "cucumber_cpp/library/TagExpression.hpp" -#include "cucumber_cpp/library/Errors.hpp" -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library -{ - namespace - { - void Replace(std::string& str, const std::string& from, const std::string& to) - { - str = std::regex_replace(str, std::regex(from), to); - } - } - - bool IsTagExprSelected(std::string_view tagExpr, const std::set>& tags) - { - if (tagExpr.empty()) - { - return true; - } - - std::string eval{ tagExpr }; - - for (std::smatch matches; std::regex_search(eval, matches, std::regex(R"((@[^ \)]+))"));) - { - Replace(eval, matches[1], tags.contains(matches[1]) ? "1" : "0"); - } - - Replace(eval, "not", "!"); - Replace(eval, "or", "|"); - Replace(eval, "and", "&"); - Replace(eval, " ", ""); - - for (;;) - { - auto s = eval.size(); - - Replace(eval, R"(\(0\))", "0"); - Replace(eval, R"(\(1\))", "1"); - Replace(eval, "!0", "1"); - Replace(eval, "!1", "0"); - - Replace(eval, "0&0", "0"); - Replace(eval, "0&1", "0"); - Replace(eval, "1&0", "0"); - Replace(eval, "1&1", "1"); - Replace(eval, "00", "0"); - Replace(eval, "01", "0"); - Replace(eval, "10", "0"); - Replace(eval, "11", "1"); - - Replace(eval, R"(0\|0)", "0"); - Replace(eval, R"(0\|1)", "1"); - Replace(eval, R"(1\|0)", "1"); - Replace(eval, R"(1\|1)", "1"); - - if (s == eval.size()) - { - break; - } - } - - if (eval.size() != 1) - { - throw InternalError("Could not parse tag expression: \"" + std::string{ tagExpr } + "\""); - } - - return eval == std::string("1"); - } -} diff --git a/cucumber_cpp/library/TagExpression.hpp b/cucumber_cpp/library/TagExpression.hpp deleted file mode 100644 index 3734f0bb..00000000 --- a/cucumber_cpp/library/TagExpression.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef CUCUMBER_CPP_TAGEXPRESSION_HPP -#define CUCUMBER_CPP_TAGEXPRESSION_HPP - -#include -#include -#include -#include - -namespace cucumber_cpp::library -{ - bool IsTagExprSelected(std::string_view tagExpr, const std::set>& tags); -} - -#endif diff --git a/cucumber_cpp/library/TagsToSet.hpp b/cucumber_cpp/library/TagsToSet.hpp deleted file mode 100644 index 58665f3b..00000000 --- a/cucumber_cpp/library/TagsToSet.hpp +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef CUCUMBER_CPP_TAGSTOSET_HPP -#define CUCUMBER_CPP_TAGSTOSET_HPP - -#include -#include -#include - -namespace cucumber_cpp::library -{ - auto TagsToSet(const auto& tags) - { - std::set> result; - - for (const auto& entry : tags) - result.insert(entry.name); - - return result; - } -} - -#endif diff --git a/cucumber_cpp/library/api/CMakeLists.txt b/cucumber_cpp/library/api/CMakeLists.txt new file mode 100644 index 00000000..5334f26d --- /dev/null +++ b/cucumber_cpp/library/api/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(cucumber_cpp.library.api STATIC ${CCR_EXCLUDE_FROM_ALL}) + +target_sources(cucumber_cpp.library.api PRIVATE + Gherkin.cpp + Gherkin.hpp + RunCucumber.cpp + RunCucumber.hpp +) + +target_include_directories(cucumber_cpp.library.api PUBLIC + ../../.. +) + +target_link_libraries(cucumber_cpp.library.api PUBLIC + # cucumber_cpp.library + # cucumber_cpp.library.util + cucumber_cpp.library.cucumber_expression +) + +if (CCR_BUILD_TESTS) + # add_subdirectory(test) + # add_subdirectory(test_helper) +endif() diff --git a/cucumber_cpp/library/api/Gherkin.cpp b/cucumber_cpp/library/api/Gherkin.cpp new file mode 100644 index 00000000..66330fdb --- /dev/null +++ b/cucumber_cpp/library/api/Gherkin.cpp @@ -0,0 +1,78 @@ +#include "cucumber_cpp/library/api/Gherkin.hpp" +#include "cucumber/gherkin/app.hpp" +#include "cucumber/gherkin/exceptions.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/gherkin/pickle_compiler.hpp" +#include "cucumber/gherkin/utils.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/parse_error.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/source_reference.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include + +namespace cucumber_cpp::library::api +{ + std::vector CollectPickles(const support::RunOptions::Sources& sources, cucumber::gherkin::id_generator_ptr idGenerator, util::Broadcaster& broadcaster) + { + std::vector pickleSources; + + cucumber::gherkin::parser<> parser{ idGenerator }; + + cucumber::messages::envelope envelope; + + for (const auto& path : sources.paths) + { + envelope.source = { + .uri = path.string(), + .data = cucumber::gherkin::slurp(path), + }; + + broadcaster.BroadcastEvent(envelope); + + try + { + auto ast = std::make_shared(parser.parse(envelope.source->uri, envelope.source->data)); + broadcaster.BroadcastEvent({ .gherkin_document = *ast }); + + cucumber::gherkin::pickle_compiler pc(idGenerator); + pc.compile(*ast, envelope.source->uri, [&pickleSources, ast, &broadcaster](const auto& pickle) + { + pickleSources.emplace_back( + std::make_shared(pickle), + ast); + + broadcaster.BroadcastEvent({ .pickle = pickle }); + }); + } + catch (const cucumber::gherkin::composite_parser_error& compositeError) + { + for (const auto& error : compositeError.errors()) + { + broadcaster.BroadcastEvent({ .parse_error = cucumber::messages::parse_error{ + .source = cucumber::messages::source_reference{ + .uri = envelope.source->uri, + .location = error->location(), + }, + .message = error->what(), + } }); + } + } + catch (const cucumber::gherkin::parser_error& error) + { + broadcaster.BroadcastEvent({ .parse_error = cucumber::messages::parse_error{ + .source = cucumber::messages::source_reference{ + .uri = envelope.source->uri, + .location = error.location(), + }, + .message = error.what(), + } }); + } + } + + return pickleSources; + } +} diff --git a/cucumber_cpp/library/api/Gherkin.hpp b/cucumber_cpp/library/api/Gherkin.hpp new file mode 100644 index 00000000..cb08b994 --- /dev/null +++ b/cucumber_cpp/library/api/Gherkin.hpp @@ -0,0 +1,14 @@ +#ifndef API_GHERKIN_HPP +#define API_GHERKIN_HPP + +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include + +namespace cucumber_cpp::library::api +{ + std::vector CollectPickles(const support::RunOptions::Sources& sources, cucumber::gherkin::id_generator_ptr idGenerator, util::Broadcaster& broadcaster); +} + +#endif diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp new file mode 100644 index 00000000..8423c9a5 --- /dev/null +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -0,0 +1,92 @@ +#include "cucumber_cpp/library/api/RunCucumber.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/api/Gherkin.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" +#include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/runtime/MakeRuntime.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::api +{ + namespace + { + void EmitParameters(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) + { + for (const auto& [name, parameter] : supportCodeLibrary.parameterRegistry.GetParameters()) + { + broadcaster.BroadcastEvent({ .parameter_type = cucumber::messages::parameter_type{ + .name = parameter.name, + .regular_expressions = parameter.regex, + .id = idGenerator->next_id(), + } }); + } + } + + void EmitStepDefinitions(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) + { + for (const auto& [name, stepDefinition] : supportCodeLibrary.stepRegistry.StepDefinitions()) + { + broadcaster.BroadcastEvent({ .step_definition = cucumber::messages::step_definition{ + .id = stepDefinition.id, + .pattern = cucumber::messages::step_definition_pattern{ + .source = stepDefinition.pattern, + .type = stepDefinition.patternType, + }, + } }); + } + } + + void EmitSupportCodeMessages(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) + { + EmitParameters(supportCodeLibrary, broadcaster, idGenerator); + // undefined parameters + EmitStepDefinitions(supportCodeLibrary, broadcaster, idGenerator); + // test case hooks + // test run hooks + } + } + + bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster) + { + cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); + + StepRegistry stepRegistry{ parameterRegistry, idGenerator }; + HookRegistry hookRegistry{}; + + support::SupportCodeLibrary supportCodeLibrary{ .hookRegistry = hookRegistry, .stepRegistry = stepRegistry, .parameterRegistry = parameterRegistry }; + + formatter::helper::EventDataCollector eventDataCollector{ broadcaster }; + formatter::PrettyPrinter prettyPrinter{ supportCodeLibrary, broadcaster, eventDataCollector }; + formatter::SummaryFormatter summaryFormatter{ supportCodeLibrary, broadcaster, eventDataCollector }; + + const auto pickleSources = CollectPickles(options.sources, idGenerator, broadcaster); + + const auto tagExpression = cucumber_cpp::library::tag_expression::Parse(options.sources.tagExpression); + const auto pickleFilter = [&tagExpression](const support::PickleSource& pickle) + { + return tagExpression->Evaluate(pickle.pickle->tags); + }; + auto filteredPicklesView = pickleSources | std::views::filter(pickleFilter); + std::vector filteredPickles(filteredPicklesView.begin(), filteredPicklesView.end()); + + EmitSupportCodeMessages(supportCodeLibrary, broadcaster, idGenerator); + + auto runtime = runtime::MakeRuntime(options.runtime, broadcaster, filteredPickles, supportCodeLibrary, idGenerator, programContext); + + return runtime->Run(); + } +} diff --git a/cucumber_cpp/library/api/RunCucumber.hpp b/cucumber_cpp/library/api/RunCucumber.hpp new file mode 100644 index 00000000..f343ee5d --- /dev/null +++ b/cucumber_cpp/library/api/RunCucumber.hpp @@ -0,0 +1,14 @@ +#ifndef API_RUN_CUCUMBER_HPP +#define API_RUN_CUCUMBER_HPP + +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" + +namespace cucumber_cpp::library::api +{ + bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster); +} + +#endif diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp new file mode 100644 index 00000000..753e98ba --- /dev/null +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -0,0 +1,94 @@ +#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::assemble +{ + std::vector AssembleTestSuites(support::SupportCodeLibrary supportCodeLibrary, + std::string_view testRunStartedId, + util::Broadcaster& broadcaster, + std::span sourcedPickles, + cucumber::gherkin::id_generator_ptr idGenerator) + { + std::map assembledTestSuiteMap; + + for (const auto& pickleSource : sourcedPickles) + { + std::vector allSteps; + allSteps.reserve(pickleSource.pickle->steps.size() * 2); // steps + hooks + + for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::before, pickleSource.pickle->tags)) + allSteps.emplace_back(hookId, idGenerator->next_id()); + + for (const auto& step : pickleSource.pickle->steps) + { + auto definitions = supportCodeLibrary.stepRegistry.FindDefinitions(step.text); + const auto& stepDefinitions = supportCodeLibrary.stepRegistry.StepDefinitions(); + + std::vector stepDefinitionIds; + std::vector stepMatchArgumentsLists; + + for (const auto& [id, definition] : stepDefinitions) + { + auto optionalStepMatchArgumentsList = std::visit(cucumber_expression::MatchArgumentsVisitor{ step.text }, definition.regex); + + if (optionalStepMatchArgumentsList) + { + stepDefinitionIds.push_back(id); + stepMatchArgumentsLists.push_back(*optionalStepMatchArgumentsList); + } + } + + allSteps.emplace_back( + std::nullopt, + idGenerator->next_id(), + step.id, + stepDefinitionIds, + stepMatchArgumentsLists); + } + + for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags)) + allSteps.emplace_back(hookId, idGenerator->next_id()); + + cucumber::messages::test_case testCase{ + .id = idGenerator->next_id(), + .pickle_id = pickleSource.pickle->id, + .test_steps = std::move(allSteps), + .test_run_started_id = std::make_optional(testRunStartedId) + }; + + broadcaster.BroadcastEvent(cucumber::messages::envelope{ .test_case = testCase }); + + if (!assembledTestSuiteMap.contains(pickleSource.gherkinDocument->uri.value())) + assembledTestSuiteMap.emplace(pickleSource.gherkinDocument->uri.value(), *pickleSource.gherkinDocument); + + assembledTestSuiteMap.at(pickleSource.gherkinDocument->uri.value()).testCases.emplace_back(*pickleSource.pickle, testCase); + } + + std::vector assembledTestSuites; + assembledTestSuites.reserve(assembledTestSuiteMap.size()); + + for (auto assembledTestSuiteValues : assembledTestSuiteMap | std::views::values) + assembledTestSuites.emplace_back(std::move(assembledTestSuiteValues)); + + return assembledTestSuites; + } +} diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.hpp b/cucumber_cpp/library/assemble/AssembleTestSuites.hpp new file mode 100644 index 00000000..e488ac1e --- /dev/null +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.hpp @@ -0,0 +1,23 @@ +#ifndef ASSEMBLE_ASSEMBLE_TEST_SUITES_HPP +#define ASSEMBLE_ASSEMBLE_TEST_SUITES_HPP + +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::assemble +{ + std::vector AssembleTestSuites( + support::SupportCodeLibrary supportCodeLibrary, + std::string_view testRunStartedId, + util::Broadcaster& broadcaster, + std::span sourcedPickles, + cucumber::gherkin::id_generator_ptr idGenerator); +} + +#endif diff --git a/cucumber_cpp/library/assemble/AssembledTestCase.hpp b/cucumber_cpp/library/assemble/AssembledTestCase.hpp new file mode 100644 index 00000000..abcbe947 --- /dev/null +++ b/cucumber_cpp/library/assemble/AssembledTestCase.hpp @@ -0,0 +1,16 @@ +#ifndef ASSEMBLE_ASSEMBLED_TEST_CASE_HPP +#define ASSEMBLE_ASSEMBLED_TEST_CASE_HPP + +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/test_case.hpp" + +namespace cucumber_cpp::library::assemble +{ + struct AssembledTestCase + { + const cucumber::messages::pickle& pickle; + const cucumber::messages::test_case testCase; + }; +} + +#endif diff --git a/cucumber_cpp/library/assemble/AssembledTestSuite.hpp b/cucumber_cpp/library/assemble/AssembledTestSuite.hpp new file mode 100644 index 00000000..69af6793 --- /dev/null +++ b/cucumber_cpp/library/assemble/AssembledTestSuite.hpp @@ -0,0 +1,17 @@ +#ifndef ASSEMBLE_ASSEMBLED_TEST_SUITE_HPP +#define ASSEMBLE_ASSEMBLED_TEST_SUITE_HPP + +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" +#include + +namespace cucumber_cpp::library::assemble +{ + struct AssembledTestSuite + { + const cucumber::messages::gherkin_document& gherkinDocument; + std::vector testCases; + }; +} + +#endif diff --git a/cucumber_cpp/library/assemble/CMakeLists.txt b/cucumber_cpp/library/assemble/CMakeLists.txt new file mode 100644 index 00000000..d245deeb --- /dev/null +++ b/cucumber_cpp/library/assemble/CMakeLists.txt @@ -0,0 +1,23 @@ +add_library(cucumber_cpp.library.assemble STATIC ${CCR_EXCLUDE_FROM_ALL}) + +target_sources(cucumber_cpp.library.assemble PRIVATE + AssembledTestCase.hpp + AssembledTestSuite.hpp + AssembleTestSuites.cpp + AssembleTestSuites.hpp +) + +target_include_directories(cucumber_cpp.library.assemble PUBLIC + ../../.. +) + +target_link_libraries(cucumber_cpp.library.assemble PUBLIC + # cucumber_cpp.library + # cucumber_cpp.library.util + cucumber_cpp.library.cucumber_expression +) + +if (CCR_BUILD_TESTS) + # add_subdirectory(test) + # add_subdirectory(test_helper) +endif() diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index c68f546c..c0ae28ad 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -11,6 +11,8 @@ target_sources(cucumber_cpp.library.cucumber_expression PRIVATE ExpressionParser.hpp ExpressionTokenizer.cpp ExpressionTokenizer.hpp + MatchRange.cpp + MatchRange.hpp ParameterRegistry.cpp ParameterRegistry.hpp ) @@ -19,6 +21,11 @@ target_include_directories(cucumber_cpp.library.cucumber_expression PUBLIC ../../.. ) +target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC + cucumber_gherkin_lib + cucumber_cpp.library +) + if (CCR_BUILD_TESTS) add_subdirectory(test) endif() diff --git a/cucumber_cpp/library/cucumber_expression/Expression.cpp b/cucumber_cpp/library/cucumber_expression/Expression.cpp index b578eaff..de4e03b6 100644 --- a/cucumber_cpp/library/cucumber_expression/Expression.cpp +++ b/cucumber_cpp/library/cucumber_expression/Expression.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber_cpp/library/cucumber_expression/Ast.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/ExpressionParser.hpp" @@ -51,7 +52,33 @@ namespace cucumber_cpp::library::cucumber_expression while (matchIter != smatch.end() && converterIter != converters.end()) { - result.emplace_back(converterIter->converter({ matchIter, matchIter + converterIter->matches })); + const auto stringArgs = converterIter->converter.toStrings({ matchIter, matchIter + converterIter->matches }); + result.emplace_back(converterIter->converter.toAny(stringArgs)); + matchIter = std::next(matchIter, converterIter->matches); + converterIter = std::next(converterIter); + } + + return result; + } + + std::optional Expression::MatchArguments(const std::string& text) const + { + std::smatch smatch; + if (!std::regex_search(text, smatch, regex)) + return std::nullopt; + + cucumber::messages::step_match_arguments_list result; + result.step_match_arguments.reserve(converters.size()); + + auto converterIter = converters.begin(); + auto matchIter = smatch.begin() + 1; + + while (matchIter != smatch.end() && converterIter != converters.end()) + { + auto groups = converterIter->converter.toStrings({ matchIter, matchIter + converterIter->matches }); + result.step_match_arguments.emplace_back( + groups, + converterIter->name); matchIter = std::next(matchIter, converterIter->matches); converterIter = std::next(converterIter); @@ -148,25 +175,32 @@ namespace cucumber_cpp::library::cucumber_expression std::string Expression::RewriteParameter(const Node& node) { - auto parameter = parameterRegistry.Lookup(node.Text()); - if (parameter.regex.empty()) - throw UndefinedParameterTypeError(node, expression, node.Text()); - - converters.emplace_back(0u, parameter.converter); - - std::string partialRegex{}; - if (parameter.regex.size() == 1) - partialRegex = std::format(R"(({}))", parameter.regex.front()); - else + try { - partialRegex = { parameter.regex.front() }; - for (const auto& parameterRegex : parameter.regex | std::views::drop(1)) - partialRegex += R"()|(?:)" + parameterRegex; - partialRegex = std::format(R"(((?:{})))", partialRegex); + auto parameter = parameterRegistry.Lookup(node.Text()); + if (parameter.regex.empty()) + throw UndefinedParameterTypeError(node, expression, node.Text()); + + auto& converter = converters.emplace_back(0u, parameter.converter, parameter.name); + + std::string partialRegex{}; + if (parameter.regex.size() == 1) + partialRegex = std::format(R"(({}))", parameter.regex.front()); + else + { + partialRegex = { parameter.regex.front() }; + for (const auto& parameterRegex : parameter.regex | std::views::drop(1)) + partialRegex += R"()|(?:)" + parameterRegex; + partialRegex = std::format(R"(((?:{})))", partialRegex); + } + + converter.matches += std::regex{ partialRegex }.mark_count(); + return partialRegex; + } + catch (const std::out_of_range&) + { + throw UndefinedParameterTypeError(node, expression, node.Text()); } - - converters.back().matches += std::regex{ partialRegex }.mark_count(); - return partialRegex; } std::string Expression::RewriteExpression(const Node& node) diff --git a/cucumber_cpp/library/cucumber_expression/Expression.hpp b/cucumber_cpp/library/cucumber_expression/Expression.hpp index b07fbeab..8c7c0a1d 100644 --- a/cucumber_cpp/library/cucumber_expression/Expression.hpp +++ b/cucumber_cpp/library/cucumber_expression/Expression.hpp @@ -1,12 +1,12 @@ #ifndef CUCUMBER_EXPRESSION_EXPRESSION_HPP #define CUCUMBER_EXPRESSION_EXPRESSION_HPP +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber_cpp/library/cucumber_expression/Ast.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include #include #include -#include #include #include #include @@ -24,8 +24,11 @@ namespace cucumber_cpp::library::cucumber_expression std::string_view Pattern() const; std::optional> Match(const std::string& text) const; + std::optional MatchArguments(const std::string& text) const; + private: - std::string RewriteToRegex(const Node& node); + std::string + RewriteToRegex(const Node& node); std::string EscapeRegex(std::string_view text) const; std::string RewriteOptional(const Node& node); std::string RewriteAlternation(const Node& node); diff --git a/cucumber_cpp/library/cucumber_expression/MatchRange.cpp b/cucumber_cpp/library/cucumber_expression/MatchRange.cpp new file mode 100644 index 00000000..9394c9b0 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/MatchRange.cpp @@ -0,0 +1,23 @@ + +#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + + std::smatch::const_iterator MatchRange::begin() const + { + return first; + } + + std::smatch::const_iterator MatchRange::end() const + { + return second; + } + + const std::ssub_match& MatchRange::operator[](std::size_t index) const + { + return *std::next(begin(), index); + } +} diff --git a/cucumber_cpp/library/cucumber_expression/MatchRange.hpp b/cucumber_cpp/library/cucumber_expression/MatchRange.hpp new file mode 100644 index 00000000..239a1650 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/MatchRange.hpp @@ -0,0 +1,20 @@ +#ifndef CUCUMBER_EXPRESSION_MATCH_RANGE_HPP +#define CUCUMBER_EXPRESSION_MATCH_RANGE_HPP + +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + struct MatchRange : std::pair + { + using std::pair::pair; + + std::smatch::const_iterator begin() const; + std::smatch::const_iterator end() const; + + const std::ssub_match& operator[](std::size_t index) const; + }; +} + +#endif diff --git a/cucumber_cpp/library/cucumber_expression/Matcher.hpp b/cucumber_cpp/library/cucumber_expression/Matcher.hpp index 214611be..f190c81e 100644 --- a/cucumber_cpp/library/cucumber_expression/Matcher.hpp +++ b/cucumber_cpp/library/cucumber_expression/Matcher.hpp @@ -1,6 +1,7 @@ #ifndef CUCUMBER_EXPRESSION_MATCHER_HPP #define CUCUMBER_EXPRESSION_MATCHER_HPP +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/RegularExpression.hpp" #include @@ -40,6 +41,16 @@ namespace cucumber_cpp::library::cucumber_expression const std::string& text; }; + + struct MatchArgumentsVisitor + { + std::optional operator()(const auto& expression) const + { + return expression.MatchArguments(text); + } + + const std::string& text; + }; } #endif diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index 670e86f2..4d83c8d1 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -1,15 +1,16 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" +#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" #include #include #include #include #include #include -#include -#include #include +#include #include #include #include @@ -20,44 +21,38 @@ namespace cucumber_cpp::library::cucumber_expression namespace { template - std::function CreateStreamConverter() + ParameterConversion CreateStreamConverter() { - return [](const MatchRange& matches) - { - return StringTo(matches.begin()->str()); - }; + return { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group + { + return { .value = matches.begin()->str() }; + }, + .toAny = [](const cucumber::messages::group& matches) -> std::any + { + return StringTo(matches.value.value()); + } }; } - std::function CreateStringConverter() + ParameterConversion CreateStringConverter() { - return [](const MatchRange& matches) - { - std::string str = matches[1].matched ? matches[1].str() : matches[3].str(); - str = std::regex_replace(str, std::regex(R"__(\\")__"), "\""); - str = std::regex_replace(str, std::regex(R"__(\\')__"), "'"); - return str; - }; + return { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group + { + std::string str = matches[1].matched ? matches[1].str() : matches[3].str(); + str = std::regex_replace(str, std::regex(R"__(\\")__"), "\""); + str = std::regex_replace(str, std::regex(R"__(\\')__"), "'"); + return { .value = str }; + }, + .toAny = [](const cucumber::messages::group& matches) -> std::any + { + return matches.value.value(); + } }; } } - std::smatch::const_iterator MatchRange::begin() const - { - return first; - } - - std::smatch::const_iterator MatchRange::end() const - { - return second; - } - - const std::ssub_match& MatchRange::operator[](std::size_t index) const - { - return *std::next(begin(), index); - } - - Converter::Converter(std::size_t matches, std::function converter) + Converter::Converter(std::size_t matches, ParameterConversion converter, std::string name) : matches{ matches } , converter{ std::move(converter) } + , name{ std::move(name) } {} ParameterRegistry::ParameterRegistry() @@ -85,16 +80,19 @@ namespace cucumber_cpp::library::cucumber_expression AddParameter("bool", { wordRegex }, CreateStreamConverter()); } - Parameter ParameterRegistry::Lookup(const std::string& name) const + const std::map& ParameterRegistry::GetParameters() const + { + return parametersByName; + } + + const Parameter& ParameterRegistry::Lookup(const std::string& name) const { - if (parameters.contains(name)) - return parameters.at(name); - return {}; + return parametersByName.at(name); } - void ParameterRegistry::AddParameter(std::string name, std::vector regex, std::function converter) + void ParameterRegistry::AddParameter(std::string name, std::vector regex, ParameterConversion converter) { - if (parameters.contains(name)) + if (parametersByName.contains(name)) { if (name.empty()) throw CucumberExpressionError{ "The anonymous parameter type has already been defined" }; @@ -102,6 +100,7 @@ namespace cucumber_cpp::library::cucumber_expression throw CucumberExpressionError{ std::format("There is already a parameter with name {}", name) }; } - parameters[name] = Parameter{ name, std::move(regex), std::move(converter) }; + parametersByName.emplace(name, Parameter{ name, regex, converter }); } + } diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp index fcd2c44b..53c5cdad 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp @@ -1,6 +1,8 @@ #ifndef CUCUMBER_EXPRESSION_PARAMETERREGISTRY_HPP #define CUCUMBER_EXPRESSION_PARAMETERREGISTRY_HPP +#include "cucumber/messages/group.hpp" +#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" #include #include #include @@ -9,14 +11,11 @@ #include #include #include -#include #include -#include #include #include #include #include -#include #include namespace cucumber_cpp::library::cucumber_expression @@ -95,29 +94,26 @@ namespace cucumber_cpp::library::cucumber_expression return iequals(s, "true") || iequals(s, "1") || iequals(s, "yes") || iequals(s, "on") || iequals(s, "enabled") || iequals(s, "active"); } - struct MatchRange : std::pair + struct ParameterConversion { - using std::pair::pair; - - std::smatch::const_iterator begin() const; - std::smatch::const_iterator end() const; - - const std::ssub_match& operator[](std::size_t index) const; + std::function toStrings; + std::function toAny; }; struct Converter { - Converter(std::size_t matches, std::function converter); + Converter(std::size_t matches, ParameterConversion converter, std::string name); + std::string name; std::size_t matches; - std::function converter; + ParameterConversion converter; }; struct Parameter { std::string name; std::vector regex; - std::function converter; + ParameterConversion converter; }; struct ParameterRegistration @@ -126,7 +122,7 @@ namespace cucumber_cpp::library::cucumber_expression ~ParameterRegistration() = default; public: - virtual void AddParameter(std::string name, std::vector regex, std::function converter) = 0; + virtual void AddParameter(std::string name, std::vector regex, ParameterConversion converter) = 0; }; struct ParameterRegistry : ParameterRegistration @@ -135,11 +131,14 @@ namespace cucumber_cpp::library::cucumber_expression virtual ~ParameterRegistry() = default; - Parameter Lookup(const std::string& name) const; - void AddParameter(std::string name, std::vector regex, std::function converter) override; + const std::map& GetParameters() const; + + const Parameter& Lookup(const std::string& name) const; + + void AddParameter(std::string name, std::vector regex, ParameterConversion converter) override; private: - std::map> parameters{}; + std::map parametersByName; }; } diff --git a/cucumber_cpp/library/cucumber_expression/RegularExpression.hpp b/cucumber_cpp/library/cucumber_expression/RegularExpression.hpp index 271b1b3f..011a9f12 100644 --- a/cucumber_cpp/library/cucumber_expression/RegularExpression.hpp +++ b/cucumber_cpp/library/cucumber_expression/RegularExpression.hpp @@ -1,6 +1,8 @@ #ifndef CUCUMBER_EXPRESSION_REGULAREXPRESSION_HPP #define CUCUMBER_EXPRESSION_REGULAREXPRESSION_HPP +#include "cucumber/messages/group.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" #include #include #include @@ -43,6 +45,21 @@ namespace cucumber_cpp::library::cucumber_expression return result; } + std::optional MatchArguments(const std::string& text) const + { + std::smatch smatch; + if (!std::regex_search(text, smatch, regex)) + return {}; + + cucumber::messages::step_match_arguments_list result{}; + result.step_match_arguments.reserve(smatch.size() - 1); + + for (const auto& match : smatch | std::views::drop(1)) + result.step_match_arguments.emplace_back(cucumber::messages::group{ .value = match.str() }); + + return result; + } + private: std::string expression; std::regex regex; diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index a13f0bb7..b26e4858 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -1,6 +1,8 @@ +#include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" @@ -12,11 +14,9 @@ #include #include #include -#include #include -#include -#include #include +#include #include #include #include @@ -78,7 +78,7 @@ namespace cucumber_cpp::library::cucumber_expression ASSERT_THAT(matchOpt, testing::IsTrue()) << FormatMessage(testdata, expression); - const auto match = *matchOpt; + const auto& match = *matchOpt; for (std::size_t i = 0; i < testdata["expected_args"].size(); ++i) { if (match[i].type() == typeid(std::string)) @@ -155,13 +155,20 @@ namespace cucumber_cpp::library::cucumber_expression std::optional number; }; - parameterRegistry.AddParameter("textAndOrNumber", { R"(([A-Z]+)?(?: )?([0-9]+)?)" }, [](MatchRange matches) -> std::any - { - std::optional text{ matches[1].matched ? StringTo(matches[1].str()) : std::optional{ std::nullopt } }; - std::optional number{ matches[2].matched ? StringTo(matches[2].str()) : std::optional{ std::nullopt } }; + parameterRegistry.AddParameter("textAndOrNumber", { R"(([A-Z]+)?(?: )?([0-9]+)?)" }, + { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group + { + std::optional text{ matches[1].matched ? matches[1].str() : std::optional{ std::nullopt } }; + std::optional number{ matches[2].matched ? matches[2].str() : std::optional{ std::nullopt } }; - return CustomType{ text, number }; - }); + return { .children = { cucumber::messages::group{ .value = text }, cucumber::messages::group{ .value = number } } }; + }, + .toAny = [](const cucumber::messages::group& matches) -> std::any + { + std::optional text{ matches.children[0].value }; + std::optional number{ matches.children[1].value ? StringTo(matches.children[1].value.value()) : std::optional{ std::nullopt } }; + return CustomType{ text, number }; + } }); auto matchString{ Match(R"__({textAndOrNumber})__", R"__(ABC)__") }; EXPECT_THAT(matchString, testing::IsTrue()); @@ -226,10 +233,15 @@ namespace cucumber_cpp::library::cucumber_expression { try { - parameterRegistry.AddParameter("", { ".*" }, [](const MatchRange& matches) - { - return StringTo(matches.begin()->str()); - }); + parameterRegistry.AddParameter("", { ".*" }, + { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group + { + return { .value = matches.begin()->str() }; + }, + .toAny = [](const cucumber::messages::group& matches) -> std::any + { + return matches.value.value(); + } }); FAIL() << "Expected CucumberExpressionError to be thrown"; } catch (const CucumberExpressionError& e) @@ -242,10 +254,15 @@ namespace cucumber_cpp::library::cucumber_expression { try { - parameterRegistry.AddParameter("word", { ".*" }, [](const MatchRange& matches) - { - return StringTo(matches.begin()->str()); - }); + parameterRegistry.AddParameter("word", { ".*" }, + { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group + { + return { .value = matches.begin()->str() }; + }, + .toAny = [](const cucumber::messages::group& matches) -> std::any + { + return matches.value.value(); + } }); FAIL() << "Expected CucumberExpressionError to be thrown"; } catch (const CucumberExpressionError& e) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp b/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp index 2f5fed80..3d94d83f 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp @@ -1,24 +1,16 @@ -#include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" #include "yaml-cpp/yaml.h" #include "gmock/gmock.h" -#include #include -#include #include #include #include -#include #include -#include -#include -#include -#include -#include +#include #include #include #include diff --git a/cucumber_cpp/library/engine/CMakeLists.txt b/cucumber_cpp/library/engine/CMakeLists.txt index 06594974..413880db 100644 --- a/cucumber_cpp/library/engine/CMakeLists.txt +++ b/cucumber_cpp/library/engine/CMakeLists.txt @@ -1,32 +1,15 @@ add_library(cucumber_cpp.library.engine STATIC ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.engine PRIVATE - ContextManager.cpp - ContextManager.hpp - FeatureFactory.cpp - FeatureFactory.hpp - FeatureInfo.cpp - FeatureInfo.hpp - HookExecutor.cpp - HookExecutor.hpp - RuleInfo.cpp - RuleInfo.hpp - ScenarioInfo.cpp - ScenarioInfo.hpp + NewRuntime.cpp + NewRuntime.hpp + Result.hpp Step.cpp Step.hpp - StepInfo.cpp - StepInfo.hpp StepType.hpp StringTo.hpp Table.cpp Table.hpp - TestExecution.cpp - TestExecution.hpp - FailureHandler.cpp - FailureHandler.hpp - TestRunner.cpp - TestRunner.hpp ) target_include_directories(cucumber_cpp.library.engine PUBLIC @@ -37,9 +20,12 @@ target_link_libraries(cucumber_cpp.library.engine PUBLIC cucumber_cpp.library cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression + cucumber_cpp.library.support + cucumber_cpp.library.assemble + cucumber_cpp.library.runtime ) if (CCR_BUILD_TESTS) - add_subdirectory(test) - add_subdirectory(test_helper) + # add_subdirectory(test) + # add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/engine/ContextManager.cpp b/cucumber_cpp/library/engine/ContextManager.cpp deleted file mode 100644 index 1d45637b..00000000 --- a/cucumber_cpp/library/engine/ContextManager.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/TraceTime.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - namespace - { - std::string ConstructErrorString(std::string_view typeName, std::string_view postfix) - { - std::string error; - - error.reserve(typeName.size() + postfix.size()); - error.append(typeName); - error.append(postfix); - - return error; - } - - auto& GetOrThrow(auto& ptr, std::string_view typeName) - { - if (ptr) - return *ptr; - - throw ContextNotAvailable{ ConstructErrorString(typeName, " not available") }; - } - } - - CurrentContext::CurrentContext(std::shared_ptr contextStorageFactory) - : Context{ std::move(contextStorageFactory) } - { - Start(); - } - - CurrentContext::CurrentContext(CurrentContext* parent) - : Context{ parent } - , parent{ parent } - - { - Start(); - } - - [[nodiscard]] Result CurrentContext::ExecutionStatus() const - { - return executionStatus; - } - - void CurrentContext::Start() - { - traceTime.Start(); - } - - TraceTime::Duration CurrentContext::Duration() const - { - return traceTime.Delta(); - } - - void CurrentContext::ExecutionStatus(Result result) - { - if (result > executionStatus) - executionStatus = result; - - if (parent != nullptr) - parent->ExecutionStatus(result); - } - - ProgramContext::ProgramContext(std::shared_ptr contextStorageFactory) - : RunnerContext{ std::move(contextStorageFactory) } - { - } - - ContextManager::ScopedFeatureContext::ScopedFeatureContext(ContextManager& contextManager) - : contextManager{ contextManager } - {} - - ContextManager::ScopedFeatureContext::~ScopedFeatureContext() - { - contextManager.DisposeFeatureContext(); - } - - const cucumber_cpp::library::engine::CurrentContext& ContextManager::ScopedFeatureContext::CurrentContext() const - { - return contextManager.FeatureContext(); - } - - ContextManager::ScopedRuleContext::ScopedRuleContext(ContextManager& contextManager) - : contextManager{ contextManager } - {} - - ContextManager::ScopedRuleContext::~ScopedRuleContext() - { - contextManager.DisposeRuleContext(); - } - - ContextManager::ScopedScenarioContext::ScopedScenarioContext(ContextManager& contextManager) - : contextManager{ contextManager } - {} - - ContextManager::ScopedScenarioContext::~ScopedScenarioContext() - { - contextManager.DisposeScenarioContext(); - } - - ContextManager::ScopedStepContext::ScopedStepContext(ContextManager& contextManager) - : contextManager{ contextManager } - {} - - ContextManager::ScopedStepContext::~ScopedStepContext() - { - contextManager.DisposeStepContext(); - } - - ContextManager::ContextManager(std::shared_ptr contextStorageFactory) - { - programContext = std::make_shared(std::move(contextStorageFactory)); - runnerContext.push(programContext); - } - - cucumber_cpp::library::engine::ProgramContext& ContextManager::ProgramContext() - { - return *programContext; - } - - cucumber_cpp::library::engine::ProgramContext& ContextManager::ProgramContext() const - { - return *programContext; - } - - ContextManager::ScopedFeatureContext ContextManager::CreateFeatureContext(const FeatureInfo& featureInfo) - { - featureContext = std::make_shared(*programContext, featureInfo); - runnerContext.push(featureContext); - - return ScopedFeatureContext{ *this }; - } - - void ContextManager::DisposeFeatureContext() - { - runnerContext.pop(); - featureContext.reset(); - } - - FeatureContext& ContextManager::FeatureContext() - { - return GetOrThrow(featureContext, "FeatureContext"); - } - - ContextManager::ScopedRuleContext ContextManager::CreateRuleContext(const RuleInfo& ruleInfo) - { - ruleContext = std::make_shared(*featureContext, ruleInfo); - runnerContext.push(ruleContext); - - return ScopedRuleContext{ *this }; - } - - void ContextManager::DisposeRuleContext() - { - runnerContext.pop(); - ruleContext.reset(); - } - - RuleContext& ContextManager::RuleContext() - { - return GetOrThrow(ruleContext, "RuleContext"); - } - - ContextManager::ScopedScenarioContext ContextManager::CreateScenarioContext(const ScenarioInfo& scenarioInfo) - { - scenarioContext = std::make_shared(CurrentContext(), scenarioInfo); - runnerContext.push(scenarioContext); - - return ScopedScenarioContext{ *this }; - } - - void ContextManager::DisposeScenarioContext() - { - runnerContext.pop(); - scenarioContext.reset(); - } - - ScenarioContext& ContextManager::ScenarioContext() - { - return GetOrThrow(scenarioContext, "ScenarioContext"); - } - - ContextManager::ScopedStepContext ContextManager::CreateStepContext(const StepInfo& stepInfo) - { - stepContext.push(std::make_shared(*scenarioContext, stepInfo)); - return ScopedStepContext{ *this }; - } - - void ContextManager::DisposeStepContext() - { - stepContext.pop(); - } - - StepContext& ContextManager::StepContext() - { - if (stepContext.empty()) - throw ContextNotAvailable{ "StepContext not available" }; - - return *stepContext.top(); - } - - cucumber_cpp::library::engine::RunnerContext& ContextManager::CurrentContext() - { - return *runnerContext.top(); - } - - cucumber_cpp::library::engine::RunnerContext* ContextManager::CurrentStepContext() - { - if (stepContext.empty()) - return nullptr; - - return stepContext.top().get(); - } -} diff --git a/cucumber_cpp/library/engine/ContextManager.hpp b/cucumber_cpp/library/engine/ContextManager.hpp deleted file mode 100644 index e6c076eb..00000000 --- a/cucumber_cpp/library/engine/ContextManager.hpp +++ /dev/null @@ -1,152 +0,0 @@ -#ifndef ENGINE_CONTEXTMANAGER_HPP -#define ENGINE_CONTEXTMANAGER_HPP - -#include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/TraceTime.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/util/Immoveable.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct CurrentContext : Context - { - explicit CurrentContext(std::shared_ptr contextStorageFactory); - explicit CurrentContext(CurrentContext* parent); - - [[nodiscard]] Result ExecutionStatus() const; - - void Start(); - [[nodiscard]] TraceTime::Duration Duration() const; - - protected: - void ExecutionStatus(Result result); - - private: - CurrentContext* parent{ nullptr }; - - Result executionStatus{ Result::passed }; - TraceTime traceTime; - }; - - struct RunnerContext : CurrentContext - { - using CurrentContext::CurrentContext; - using CurrentContext::ExecutionStatus; - }; - - struct ProgramContext : RunnerContext - { - explicit ProgramContext(std::shared_ptr contextStorageFactory); - }; - - template - struct NestedContext : RunnerContext - { - NestedContext(RunnerContext& parent, const T& info) - : RunnerContext{ &parent } - , info{ info } - {} - - const T& info; - }; - - using FeatureContext = NestedContext; - using RuleContext = NestedContext; - using ScenarioContext = NestedContext; - using StepContext = NestedContext; - using NestedStepContext = NestedContext; - - struct ContextNotAvailable : std::logic_error - { - using std::logic_error::logic_error; - }; - - struct ContextManager - { - explicit ContextManager(std::shared_ptr contextStorageFactory); - - cucumber_cpp::library::engine::ProgramContext& ProgramContext(); - [[nodiscard]] cucumber_cpp::library::engine::ProgramContext& ProgramContext() const; - - struct ScopedFeatureContext; - struct ScopedRuleContext; - struct ScopedScenarioContext; - struct ScopedStepContext; - - [[nodiscard]] ScopedFeatureContext CreateFeatureContext(const FeatureInfo& featureInfo); - cucumber_cpp::library::engine::FeatureContext& FeatureContext(); - - [[nodiscard]] ScopedRuleContext CreateRuleContext(const RuleInfo& ruleInfo); - cucumber_cpp::library::engine::RuleContext& RuleContext(); - - [[nodiscard]] ScopedScenarioContext CreateScenarioContext(const ScenarioInfo& scenarioInfo); - cucumber_cpp::library::engine::ScenarioContext& ScenarioContext(); - - [[nodiscard]] ScopedStepContext CreateStepContext(const StepInfo& stepInfo); - - cucumber_cpp::library::engine::StepContext& StepContext(); - - cucumber_cpp::library::engine::RunnerContext& CurrentContext(); - cucumber_cpp::library::engine::RunnerContext* CurrentStepContext(); - - private: - void DisposeFeatureContext(); - void DisposeRuleContext(); - void DisposeScenarioContext(); - void DisposeStepContext(); - - std::shared_ptr programContext; - std::shared_ptr featureContext; - std::shared_ptr ruleContext; - std::shared_ptr scenarioContext; - - std::stack> runnerContext; - std::stack> stepContext; - }; - - struct ContextManager::ScopedFeatureContext : library::util::Immoveable - { - explicit ScopedFeatureContext(ContextManager& contextManager); - ~ScopedFeatureContext(); - - const cucumber_cpp::library::engine::CurrentContext& CurrentContext() const; - - private: - ContextManager& contextManager; - }; - - struct ContextManager::ScopedRuleContext : library::util::Immoveable - { - explicit ScopedRuleContext(ContextManager& contextManager); - ~ScopedRuleContext(); - - private: - ContextManager& contextManager; - }; - - struct ContextManager::ScopedScenarioContext : library::util::Immoveable - { - explicit ScopedScenarioContext(ContextManager& contextManager); - ~ScopedScenarioContext(); - - private: - ContextManager& contextManager; - }; - - struct ContextManager::ScopedStepContext : library::util::Immoveable - { - explicit ScopedStepContext(ContextManager& contextManager); - ~ScopedStepContext(); - - private: - ContextManager& contextManager; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/FailureHandler.cpp b/cucumber_cpp/library/engine/FailureHandler.cpp deleted file mode 100644 index 872aa81f..00000000 --- a/cucumber_cpp/library/engine/FailureHandler.cpp +++ /dev/null @@ -1,43 +0,0 @@ -#include "cucumber_cpp/library/engine/FailureHandler.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include "gtest/gtest.h" -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - TestAssertionHandlerImpl::TestAssertionHandlerImpl(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler) - : contextManager{ contextManager } - , reportHandler{ reportHandler } - {} - - void TestAssertionHandlerImpl::AddAssertionError(const char* file, int line, const std::string& message) - { - std::filesystem::path relativeFilePath = std::filesystem::relative(file); - - contextManager.CurrentContext().ExecutionStatus(cucumber_cpp::library::engine::Result::failed); - - if (auto* stepContext = contextManager.CurrentStepContext(); stepContext != nullptr) - stepContext->ExecutionStatus(cucumber_cpp::library::engine::Result::failed); - - reportHandler.Failure(message, relativeFilePath, line); - } - - GoogleTestEventListener::GoogleTestEventListener(TestAssertionHandler& testAssertionHandler) - : testAssertionHandler(testAssertionHandler) - {} - - void GoogleTestEventListener::OnTestPartResult(const testing::TestPartResult& testPartResult) - { - if (testPartResult.failed()) - { - testAssertionHandler.AddAssertionError( - testPartResult.file_name() != nullptr ? testPartResult.file_name() : "", - testPartResult.line_number(), - testPartResult.message()); - } - } -} diff --git a/cucumber_cpp/library/engine/FailureHandler.hpp b/cucumber_cpp/library/engine/FailureHandler.hpp deleted file mode 100644 index 89c0f401..00000000 --- a/cucumber_cpp/library/engine/FailureHandler.hpp +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef ENGINE_FAILUREHANDLER_HPP -#define ENGINE_FAILUREHANDLER_HPP - -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct TestAssertionHandler - { - virtual ~TestAssertionHandler() = default; - - virtual void AddAssertionError(const char* file, int line, const std::string& message) = 0; - }; - - struct TestAssertionHandlerImpl : TestAssertionHandler - { - explicit TestAssertionHandlerImpl(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler); - - void AddAssertionError(const char* file, int line, const std::string& message) override; - - private: - cucumber_cpp::library::engine::ContextManager& contextManager; - report::ReportForwarder& reportHandler; - }; - - struct GoogleTestEventListener : testing::EmptyTestEventListener - { - explicit GoogleTestEventListener(TestAssertionHandler& testAssertionHandler); - - void OnTestPartResult(const testing::TestPartResult& testPartResult) override; - - private: - TestAssertionHandler& testAssertionHandler; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/FeatureFactory.cpp b/cucumber_cpp/library/engine/FeatureFactory.cpp deleted file mode 100644 index b254e627..00000000 --- a/cucumber_cpp/library/engine/FeatureFactory.cpp +++ /dev/null @@ -1,346 +0,0 @@ -#include "cucumber_cpp/library/engine/FeatureFactory.hpp" -#include "cucumber/gherkin/app.hpp" -#include "cucumber/gherkin/file.hpp" -#include "cucumber/gherkin/parse_error.hpp" -#include "cucumber/messages/background.hpp" -#include "cucumber/messages/feature.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/pickle_step_argument.hpp" -#include "cucumber/messages/pickle_step_type.hpp" -#include "cucumber/messages/pickle_tag.hpp" -#include "cucumber/messages/rule.hpp" -#include "cucumber/messages/scenario.hpp" -#include "cucumber/messages/step.hpp" -#include "cucumber/messages/tag.hpp" -#include "cucumber_cpp/library/Errors.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/TagExpression.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - namespace - { - struct ScenarioWithRule - { - const cucumber::messages::rule* rule; - const cucumber::messages::scenario* scenario; - }; - - using AllTypes = std::variant< - const cucumber::messages::background*, - const cucumber::messages::scenario*, - const cucumber::messages::step*, - ScenarioWithRule>; - - using FlatAst = std::map>; - - void FlattenBackgroundAst(FlatAst& flatAst, const cucumber::messages::background& background) - { - flatAst[background.id] = &background; - for (const auto& step : background.steps) - flatAst[step.id] = &step; - } - - void FlattenScenarioAst(FlatAst& flatAst, const cucumber::messages::scenario& scenario) - { - flatAst[scenario.id] = &scenario; - for (const auto& step : scenario.steps) - flatAst[step.id] = &step; - } - - void FlattenRuleAst(FlatAst& flatAst, const cucumber::messages::rule& rule) - { - for (const auto& ruleChild : rule.children) - { - if (ruleChild.background) - { - flatAst[ruleChild.background->id] = &*ruleChild.background; - for (const auto& step : ruleChild.background->steps) - flatAst[step.id] = &step; - } - else if (ruleChild.scenario) - { - flatAst[ruleChild.scenario->id] = ScenarioWithRule{ &rule, &*ruleChild.scenario }; - for (const auto& step : ruleChild.scenario->steps) - flatAst[step.id] = &step; - } - } - } - - FlatAst FlattenAst(const cucumber::messages::feature& feature) - { - FlatAst flatAst; - - for (const auto& child : feature.children) - { - if (child.background) - FlattenBackgroundAst(flatAst, *child.background); - else if (child.rule) - FlattenRuleAst(flatAst, *child.rule); - else if (child.scenario) - FlattenScenarioAst(flatAst, *child.scenario); - } - - return flatAst; - } - - const std::map stepTypeLut{ - { cucumber::messages::pickle_step_type::CONTEXT, StepType::given }, - { cucumber::messages::pickle_step_type::ACTION, StepType::when }, - { cucumber::messages::pickle_step_type::OUTCOME, StepType::then }, - }; - - auto TableFactory(const std::optional& optionalPickleStepArgument) - { - std::vector> table{}; - - if (!optionalPickleStepArgument) - return table; - - if (!optionalPickleStepArgument.value().data_table) - return table; - - for (const auto& dataTable = optionalPickleStepArgument.value().data_table.value(); - const auto& row : dataTable.rows) - { - table.emplace_back(); - auto& back = table.back(); - - for (const auto& cols : row.cells) - back.emplace_back(cols.value); - } - - 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); - return { range.begin(), range.end() }; - } - - std::set> TagsFactory(const std::vector& tags) - { - const auto range = tags | std::views::transform(&cucumber::messages::pickle_tag::name); - return { range.begin(), range.end() }; - } - - 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), std::move(docString))); - } - catch (const std::out_of_range&) - { - throw UnsupportedAsteriskError{ std::format("{}:{}: * steps are not supported", scenarioInfo.FeatureInfo().Path().string(), step.location.line) }; - } - } - - void ConstructSteps(const FeatureTreeFactory& featureTreeFactory, ScenarioInfo& scenarioInfo, const FlatAst& flatAst, const std::vector& pickleSteps) - { - for (const auto& pickleStep : pickleSteps) - { - const auto* astStep = std::get(flatAst.at(pickleStep.ast_node_ids.front())); - - ConstructStep(featureTreeFactory, scenarioInfo, *astStep, pickleStep); - } - } - - void ConstructScenario(const FeatureTreeFactory& featureTreeFactory, FeatureInfo& featureInfo, const FlatAst& flatAst, const cucumber::messages::scenario& scenario, const cucumber::messages::pickle& pickle, std::string_view tagExpression) - { - auto tags = TagsFactory(pickle.tags); - - if (!IsTagExprSelected(tagExpression, tags)) - return; - - featureInfo.Scenarios().push_back(std::make_unique( - featureInfo, - std::move(tags), - pickle.name, - scenario.description, - scenario.location.line, - scenario.location.column.value_or(0))); - - ConstructSteps(featureTreeFactory, *featureInfo.Scenarios().back(), flatAst, pickle.steps); - } - - void ConstructScenario(const FeatureTreeFactory& featureTreeFactory, RuleInfo& ruleInfo, const FlatAst& flatAst, const cucumber::messages::scenario& scenario, const cucumber::messages::pickle& pickle, std::set> tags) - { - ruleInfo.Scenarios().push_back(std::make_unique( - ruleInfo, - std::move(tags), - pickle.name, - scenario.description, - scenario.location.line, - scenario.location.column.value_or(0))); - - ConstructSteps(featureTreeFactory, *ruleInfo.Scenarios().back(), flatAst, pickle.steps); - } - - RuleInfo& GetOrConstructRule(FeatureInfo& featureInfo, const cucumber::messages::rule& rule) - { - if (auto iter = std::ranges::find(featureInfo.Rules(), rule.id, &RuleInfo::Id); iter != featureInfo.Rules().end()) - return **iter; - - featureInfo.Rules().push_back( - std::make_unique(featureInfo, - rule.id, - rule.name, - rule.description, - rule.location.line, - rule.location.column.value_or(0))); - - return *featureInfo.Rules().back(); - } - - void ConstructScenarioWithRule(const FeatureTreeFactory& featureTreeFactory, FeatureInfo& featureInfo, const FlatAst& flatAst, const cucumber::messages::rule& rule, const cucumber::messages::scenario& scenario, const cucumber::messages::pickle& pickle, std::string_view tagExpression) - { - auto tags = TagsFactory(pickle.tags); - - if (!IsTagExprSelected(tagExpression, tags)) - return; - - auto& ruleInfo = GetOrConstructRule(featureInfo, rule); - - ConstructScenario(featureTreeFactory, ruleInfo, flatAst, scenario, pickle, std::move(tags)); - } - - struct ConstructScenarioVisitor - { - ConstructScenarioVisitor(const FeatureTreeFactory& featureTreeFactory, FeatureInfo& featureInfo, const FlatAst& flatAst, const cucumber::messages::pickle& pickle, std::string_view tagExpression) - : featureTreeFactory{ featureTreeFactory } - , featureInfo{ featureInfo } - , flatAst{ flatAst } - , pickle{ pickle } - , tagExpression{ tagExpression } - {} - - void operator()(const cucumber::messages::scenario* scenario) const - { - ConstructScenario(featureTreeFactory, featureInfo, flatAst, *scenario, pickle, tagExpression); - } - - void operator()(ScenarioWithRule ruleWithScenario) const - { - ConstructScenarioWithRule(featureTreeFactory, featureInfo, flatAst, *ruleWithScenario.rule, *ruleWithScenario.scenario, pickle, tagExpression); - } - - void operator()(auto /* ignore */) const - { - /* ignore */ - } - - private: - const FeatureTreeFactory& featureTreeFactory; - FeatureInfo& featureInfo; - const FlatAst& flatAst; - const cucumber::messages::pickle& pickle; - std::string_view tagExpression; - }; - - void ConstructScenario(const FeatureTreeFactory& featureTreeFactory, FeatureInfo& featureInfo, const FlatAst& flatAst, const cucumber::messages::pickle& pickle, std::string_view tagExpression) - { - ConstructScenarioVisitor visitor{ featureTreeFactory, featureInfo, flatAst, pickle, tagExpression }; - - std::visit(visitor, flatAst.at(pickle.ast_node_ids.front())); - } - - std::unique_ptr FeatureFactory(std::filesystem::path path, const cucumber::gherkin::app::parser_result& ast) - { - return std::make_unique( - TagsFactory(ast.feature->tags), - ast.feature->name, - ast.feature->description, - std::move(path), - ast.feature->location.line, - ast.feature->location.column.value_or(0)); - } - } - - FeatureTreeFactory::FeatureTreeFactory(StepRegistry& stepRegistry) - : 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, 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(docString), std::move(stepMatch)); - } - catch (const StepRegistry::StepNotFoundError&) - { - 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(docString), std::move(ase.matches)); - } - } - - std::unique_ptr FeatureTreeFactory::Create(const std::filesystem::path& path, std::string_view tagExpression) const - { - std::unique_ptr featureInfo; - std::optional flatAst; - - cucumber::gherkin::app::callbacks callbacks{ - .ast = [&path, &flatAst, &featureInfo](const cucumber::gherkin::app::parser_result& ast) - { - featureInfo = FeatureFactory(path, ast); - flatAst = FlattenAst(*ast.feature); - }, - .pickle = [this, &featureInfo, &flatAst, &tagExpression](const cucumber::messages::pickle& pickle) - { - ConstructScenario(*this, *featureInfo, *flatAst, pickle, tagExpression); - }, - .error = [](const cucumber::gherkin::parse_error& /* _ */) - { - /* not handled yet */ - } - }; - - cucumber::gherkin::app gherkin; - - gherkin.parse(cucumber::gherkin::file{ path.string() }, callbacks); - - return featureInfo; - } -} diff --git a/cucumber_cpp/library/engine/FeatureFactory.hpp b/cucumber_cpp/library/engine/FeatureFactory.hpp deleted file mode 100644 index a064f366..00000000 --- a/cucumber_cpp/library/engine/FeatureFactory.hpp +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef ENGINE_FEATUREFACTORY_HPP -#define ENGINE_FEATUREFACTORY_HPP - -#include "cucumber/gherkin/app.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct FeatureTreeFactory - { - 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, std::string docString) const; - - [[nodiscard]] std::unique_ptr Create(const std::filesystem::path& path, std::string_view tagExpression) const; - - private: - StepRegistry& stepRegistry; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/FeatureInfo.cpp b/cucumber_cpp/library/engine/FeatureInfo.cpp deleted file mode 100644 index 016f964c..00000000 --- a/cucumber_cpp/library/engine/FeatureInfo.cpp +++ /dev/null @@ -1,75 +0,0 @@ -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - FeatureInfo::FeatureInfo(std::set> tags, std::string title, std::string description, std::filesystem::path path, std::size_t line, std::size_t column) - : tags{ std::move(tags) } - , title{ std::move(title) } - , description{ std::move(description) } - , path{ std::move(path) } - , line{ line } - , column{ column } - { - } - - const std::set>& FeatureInfo::Tags() const - { - return tags; - } - - const std::string& FeatureInfo::Title() const - { - return title; - } - - const std::string& FeatureInfo::Description() const - { - return description; - } - - const std::filesystem::path& FeatureInfo::Path() const - { - return path; - } - - std::size_t FeatureInfo::Line() const - { - return line; - } - - std::size_t FeatureInfo::Column() const - { - return column; - } - - std::vector>& FeatureInfo::Rules() - { - return rules; - } - - const std::vector>& FeatureInfo::Rules() const - { - return rules; - } - - std::vector>& FeatureInfo::Scenarios() - { - return scenarios; - } - - const std::vector>& FeatureInfo::Scenarios() const - { - return scenarios; - } - -} diff --git a/cucumber_cpp/library/engine/FeatureInfo.hpp b/cucumber_cpp/library/engine/FeatureInfo.hpp deleted file mode 100644 index 1cf18401..00000000 --- a/cucumber_cpp/library/engine/FeatureInfo.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef ENGINE_FEATUREINFO_HPP -#define ENGINE_FEATUREINFO_HPP - -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct FeatureInfo - { - FeatureInfo(std::set> tags, std::string title, std::string description, std::filesystem::path path, std::size_t line, std::size_t column); - - [[nodiscard]] const std::set>& Tags() const; - [[nodiscard]] const std::string& Title() const; - [[nodiscard]] const std::string& Description() const; - - [[nodiscard]] const std::filesystem::path& Path() const; - - [[nodiscard]] std::size_t Line() const; - [[nodiscard]] std::size_t Column() const; - - [[nodiscard]] std::vector>& Rules(); - [[nodiscard]] const std::vector>& Rules() const; - - [[nodiscard]] std::vector>& Scenarios(); - [[nodiscard]] const std::vector>& Scenarios() const; - - private: - std::set> tags; - std::string title; - std::string description; - - std::filesystem::path path; - - std::size_t line; - std::size_t column; - - std::vector> rules; - std::vector> scenarios; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/HookExecutor.cpp b/cucumber_cpp/library/engine/HookExecutor.cpp deleted file mode 100644 index 1c2f4fbd..00000000 --- a/cucumber_cpp/library/engine/HookExecutor.cpp +++ /dev/null @@ -1,85 +0,0 @@ -#include "cucumber_cpp/library/engine/HookExecutor.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - namespace - { - constexpr HookPair programHooks{ HookType::beforeAll, HookType::afterAll }; - constexpr HookPair featureHooks{ HookType::beforeFeature, HookType::afterFeature }; - constexpr HookPair scenarioHooks{ HookType::before, HookType::after }; - constexpr HookPair stepHooks{ HookType::beforeStep, HookType::afterStep }; - - void ExecuteHook(cucumber_cpp::library::engine::RunnerContext& runnerContext, HookType hook, const std::set>& tags) - { - for (const auto& match : HookRegistry::Instance().Query(hook, tags)) - { - match.factory(runnerContext)->Execute(); - - if (runnerContext.ExecutionStatus() != cucumber_cpp::library::engine::Result::passed) - return; - } - } - } - - HookExecutor::ScopedHook::ScopedHook(cucumber_cpp::library::engine::RunnerContext& runnerContext, HookPair hookPair, const std::set>& tags) - : runnerContext{ runnerContext } - , hookPair{ hookPair } - , tags{ tags } - { - ExecuteHook(runnerContext, hookPair.before, tags); - } - - HookExecutor::ScopedHook::~ScopedHook() - { - ExecuteHook(runnerContext, hookPair.after, tags); - } - - HookExecutor::ProgramScope::ProgramScope(cucumber_cpp::library::engine::ContextManager& contextManager) - : ScopedHook{ contextManager.ProgramContext(), programHooks, {} } - {} - - HookExecutor::FeatureScope::FeatureScope(cucumber_cpp::library::engine::ContextManager& contextManager) - : ScopedHook{ contextManager.FeatureContext(), featureHooks, contextManager.FeatureContext().info.Tags() } - {} - - HookExecutor::ScenarioScope::ScenarioScope(cucumber_cpp::library::engine::ContextManager& contextManager) - : ScopedHook{ contextManager.ScenarioContext(), scenarioHooks, contextManager.ScenarioContext().info.Tags() } - {} - - HookExecutor::StepScope::StepScope(cucumber_cpp::library::engine::ContextManager& contextManager) - : ScopedHook{ contextManager.ScenarioContext(), stepHooks, contextManager.ScenarioContext().info.Tags() } - {} - - HookExecutorImpl::HookExecutorImpl(cucumber_cpp::library::engine::ContextManager& contextManager) - : contextManager{ contextManager } - {} - - HookExecutor::ProgramScope HookExecutorImpl::BeforeAll() - { - return ProgramScope{ contextManager }; - } - - HookExecutor::FeatureScope HookExecutorImpl::FeatureStart() - { - return FeatureScope{ contextManager }; - } - - HookExecutor::ScenarioScope HookExecutorImpl::ScenarioStart() - { - return ScenarioScope{ contextManager }; - } - - HookExecutor::StepScope HookExecutorImpl::StepStart() - { - return StepScope{ contextManager }; - } -} diff --git a/cucumber_cpp/library/engine/HookExecutor.hpp b/cucumber_cpp/library/engine/HookExecutor.hpp deleted file mode 100644 index e74dfcf7..00000000 --- a/cucumber_cpp/library/engine/HookExecutor.hpp +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef ENGINE_HOOKEXECUTOR_HPP -#define ENGINE_HOOKEXECUTOR_HPP - -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/util/Immoveable.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - - struct HookExecutor - { - protected: - ~HookExecutor() = default; - - public: - struct ProgramScope; - struct FeatureScope; - struct ScenarioScope; - struct StepScope; - - [[nodiscard]] virtual ProgramScope BeforeAll() = 0; - [[nodiscard]] virtual FeatureScope FeatureStart() = 0; - [[nodiscard]] virtual ScenarioScope ScenarioStart() = 0; - [[nodiscard]] virtual StepScope StepStart() = 0; - - private: - struct ScopedHook; - }; - - struct HookPair - { - const HookType before; - const HookType after; - }; - - struct HookExecutor::ScopedHook : util::Immoveable - { - ScopedHook(cucumber_cpp::library::engine::RunnerContext& runnerContext, HookPair hookPair, const std::set>& tags); - ~ScopedHook(); - - private: - cucumber_cpp::library::engine::RunnerContext& runnerContext; - HookPair hookPair; - const std::set>& tags; - }; - - struct HookExecutor::ProgramScope : private ScopedHook - { - explicit ProgramScope(cucumber_cpp::library::engine::ContextManager& contextManager); - }; - - struct HookExecutor::FeatureScope : private ScopedHook - { - explicit FeatureScope(cucumber_cpp::library::engine::ContextManager& contextManager); - }; - - struct HookExecutor::ScenarioScope : private ScopedHook - { - explicit ScenarioScope(cucumber_cpp::library::engine::ContextManager& contextManager); - }; - - struct HookExecutor::StepScope : private ScopedHook - { - explicit StepScope(cucumber_cpp::library::engine::ContextManager& contextManager); - }; - - struct HookExecutorImpl : HookExecutor - { - explicit HookExecutorImpl(cucumber_cpp::library::engine::ContextManager& contextManager); - virtual ~HookExecutorImpl() = default; - - [[nodiscard]] ProgramScope - BeforeAll() override; - [[nodiscard]] FeatureScope FeatureStart() override; - [[nodiscard]] ScenarioScope ScenarioStart() override; - [[nodiscard]] StepScope StepStart() override; - - private: - cucumber_cpp::library::engine::ContextManager& contextManager; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/NewRuntime.cpp b/cucumber_cpp/library/engine/NewRuntime.cpp new file mode 100644 index 00000000..428c8c20 --- /dev/null +++ b/cucumber_cpp/library/engine/NewRuntime.cpp @@ -0,0 +1 @@ +#include "cucumber_cpp/library/engine/NewRuntime.hpp" diff --git a/cucumber_cpp/library/engine/NewRuntime.hpp b/cucumber_cpp/library/engine/NewRuntime.hpp new file mode 100644 index 00000000..87d398ec --- /dev/null +++ b/cucumber_cpp/library/engine/NewRuntime.hpp @@ -0,0 +1,46 @@ +#ifndef ENGINE_NEW_RUNTIME_HPP +#define ENGINE_NEW_RUNTIME_HPP + +#include "cucumber/gherkin/app.hpp" +#include "cucumber/gherkin/exceptions.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/gherkin/pickle_compiler.hpp" +#include "cucumber/gherkin/utils.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/parse_error.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/source.hpp" +#include "cucumber/messages/source_reference.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/runtime/Coordinator.hpp" +#include "cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp" +#include "cucumber_cpp/library/runtime/Worker.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::engine +{ + +} + +#endif diff --git a/cucumber_cpp/library/engine/RuleInfo.cpp b/cucumber_cpp/library/engine/RuleInfo.cpp deleted file mode 100644 index 72a20781..00000000 --- a/cucumber_cpp/library/engine/RuleInfo.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - RuleInfo::RuleInfo(const struct FeatureInfo& featureInfo, std::string id, std::string title, std::string description, std::size_t line, std::size_t column) - : featureInfo{ featureInfo } - , id{ std::move(id) } - , title{ std::move(title) } - , description{ std::move(description) } - , line{ line } - , column{ column } - {} - - const FeatureInfo& RuleInfo::FeatureInfo() const - { - return featureInfo; - } - - [[nodiscard]] std::string_view RuleInfo::Id() const - { - return id; - } - - const std::string& RuleInfo::Title() const - { - return title; - } - - const std::string& RuleInfo::Description() const - { - return description; - } - - const std::filesystem::path& RuleInfo::Path() const - { - return FeatureInfo().Path(); - } - - std::size_t RuleInfo::Line() const - { - return line; - } - - std::size_t RuleInfo::Column() const - { - return column; - } - - std::vector>& RuleInfo::Scenarios() - { - return scenarios; - } - - const std::vector>& RuleInfo::Scenarios() const - { - return scenarios; - } -} diff --git a/cucumber_cpp/library/engine/RuleInfo.hpp b/cucumber_cpp/library/engine/RuleInfo.hpp deleted file mode 100644 index 2dabfc21..00000000 --- a/cucumber_cpp/library/engine/RuleInfo.hpp +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef ENGINE_RULEINFO_HPP -#define ENGINE_RULEINFO_HPP - -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct FeatureInfo; - - struct RuleInfo - { - RuleInfo(const FeatureInfo& featureInfo, std::string id, std::string title, std::string description, std::size_t line, std::size_t column); - - [[nodiscard]] const struct FeatureInfo& FeatureInfo() const; - - [[nodiscard]] std::string_view Id() const; - - [[nodiscard]] const std::string& Title() const; - [[nodiscard]] const std::string& Description() const; - - [[nodiscard]] const std::filesystem::path& Path() const; - - [[nodiscard]] std::size_t Line() const; - [[nodiscard]] std::size_t Column() const; - - [[nodiscard]] std::vector>& Scenarios(); - [[nodiscard]] const std::vector>& Scenarios() const; - - private: - const struct FeatureInfo& featureInfo; - - std::string id; - - std::string title; - std::string description; - - std::size_t line; - std::size_t column; - - std::vector> scenarios; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/ScenarioInfo.cpp b/cucumber_cpp/library/engine/ScenarioInfo.cpp deleted file mode 100644 index 655a93b4..00000000 --- a/cucumber_cpp/library/engine/ScenarioInfo.cpp +++ /dev/null @@ -1,96 +0,0 @@ -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - ScenarioInfo::ScenarioInfo(const struct RuleInfo& ruleInfo, std::set> tags, std::string title, std::string description, std::size_t line, std::size_t column) - : parentInfo{ &ruleInfo } - , tags{ std::move(tags) } - , title{ std::move(title) } - , description{ std::move(description) } - , line{ line } - , column{ column } - {} - - ScenarioInfo::ScenarioInfo(const struct FeatureInfo& featureInfo, std::set> tags, std::string title, std::string description, std::size_t line, std::size_t column) - : parentInfo{ &featureInfo } - , tags{ std::move(tags) } - , title{ std::move(title) } - , description{ std::move(description) } - , line{ line } - , column{ column } - { - } - - const FeatureInfo& ScenarioInfo::FeatureInfo() const - { - if (std::holds_alternative(parentInfo)) - return *std::get(parentInfo); - - if (std::holds_alternative(parentInfo)) - return std::get(parentInfo)->FeatureInfo(); - - std::abort(); - } - - std::optional> ScenarioInfo::RuleInfo() const - { - if (std::holds_alternative(parentInfo)) - return *std::get(parentInfo); - - return std::nullopt; - } - - const std::set>& ScenarioInfo::Tags() const - { - return tags; - } - - const std::string& ScenarioInfo::Title() const - { - return title; - } - - const std::string& ScenarioInfo::Description() const - { - return description; - } - - const std::filesystem::path& ScenarioInfo::Path() const - { - return FeatureInfo().Path(); - } - - std::size_t ScenarioInfo::Line() const - { - return line; - } - - std::size_t ScenarioInfo::Column() const - { - return column; - } - - std::vector>& ScenarioInfo::Children() - { - return children; - } - - const std::vector>& ScenarioInfo::Children() const - { - return children; - } -} diff --git a/cucumber_cpp/library/engine/ScenarioInfo.hpp b/cucumber_cpp/library/engine/ScenarioInfo.hpp deleted file mode 100644 index c502da49..00000000 --- a/cucumber_cpp/library/engine/ScenarioInfo.hpp +++ /dev/null @@ -1,54 +0,0 @@ -#ifndef ENGINE_SCENARIOINFO_HPP -#define ENGINE_SCENARIOINFO_HPP - -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct FeatureInfo; - - struct ScenarioInfo - { - ScenarioInfo(const RuleInfo& ruleInfo, std::set> tags, std::string title, std::string description, std::size_t line, std::size_t column); - ScenarioInfo(const FeatureInfo& featureInfo, std::set> tags, std::string title, std::string description, std::size_t line, std::size_t column); - - [[nodiscard]] const struct FeatureInfo& FeatureInfo() const; - [[nodiscard]] std::optional> RuleInfo() const; - - [[nodiscard]] const std::set>& Tags() const; - [[nodiscard]] const std::string& Title() const; - [[nodiscard]] const std::string& Description() const; - - [[nodiscard]] const std::filesystem::path& Path() const; - - [[nodiscard]] std::size_t Line() const; - [[nodiscard]] std::size_t Column() const; - - [[nodiscard]] std::vector>& Children(); - [[nodiscard]] const std::vector>& Children() const; - - private: - std::variant parentInfo; - - std::set> tags; - std::string title; - std::string description; - - std::size_t line; - std::size_t column; - - std::vector> children; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index 140b26eb..6d75cc47 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -1,8 +1,8 @@ #include "cucumber_cpp/library/engine/Step.hpp" +#include "cucumber/messages/pickle_step_type.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/engine/Table.hpp" -#include "cucumber_cpp/library/engine/TestRunner.hpp" #include #include @@ -16,17 +16,17 @@ namespace cucumber_cpp::library::engine void Step::Given(const std::string& step) const { - TestRunner::Instance().NestedStep(StepType::given, step); + // CucumberTestServer::Instance()->RunStep(step, cucumber::messages::pickle_step_type::CONTEXT); } void Step::When(const std::string& step) const { - TestRunner::Instance().NestedStep(StepType::when, step); + // CucumberTestServer::Instance()->RunStep(step, cucumber::messages::pickle_step_type::ACTION); } void Step::Then(const std::string& step) const { - TestRunner::Instance().NestedStep(StepType::then, step); + // CucumberTestServer::Instance()->RunStep(step, cucumber::messages::pickle_step_type::OUTCOME); } void Step::Pending(const std::string& message, std::source_location current) noexcept(false) diff --git a/cucumber_cpp/library/engine/StepInfo.cpp b/cucumber_cpp/library/engine/StepInfo.cpp deleted file mode 100644 index f21b6f24..00000000 --- a/cucumber_cpp/library/engine/StepInfo.cpp +++ /dev/null @@ -1,87 +0,0 @@ -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" -#include -#include -#include -#include -#include - -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, 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, 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::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) } - { - } - - const struct ScenarioInfo& StepInfo::ScenarioInfo() const - { - return scenarioInfo; - } - - const std::string& StepInfo::Text() const - { - return text; - } - - StepType StepInfo::Type() const - { - return type; - } - - std::size_t StepInfo::Line() const - { - return line; - } - - std::size_t StepInfo::Column() const - { - return column; - } - - [[nodiscard]] const std::vector>& StepInfo::Table() const - { - 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 deleted file mode 100644 index 2e3b841c..00000000 --- a/cucumber_cpp/library/engine/StepInfo.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#ifndef ENGINE_STEPINFO_HPP -#define ENGINE_STEPINFO_HPP - -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct ScenarioInfo; - - struct 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); - 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; - - [[nodiscard]] const std::string& Text() const; - [[nodiscard]] StepType Type() const; - - [[nodiscard]] std::size_t Line() const; - [[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: - const struct ScenarioInfo& scenarioInfo; - - std::string text; - StepType type; - - std::size_t line; - std::size_t column; - - std::vector> table; - std::string docString; - std::variant> stepMatch; - }; - - struct NestedStepInfo - { - NestedStepInfo(std::string text, StepType type, std::filesystem::path, std::size_t line, std::size_t column, std::vector> table); - NestedStepInfo(std::string text, StepType type, std::filesystem::path, std::size_t line, std::size_t column, std::vector> table, StepMatch); - NestedStepInfo(std::string text, StepType type, std::filesystem::path, std::size_t line, std::size_t column, std::vector> table, std::vector); - - [[nodiscard]] const std::filesystem::path& FilePath() const; - - [[nodiscard]] const std::string& Text() const; - [[nodiscard]] StepType Type() const; - - [[nodiscard]] std::size_t Line() const; - [[nodiscard]] std::size_t Column() const; - - [[nodiscard]] const std::vector>& Table() const; - [[nodiscard]] const std::variant>& StepMatch() const; - - private: - const std::filesystem::path filePath; - - std::string text; - StepType type; - - std::size_t line; - std::size_t column; - - std::vector> table; - std::variant> stepMatch; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/TestExecution.cpp b/cucumber_cpp/library/engine/TestExecution.cpp deleted file mode 100644 index ae1c86dc..00000000 --- a/cucumber_cpp/library/engine/TestExecution.cpp +++ /dev/null @@ -1,154 +0,0 @@ -#include "cucumber_cpp/library/engine/TestExecution.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/HookExecutor.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include "gtest/gtest.h" -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - const DryRunPolicy dryRunPolicy; - const ExecuteRunPolicy executeRunPolicy; - - TestExecution::ProgramScope::ProgramScope(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution) - : contextManager{ contextManager } - , scopedProgramReport{ reportHandler.ProgramStart() } - , scopedProgramHook{ hookExecution.BeforeAll() } - { - } - - Result TestExecution::ProgramScope::ExecutionStatus() const - { - return contextManager.ProgramContext().ExecutionStatus(); - } - - TestExecution::FeatureScope::FeatureScope(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution, const cucumber_cpp::library::engine::FeatureInfo& featureInfo) - : scopedFeatureContext{ contextManager.CreateFeatureContext(featureInfo) } - , scopedFeatureReport{ reportHandler.FeatureStart() } - , scopedFeatureHook{ hookExecution.FeatureStart() } - { - } - - Result TestExecution::FeatureScope::ExecutionStatus() const - { - return scopedFeatureContext.CurrentContext().ExecutionStatus(); - } - - TestExecution::RuleScope::RuleScope(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, const cucumber_cpp::library::engine::RuleInfo& ruleInfo) - : scopedRuleContext{ contextManager.CreateRuleContext(ruleInfo) } - , scopedRuleReport{ reportHandler.RuleStart() } - { - } - - TestExecution::ScenarioScope::ScenarioScope(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution, const cucumber_cpp::library::engine::ScenarioInfo& scenarioInfo) - : scopedScenarioContext{ contextManager.CreateScenarioContext(scenarioInfo) } - , scopedScenarioReport{ reportHandler.ScenarioStart() } - , scopedScenarioHook{ hookExecution.ScenarioStart() } - { - } - - void DryRunPolicy::RunStep(cucumber_cpp::library::engine::ContextManager& /* contextManager */, const StepMatch& /* stepMatch */) const - { - /* don't execute actual steps */ - } - - void ExecuteRunPolicy::RunStep(cucumber_cpp::library::engine::ContextManager& contextManager, const StepMatch& stepMatch) const - { - const auto& stepContext = contextManager.StepContext(); - auto& scenarioContext = contextManager.ScenarioContext(); - - 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) - : contextManager{ contextManager } - , reportHandler{ reportHandler } - , hookExecution{ hookExecution } - , executionPolicy{ executionPolicy } - { - auto& listeners = testing::UnitTest::GetInstance()->listeners(); - listeners.Append(&googleTestEventListener); - } - - TestExecutionImpl::~TestExecutionImpl() - { - auto& listeners = testing::UnitTest::GetInstance()->listeners(); - listeners.Release(&googleTestEventListener); - } - - TestExecution::ProgramScope TestExecutionImpl::StartRun() - { - return ProgramScope{ contextManager, reportHandler, hookExecution }; - } - - TestExecution::FeatureScope TestExecutionImpl::StartFeature(const cucumber_cpp::library::engine::FeatureInfo& featureInfo) - { - return FeatureScope{ contextManager, reportHandler, hookExecution, featureInfo }; - } - - TestExecution::RuleScope TestExecutionImpl::StartRule(const cucumber_cpp::library::engine::RuleInfo& ruleInfo) - { - return RuleScope{ contextManager, reportHandler, ruleInfo }; - } - - TestExecution::ScenarioScope TestExecutionImpl::StartScenario(const cucumber_cpp::library::engine::ScenarioInfo& scenarioInfo) - { - return ScenarioScope{ contextManager, reportHandler, hookExecution, scenarioInfo }; - } - - void TestExecutionImpl::RunStep(const cucumber_cpp::library::engine::StepInfo& stepInfo) - { - auto scopedContext = contextManager.CreateStepContext(stepInfo); - if (contextManager.ScenarioContext().ExecutionStatus() == Result::passed) - { - const auto scopedStepReport = reportHandler.StepStart(); - const auto scopedStepHook = hookExecution.StepStart(); - - std::visit([this](const auto& value) - { - RunStepMatch(value); - }, - stepInfo.StepMatch()); - } - else - { - contextManager.StepContext().ExecutionStatus(cucumber_cpp::library::engine::Result::skipped); - reportHandler.StepSkipped(); - } - } - - void TestExecutionImpl::RunStepMatch(std::monostate /* not used */) - { - contextManager.StepContext().ExecutionStatus(cucumber_cpp::library::engine::Result::undefined); - } - - void TestExecutionImpl::RunStepMatch(const std::vector& /* not used */) - { - contextManager.StepContext().ExecutionStatus(cucumber_cpp::library::engine::Result::ambiguous); - } - - void TestExecutionImpl::RunStepMatch(const StepMatch& stepMatch) - { - executionPolicy.RunStep(contextManager, stepMatch); - } - - TestExecutionImplWithoutDefaultGoogleListener::TestExecutionImplWithoutDefaultGoogleListener(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution, const Policy& executionPolicy) - : TestExecutionImpl{ contextManager, reportHandler, hookExecution, executionPolicy } - { - auto& listeners = testing::UnitTest::GetInstance()->listeners(); - defaultEventListener = listeners.Release(listeners.default_result_printer()); - } - - TestExecutionImplWithoutDefaultGoogleListener::~TestExecutionImplWithoutDefaultGoogleListener() - { - auto& listeners = testing::UnitTest::GetInstance()->listeners(); - listeners.Append(defaultEventListener); - } -} diff --git a/cucumber_cpp/library/engine/TestExecution.hpp b/cucumber_cpp/library/engine/TestExecution.hpp deleted file mode 100644 index 904d8961..00000000 --- a/cucumber_cpp/library/engine/TestExecution.hpp +++ /dev/null @@ -1,153 +0,0 @@ -#ifndef ENGINE_TESTEXECUTION_HPP -#define ENGINE_TESTEXECUTION_HPP - -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FailureHandler.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/HookExecutor.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include "cucumber_cpp/library/util/Immoveable.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct TestExecution - { - protected: - ~TestExecution() = default; - - public: - struct ProgramScope; - struct FeatureScope; - struct RuleScope; - struct ScenarioScope; - - struct Policy; - - [[nodiscard]] virtual ProgramScope StartRun() = 0; - [[nodiscard]] virtual FeatureScope StartFeature(const cucumber_cpp::library::engine::FeatureInfo& featureInfo) = 0; - [[nodiscard]] virtual RuleScope StartRule(const cucumber_cpp::library::engine::RuleInfo& ruleInfo) = 0; - [[nodiscard]] virtual ScenarioScope StartScenario(const cucumber_cpp::library::engine::ScenarioInfo& scenarioInfo) = 0; - - virtual void RunStep(const cucumber_cpp::library::engine::StepInfo& stepInfo) = 0; - }; - - struct TestExecution::ProgramScope : library::util::Immoveable - { - ProgramScope(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution); - - Result ExecutionStatus() const; - - private: - cucumber_cpp::library::engine::ContextManager& contextManager; - report::ReportForwarder::ProgramScope scopedProgramReport; - HookExecutor::ProgramScope scopedProgramHook; - }; - - struct TestExecution::FeatureScope : library::util::Immoveable - { - FeatureScope(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution, const cucumber_cpp::library::engine::FeatureInfo& featureInfo); - - Result ExecutionStatus() const; - - private: - cucumber_cpp::library::engine::ContextManager::ScopedFeatureContext scopedFeatureContext; - report::ReportForwarder::FeatureScope scopedFeatureReport; - HookExecutor::FeatureScope scopedFeatureHook; - }; - - struct TestExecution::RuleScope : library::util::Immoveable - { - RuleScope(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, const cucumber_cpp::library::engine::RuleInfo& ruleInfo); - - private: - cucumber_cpp::library::engine::ContextManager::ScopedRuleContext scopedRuleContext; - report::ReportForwarder::RuleScope scopedRuleReport; - }; - - struct TestExecution::ScenarioScope : library::util::Immoveable - { - ScenarioScope(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution, const cucumber_cpp::library::engine::ScenarioInfo& scenarioInfo); - - private: - cucumber_cpp::library::engine::ContextManager::ScopedScenarioContext scopedScenarioContext; - report::ReportForwarder::ScenarioScope scopedScenarioReport; - HookExecutor::ScenarioScope scopedScenarioHook; - }; - - struct TestExecution::Policy - { - protected: - ~Policy() = default; - - public: - virtual void RunStep(cucumber_cpp::library::engine::ContextManager& contextManager, const StepMatch& stepMatch) const = 0; - }; - - struct DryRunPolicy : TestExecution::Policy - { - virtual ~DryRunPolicy() = default; - - void RunStep(cucumber_cpp::library::engine::ContextManager& contextManager, const StepMatch& stepMatch) const override; - }; - - struct ExecuteRunPolicy : TestExecution::Policy - { - virtual ~ExecuteRunPolicy() = default; - - void RunStep(cucumber_cpp::library::engine::ContextManager& contextManager, const StepMatch& stepMatch) const override; - }; - - extern const DryRunPolicy dryRunPolicy; - extern const ExecuteRunPolicy executeRunPolicy; - - struct TestExecutionImpl : TestExecution - { - TestExecutionImpl(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution, const Policy& executionPolicy = executeRunPolicy); - - TestExecutionImpl(const TestExecutionImpl&) = delete; - TestExecutionImpl& operator=(const TestExecutionImpl&) = delete; - - virtual ~TestExecutionImpl(); - - ProgramScope StartRun() override; - FeatureScope StartFeature(const cucumber_cpp::library::engine::FeatureInfo& featureInfo) override; - RuleScope StartRule(const cucumber_cpp::library::engine::RuleInfo& ruleInfo) override; - ScenarioScope StartScenario(const cucumber_cpp::library::engine::ScenarioInfo& scenarioInfo) override; - - void RunStep(const cucumber_cpp::library::engine::StepInfo& stepInfo) override; - - private: - void RunStepMatch(std::monostate); - void RunStepMatch(const std::vector&); - void RunStepMatch(const StepMatch& stepMatch); - - cucumber_cpp::library::engine::ContextManager& contextManager; - report::ReportForwarder& reportHandler; - HookExecutor& hookExecution; - - const Policy& executionPolicy; - - TestAssertionHandlerImpl testAssertionHandler{ contextManager, reportHandler }; - GoogleTestEventListener googleTestEventListener{ testAssertionHandler }; - }; - - struct TestExecutionImplWithoutDefaultGoogleListener : TestExecutionImpl - { - TestExecutionImplWithoutDefaultGoogleListener(cucumber_cpp::library::engine::ContextManager& contextManager, report::ReportForwarder& reportHandler, HookExecutor& hookExecution, const Policy& executionPolicy = executeRunPolicy); - ~TestExecutionImplWithoutDefaultGoogleListener() override; - - private: - testing::TestEventListener* defaultEventListener; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/TestRunner.cpp b/cucumber_cpp/library/engine/TestRunner.cpp deleted file mode 100644 index c56709ca..00000000 --- a/cucumber_cpp/library/engine/TestRunner.cpp +++ /dev/null @@ -1,104 +0,0 @@ - -#include "cucumber_cpp/library/engine/TestRunner.hpp" -#include "cucumber_cpp/library/engine/FeatureFactory.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/TestExecution.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - TestRunner* TestRunner::instance = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - - TestRunner::TestRunner() - { - TestRunner::instance = this; - } - - TestRunner::~TestRunner() - { - TestRunner::instance = nullptr; - } - - TestRunner& TestRunner::Instance() - { - return *instance; - } - - TestRunnerImpl::TestRunnerImpl(FeatureTreeFactory& featureTreeFactory, cucumber_cpp::library::engine::TestExecution& testExecution) - : featureTreeFactory{ featureTreeFactory } - , testExecution{ testExecution } - { - } - - void TestRunnerImpl::Run(const std::vector>& features) - { - auto scope = testExecution.StartRun(); - - if (scope.ExecutionStatus() != Result::passed) - return; - - for (const auto& featurePtr : features) - RunFeature(*featurePtr); - } - - void TestRunnerImpl::NestedStep(StepType type, std::string step) - { - const auto nestedStep = featureTreeFactory.CreateStepInfo(type, std::move(step), *currentScenario, 0, 0, {}, ""); - testExecution.RunStep(*nestedStep); - } - - void TestRunnerImpl::RunFeature(const FeatureInfo& feature) - { - if (feature.Rules().empty() && feature.Scenarios().empty()) - return; - - const auto featureScope = testExecution.StartFeature(feature); - - if (featureScope.ExecutionStatus() != Result::passed) - return; - - RunRules(feature.Rules()); - RunScenarios(feature.Scenarios()); - } - - void TestRunnerImpl::RunRule(const RuleInfo& rule) - { - const auto ruleScope = testExecution.StartRule(rule); - - RunScenarios(rule.Scenarios()); - } - - void TestRunnerImpl::RunRules(const std::vector>& rules) - { - for (const auto& rule : rules) - RunRule(*rule); - } - - void TestRunnerImpl::RunScenario(const ScenarioInfo& scenario) - { - const auto scenarioScope = testExecution.StartScenario(scenario); - - currentScenario = &scenario; - - ExecuteSteps(scenario); - } - - void TestRunnerImpl::RunScenarios(const std::vector>& scenarios) - { - for (const auto& scenario : scenarios) - RunScenario(*scenario); - } - - void TestRunnerImpl::ExecuteSteps(const ScenarioInfo& scenario) - { - for (const auto& step : scenario.Children()) - testExecution.RunStep(*step); - } -} diff --git a/cucumber_cpp/library/engine/TestRunner.hpp b/cucumber_cpp/library/engine/TestRunner.hpp deleted file mode 100644 index bf813f8e..00000000 --- a/cucumber_cpp/library/engine/TestRunner.hpp +++ /dev/null @@ -1,73 +0,0 @@ -#ifndef ENGINE_TESTRUNNER_HPP -#define ENGINE_TESTRUNNER_HPP - -#include "cucumber_cpp/library/engine/FeatureFactory.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/TestExecution.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::report -{ - struct ReportHandlerV2; -} - -namespace cucumber_cpp::library::engine -{ - struct TestRunner - { - struct PendingException - {}; - - struct StepNotFoundException - {}; - - struct AmbiguousStepException - {}; - - protected: - TestRunner(); - ~TestRunner(); - - public: - TestRunner(const TestRunner&) = delete; - - static TestRunner& Instance(); - - virtual void Run(const std::vector>& feature) = 0; - virtual void NestedStep(StepType type, std::string step) = 0; - - private: - static TestRunner* instance; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - }; - - struct TestRunnerImpl : TestRunner - { - TestRunnerImpl(FeatureTreeFactory& featureTreeFactory, TestExecution& testExecution); - virtual ~TestRunnerImpl() = default; - - void Run(const std::vector>& feature) override; - - void NestedStep(StepType type, std::string step) override; - - private: - void ExecuteSteps(const ScenarioInfo& scenario); - void RunScenario(const ScenarioInfo& scenario); - void RunScenarios(const std::vector>& scenarios); - void RunRule(const RuleInfo& rule); - void RunRules(const std::vector>& rules); - void RunFeature(const FeatureInfo& feature); - - FeatureTreeFactory& featureTreeFactory; - TestExecution& testExecution; - - const ScenarioInfo* currentScenario = nullptr; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/test/CMakeLists.txt b/cucumber_cpp/library/engine/test/CMakeLists.txt index 39b9330e..1f4d530c 100644 --- a/cucumber_cpp/library/engine/test/CMakeLists.txt +++ b/cucumber_cpp/library/engine/test/CMakeLists.txt @@ -16,5 +16,4 @@ target_sources(cucumber_cpp.library.engine.test PRIVATE TestHookExecutorHooks.cpp TestStep.cpp TestStringTo.cpp - TestTestRunner.cpp ) diff --git a/cucumber_cpp/library/engine/test/TestFeatureFactory.cpp b/cucumber_cpp/library/engine/test/TestFeatureFactory.cpp index 65f4d16d..96acc4f7 100644 --- a/cucumber_cpp/library/engine/test/TestFeatureFactory.cpp +++ b/cucumber_cpp/library/engine/test/TestFeatureFactory.cpp @@ -1,312 +1,316 @@ -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/engine/FeatureFactory.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/test_helper/TemporaryFile.hpp" -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - - struct TestFeatureFactory : testing::Test - { - cucumber_expression::ParameterRegistry parameterRegistry; - StepRegistry stepRegistry{ parameterRegistry }; - FeatureTreeFactory featureTreeFactory{ stepRegistry }; - }; - - TEST_F(TestFeatureFactory, CreateEmptyFeature) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "Feature: Test feature\n" - "Custom\n" - "Description\n"; - - auto feature = featureTreeFactory.Create(tmp.Path(), ""); - EXPECT_THAT(feature->Title(), testing::StrEq("Test feature")); - EXPECT_THAT(feature->Description(), testing::StrEq("Custom\nDescription")); - EXPECT_THAT(feature->Tags(), testing::Eq(std::set>{})); - EXPECT_THAT(feature->Path(), testing::Eq(tmp.Path())); - EXPECT_THAT(feature->Line(), testing::Eq(1)); - EXPECT_THAT(feature->Column(), testing::Eq(1)); - EXPECT_THAT(feature->Rules().size(), testing::Eq(0)); - EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); - } - - TEST_F(TestFeatureFactory, CreateScenario) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "Feature: Test feature\n" - "Custom\n" - "Description\n" - " Scenario: Test scenario\n" - " Custom Scenario\n" - " Description\n"; - - const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - EXPECT_THAT(feature->Rules().size(), testing::Eq(0)); - EXPECT_THAT(feature->Scenarios().size(), testing::Eq(1)); - - const auto& scenario = feature->Scenarios().front(); - EXPECT_THAT(scenario->Title(), testing::StrEq("Test scenario")); - EXPECT_THAT(scenario->Description(), testing::StrEq(" Custom Scenario\n Description")); - EXPECT_THAT(scenario->Tags(), testing::Eq(std::set>{})); - EXPECT_THAT(scenario->Path(), testing::Eq(tmp.Path())); - EXPECT_THAT(scenario->Line(), testing::Eq(4)); - EXPECT_THAT(scenario->Column(), testing::Eq(3)); - EXPECT_THAT(scenario->Children().size(), testing::Eq(0)); - } - - TEST_F(TestFeatureFactory, CreateRules) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "Feature: Test feature\n" - "Custom\n" - "Description\n" - " Rule: Test rule\n" - " Custom Rule\n" - " Description1\n" - " Scenario: Test scenario1\n" - " Custom Scenario\n" - " Description\n" - " Rule: Test rule\n" - " Custom Rule\n" - " Description2\n" - " Scenario: Test scenario2\n" - " Custom Scenario\n" - " Description\n"; - - const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - EXPECT_THAT(feature->Rules().size(), testing::Eq(2)); - EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); - - const auto& rule1 = feature->Rules().front(); - EXPECT_THAT(rule1->Title(), testing::StrEq("Test rule")); - EXPECT_THAT(rule1->Description(), testing::StrEq(" Custom Rule\n Description1")); - EXPECT_THAT(rule1->Line(), testing::Eq(4)); - EXPECT_THAT(rule1->Column(), testing::Eq(3)); - EXPECT_THAT(rule1->Scenarios().size(), testing::Eq(1)); - - const auto& rule1scenario1 = rule1->Scenarios().front(); - EXPECT_THAT(rule1scenario1->Title(), testing::StrEq("Test scenario1")); - EXPECT_THAT(rule1scenario1->Line(), testing::Eq(7)); - - const auto& rule2 = feature->Rules().back(); - EXPECT_THAT(rule2->Title(), testing::StrEq("Test rule")); - EXPECT_THAT(rule2->Description(), testing::StrEq(" Custom Rule\n Description2")); - EXPECT_THAT(rule2->Line(), testing::Eq(10)); - EXPECT_THAT(rule2->Column(), testing::Eq(3)); - EXPECT_THAT(rule2->Scenarios().size(), testing::Eq(1)); - - const auto& rule2scenario1 = rule2->Scenarios().front(); - EXPECT_THAT(rule2scenario1->Title(), testing::StrEq("Test scenario2")); - EXPECT_THAT(rule2scenario1->Line(), testing::Eq(13)); - } - - TEST_F(TestFeatureFactory, CreateSteps) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "Feature: Test feature\n" - " Scenario: Test scenario1\n" - " Given I have a step1\n" - " When I do something1\n" - " Then I expect something1\n" - " Rule: Test rule\n" - " Scenario: Test scenario2\n" - " Given I have a step2\n" - " When I do something2\n" - " Then I expect something2\n" - " Rule: Test rule\n" - " Scenario: Test scenario3\n" - " Given I have a step3\n" - " When I do something3\n" - " Then I expect something3\n"; - - const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - EXPECT_THAT(feature->Rules().size(), testing::Eq(2)); - EXPECT_THAT(feature->Scenarios().size(), testing::Eq(1)); - - const auto& scenario1 = feature->Scenarios()[0]; - EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); - EXPECT_THAT(scenario1->Children().size(), testing::Eq(3)); - EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario1->Children()[1]->Type(), testing::Eq(StepType::when)); - EXPECT_THAT(scenario1->Children()[2]->Type(), testing::Eq(StepType::then)); - EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("I have a step1")); - EXPECT_THAT(scenario1->Children()[1]->Text(), testing::StrEq("I do something1")); - EXPECT_THAT(scenario1->Children()[2]->Text(), testing::StrEq("I expect something1")); - - const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; - EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); - EXPECT_THAT(scenario2->Children().size(), testing::Eq(3)); - EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario2->Children()[1]->Type(), testing::Eq(StepType::when)); - EXPECT_THAT(scenario2->Children()[2]->Type(), testing::Eq(StepType::then)); - EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("I have a step2")); - EXPECT_THAT(scenario2->Children()[1]->Text(), testing::StrEq("I do something2")); - EXPECT_THAT(scenario2->Children()[2]->Text(), testing::StrEq("I expect something2")); - - const auto& scenario3 = feature->Rules()[1]->Scenarios()[0]; - EXPECT_THAT(scenario3->Title(), testing::StrEq("Test scenario3")); - EXPECT_THAT(scenario3->Children().size(), testing::Eq(3)); - EXPECT_THAT(scenario3->Children()[0]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario3->Children()[1]->Type(), testing::Eq(StepType::when)); - EXPECT_THAT(scenario3->Children()[2]->Type(), testing::Eq(StepType::then)); - EXPECT_THAT(scenario3->Children()[0]->Text(), testing::StrEq("I have a step3")); - EXPECT_THAT(scenario3->Children()[1]->Text(), testing::StrEq("I do something3")); - EXPECT_THAT(scenario3->Children()[2]->Text(), testing::StrEq("I expect something3")); - } - - TEST_F(TestFeatureFactory, CreateBackground) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "Feature: Test feature\n" - " Background: Test background\n" - " Given a background step1\n" - " Scenario: Test scenario1\n" - " Given I have a step1\n" - " Rule: Test rule\n" - " Background: Test background\n" - " Given a background step2\n" - " Scenario: Test scenario2\n" - " Given I have a step2\n" - " Rule: Test rule\n" - " Scenario: Test scenario3\n" - " Given I have a step3\n"; - - const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - - const auto& scenario1 = feature->Scenarios()[0]; - EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); - EXPECT_THAT(scenario1->Children().size(), testing::Eq(2)); - EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario1->Children()[1]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("a background step1")); - EXPECT_THAT(scenario1->Children()[1]->Text(), testing::StrEq("I have a step1")); - - const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; - EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); - EXPECT_THAT(scenario2->Children().size(), testing::Eq(3)); - EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario2->Children()[1]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("a background step1")); - EXPECT_THAT(scenario2->Children()[1]->Text(), testing::StrEq("a background step2")); - EXPECT_THAT(scenario2->Children()[2]->Text(), testing::StrEq("I have a step2")); - - const auto& scenario3 = feature->Rules()[1]->Scenarios()[0]; - EXPECT_THAT(scenario3->Title(), testing::StrEq("Test scenario3")); - EXPECT_THAT(scenario3->Children().size(), testing::Eq(2)); - EXPECT_THAT(scenario3->Children()[0]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario3->Children()[1]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario3->Children()[0]->Text(), testing::StrEq("a background step1")); - EXPECT_THAT(scenario3->Children()[1]->Text(), testing::StrEq("I have a step3")); - } - - TEST_F(TestFeatureFactory, CreateTagsTags) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "@feature\n" - "Feature: Test feature\n" - " @scenario1\n" - " Scenario: Test scenario1\n" - " Given I have a step1\n" - " @rule\n" - " Rule: Test rule\n" - " @scenario2\n" - " Scenario: Test scenario2\n" - " Given I have a step2\n"; - - const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - - const auto& scenario1 = feature->Scenarios()[0]; - EXPECT_THAT(scenario1->Tags(), testing::Eq(std::set>{ "@feature", "@scenario1" })); - - const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; - EXPECT_THAT(scenario2->Tags(), testing::Eq(std::set>{ "@feature", "@rule", "@scenario2" })); - } - - TEST_F(TestFeatureFactory, SelectTags) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "@feature\n" - "Feature: Test feature\n" - " Rule: Test rule1\n" - " @scenario1 @debug\n" - " Scenario: Test scenario1\n" - " Given I have a step1\n" - " @rule\n" - " Rule: Test rule2\n" - " @scenario2 @debug\n" - " Scenario: Test scenario2\n" - " Given I have a step2\n"; - - const auto feature = featureTreeFactory.Create(tmp.Path(), "@debug & @rule"); - EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); - EXPECT_THAT(feature->Rules().size(), testing::Eq(1)); - - const auto& scenario1 = feature->Rules()[0]->Scenarios()[0]; - EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario2")); - } - - TEST_F(TestFeatureFactory, CreateMultipleScenariosInOneRule) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "Feature: Test feature\n" - " Rule: Test rule\n" - " Scenario: Test scenario1\n" - " Given I have a step1\n" - " Scenario: Test scenario2\n" - " Given I have a step2\n"; - - const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - EXPECT_THAT(feature->Rules().size(), testing::Eq(1)); - - const auto& rule = feature->Rules()[0]; - EXPECT_THAT(rule->Scenarios().size(), testing::Eq(2)); - - const auto& scenario1 = rule->Scenarios()[0]; - EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); - EXPECT_THAT(scenario1->Children().size(), testing::Eq(1)); - EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("I have a step1")); - - const auto& scenario2 = rule->Scenarios()[1]; - EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); - EXPECT_THAT(scenario2->Children().size(), testing::Eq(1)); - EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); - EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("I have a step2")); - } - - TEST_F(TestFeatureFactory, CreateTable) - { - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - - tmp << "Feature: Test feature\n" - " Scenario: Test scenario1\n" - " Given I have a step1\n" - " | a | b |\n" - " | c | d |\n"; - - const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - - const auto& scenario1 = feature->Scenarios()[0]; - EXPECT_THAT(scenario1->Children().size(), testing::Eq(1)); - EXPECT_THAT(scenario1->Children()[0]->Table()[0][0].As(), testing::StrEq("a")); - EXPECT_THAT(scenario1->Children()[0]->Table()[0][1].As(), testing::StrEq("b")); - EXPECT_THAT(scenario1->Children()[0]->Table()[1][0].As(), testing::StrEq("c")); - EXPECT_THAT(scenario1->Children()[0]->Table()[1][1].As(), testing::StrEq("d")); - } -} +// #include "cucumber_cpp/CucumberCpp.hpp" +// #include "cucumber_cpp/library/StepRegistry.hpp" +// #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +// #include "cucumber_cpp/library/engine/FeatureFactory.hpp" +// #include "cucumber_cpp/library/engine/StepType.hpp" +// #include "cucumber_cpp/library/engine/test_helper/TemporaryFile.hpp" +// #include +// #include +// #include +// #include +// #include +// #include +// #include + +// namespace cucumber_cpp::library::engine +// { + +// struct TestFeatureFactory : testing::Test +// { +// cucumber_expression::ParameterRegistry parameterRegistry; +// StepRegistry stepRegistry{ parameterRegistry }; + +// std::shared_ptr contextStorageFactory = std::make_shared(); +// GoogleTestBuilder featureTreeFactory{ std::make_unique(contextStorageFactory), stepRegistry }; +// }; + +// TEST_F(TestFeatureFactory, CreateEmptyFeature) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "Feature: Test feature\n" +// "Custom\n" +// "Description\n"; + +// auto feature = featureTreeFactory.Create(tmp.Path(), ""); +// EXPECT_THAT(feature->Title(), testing::StrEq("Test feature")); +// EXPECT_THAT(feature->Description(), testing::StrEq("Custom\nDescription")); +// EXPECT_THAT(feature->Tags(), testing::Eq(std::set>{})); +// EXPECT_THAT(feature->Path(), testing::Eq(tmp.Path())); +// EXPECT_THAT(feature->Line(), testing::Eq(1)); +// EXPECT_THAT(feature->Column(), testing::Eq(1)); +// EXPECT_THAT(feature->Rules().size(), testing::Eq(0)); +// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); +// } + +// TEST_F(TestFeatureFactory, CreateScenario) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "Feature: Test feature\n" +// "Custom\n" +// "Description\n" +// " Scenario: Test scenario\n" +// " Custom Scenario\n" +// " Description\n"; + +// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); +// EXPECT_THAT(feature->Rules().size(), testing::Eq(0)); +// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(1)); + +// const auto& scenario = feature->Scenarios().front(); +// EXPECT_THAT(scenario->Title(), testing::StrEq("Test scenario")); +// EXPECT_THAT(scenario->Description(), testing::StrEq(" Custom Scenario\n Description")); +// EXPECT_THAT(scenario->Tags(), testing::Eq(std::set>{})); +// EXPECT_THAT(scenario->Path(), testing::Eq(tmp.Path())); +// EXPECT_THAT(scenario->Line(), testing::Eq(4)); +// EXPECT_THAT(scenario->Column(), testing::Eq(3)); +// EXPECT_THAT(scenario->Children().size(), testing::Eq(0)); +// } + +// TEST_F(TestFeatureFactory, CreateRules) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "Feature: Test feature\n" +// "Custom\n" +// "Description\n" +// " Rule: Test rule\n" +// " Custom Rule\n" +// " Description1\n" +// " Scenario: Test scenario1\n" +// " Custom Scenario\n" +// " Description\n" +// " Rule: Test rule\n" +// " Custom Rule\n" +// " Description2\n" +// " Scenario: Test scenario2\n" +// " Custom Scenario\n" +// " Description\n"; + +// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); +// EXPECT_THAT(feature->Rules().size(), testing::Eq(2)); +// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); + +// const auto& rule1 = feature->Rules().front(); +// EXPECT_THAT(rule1->Title(), testing::StrEq("Test rule")); +// EXPECT_THAT(rule1->Description(), testing::StrEq(" Custom Rule\n Description1")); +// EXPECT_THAT(rule1->Line(), testing::Eq(4)); +// EXPECT_THAT(rule1->Column(), testing::Eq(3)); +// EXPECT_THAT(rule1->Scenarios().size(), testing::Eq(1)); + +// const auto& rule1scenario1 = rule1->Scenarios().front(); +// EXPECT_THAT(rule1scenario1->Title(), testing::StrEq("Test scenario1")); +// EXPECT_THAT(rule1scenario1->Line(), testing::Eq(7)); + +// const auto& rule2 = feature->Rules().back(); +// EXPECT_THAT(rule2->Title(), testing::StrEq("Test rule")); +// EXPECT_THAT(rule2->Description(), testing::StrEq(" Custom Rule\n Description2")); +// EXPECT_THAT(rule2->Line(), testing::Eq(10)); +// EXPECT_THAT(rule2->Column(), testing::Eq(3)); +// EXPECT_THAT(rule2->Scenarios().size(), testing::Eq(1)); + +// const auto& rule2scenario1 = rule2->Scenarios().front(); +// EXPECT_THAT(rule2scenario1->Title(), testing::StrEq("Test scenario2")); +// EXPECT_THAT(rule2scenario1->Line(), testing::Eq(13)); +// } + +// TEST_F(TestFeatureFactory, CreateSteps) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "Feature: Test feature\n" +// " Scenario: Test scenario1\n" +// " Given I have a step1\n" +// " When I do something1\n" +// " Then I expect something1\n" +// " Rule: Test rule\n" +// " Scenario: Test scenario2\n" +// " Given I have a step2\n" +// " When I do something2\n" +// " Then I expect something2\n" +// " Rule: Test rule\n" +// " Scenario: Test scenario3\n" +// " Given I have a step3\n" +// " When I do something3\n" +// " Then I expect something3\n"; + +// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); +// EXPECT_THAT(feature->Rules().size(), testing::Eq(2)); +// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(1)); + +// const auto& scenario1 = feature->Scenarios()[0]; +// EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); +// EXPECT_THAT(scenario1->Children().size(), testing::Eq(3)); +// EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario1->Children()[1]->Type(), testing::Eq(StepType::when)); +// EXPECT_THAT(scenario1->Children()[2]->Type(), testing::Eq(StepType::then)); +// EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("I have a step1")); +// EXPECT_THAT(scenario1->Children()[1]->Text(), testing::StrEq("I do something1")); +// EXPECT_THAT(scenario1->Children()[2]->Text(), testing::StrEq("I expect something1")); + +// const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; +// EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); +// EXPECT_THAT(scenario2->Children().size(), testing::Eq(3)); +// EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario2->Children()[1]->Type(), testing::Eq(StepType::when)); +// EXPECT_THAT(scenario2->Children()[2]->Type(), testing::Eq(StepType::then)); +// EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("I have a step2")); +// EXPECT_THAT(scenario2->Children()[1]->Text(), testing::StrEq("I do something2")); +// EXPECT_THAT(scenario2->Children()[2]->Text(), testing::StrEq("I expect something2")); + +// const auto& scenario3 = feature->Rules()[1]->Scenarios()[0]; +// EXPECT_THAT(scenario3->Title(), testing::StrEq("Test scenario3")); +// EXPECT_THAT(scenario3->Children().size(), testing::Eq(3)); +// EXPECT_THAT(scenario3->Children()[0]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario3->Children()[1]->Type(), testing::Eq(StepType::when)); +// EXPECT_THAT(scenario3->Children()[2]->Type(), testing::Eq(StepType::then)); +// EXPECT_THAT(scenario3->Children()[0]->Text(), testing::StrEq("I have a step3")); +// EXPECT_THAT(scenario3->Children()[1]->Text(), testing::StrEq("I do something3")); +// EXPECT_THAT(scenario3->Children()[2]->Text(), testing::StrEq("I expect something3")); +// } + +// TEST_F(TestFeatureFactory, CreateBackground) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "Feature: Test feature\n" +// " Background: Test background\n" +// " Given a background step1\n" +// " Scenario: Test scenario1\n" +// " Given I have a step1\n" +// " Rule: Test rule\n" +// " Background: Test background\n" +// " Given a background step2\n" +// " Scenario: Test scenario2\n" +// " Given I have a step2\n" +// " Rule: Test rule\n" +// " Scenario: Test scenario3\n" +// " Given I have a step3\n"; + +// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); + +// const auto& scenario1 = feature->Scenarios()[0]; +// EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); +// EXPECT_THAT(scenario1->Children().size(), testing::Eq(2)); +// EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario1->Children()[1]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("a background step1")); +// EXPECT_THAT(scenario1->Children()[1]->Text(), testing::StrEq("I have a step1")); + +// const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; +// EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); +// EXPECT_THAT(scenario2->Children().size(), testing::Eq(3)); +// EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario2->Children()[1]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("a background step1")); +// EXPECT_THAT(scenario2->Children()[1]->Text(), testing::StrEq("a background step2")); +// EXPECT_THAT(scenario2->Children()[2]->Text(), testing::StrEq("I have a step2")); + +// const auto& scenario3 = feature->Rules()[1]->Scenarios()[0]; +// EXPECT_THAT(scenario3->Title(), testing::StrEq("Test scenario3")); +// EXPECT_THAT(scenario3->Children().size(), testing::Eq(2)); +// EXPECT_THAT(scenario3->Children()[0]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario3->Children()[1]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario3->Children()[0]->Text(), testing::StrEq("a background step1")); +// EXPECT_THAT(scenario3->Children()[1]->Text(), testing::StrEq("I have a step3")); +// } + +// TEST_F(TestFeatureFactory, CreateTagsTags) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "@feature\n" +// "Feature: Test feature\n" +// " @scenario1\n" +// " Scenario: Test scenario1\n" +// " Given I have a step1\n" +// " @rule\n" +// " Rule: Test rule\n" +// " @scenario2\n" +// " Scenario: Test scenario2\n" +// " Given I have a step2\n"; + +// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); + +// const auto& scenario1 = feature->Scenarios()[0]; +// EXPECT_THAT(scenario1->Tags(), testing::Eq(std::set>{ "@feature", "@scenario1" })); + +// const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; +// EXPECT_THAT(scenario2->Tags(), testing::Eq(std::set>{ "@feature", "@rule", "@scenario2" })); +// } + +// TEST_F(TestFeatureFactory, SelectTags) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "@feature\n" +// "Feature: Test feature\n" +// " Rule: Test rule1\n" +// " @scenario1 @debug\n" +// " Scenario: Test scenario1\n" +// " Given I have a step1\n" +// " @rule\n" +// " Rule: Test rule2\n" +// " @scenario2 @debug\n" +// " Scenario: Test scenario2\n" +// " Given I have a step2\n"; + +// const auto feature = featureTreeFactory.Create(tmp.Path(), "@debug & @rule"); +// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); +// EXPECT_THAT(feature->Rules().size(), testing::Eq(1)); + +// const auto& scenario1 = feature->Rules()[0]->Scenarios()[0]; +// EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario2")); +// } + +// TEST_F(TestFeatureFactory, CreateMultipleScenariosInOneRule) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "Feature: Test feature\n" +// " Rule: Test rule\n" +// " Scenario: Test scenario1\n" +// " Given I have a step1\n" +// " Scenario: Test scenario2\n" +// " Given I have a step2\n"; + +// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); +// EXPECT_THAT(feature->Rules().size(), testing::Eq(1)); + +// const auto& rule = feature->Rules()[0]; +// EXPECT_THAT(rule->Scenarios().size(), testing::Eq(2)); + +// const auto& scenario1 = rule->Scenarios()[0]; +// EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); +// EXPECT_THAT(scenario1->Children().size(), testing::Eq(1)); +// EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("I have a step1")); + +// const auto& scenario2 = rule->Scenarios()[1]; +// EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); +// EXPECT_THAT(scenario2->Children().size(), testing::Eq(1)); +// EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); +// EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("I have a step2")); +// } + +// TEST_F(TestFeatureFactory, CreateTable) +// { +// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; + +// tmp << "Feature: Test feature\n" +// " Scenario: Test scenario1\n" +// " Given I have a step1\n" +// " | a | b |\n" +// " | c | d |\n"; + +// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); + +// const auto& scenario1 = feature->Scenarios()[0]; +// EXPECT_THAT(scenario1->Children().size(), testing::Eq(1)); +// EXPECT_THAT(scenario1->Children()[0]->Table()[0][0].As(), testing::StrEq("a")); +// EXPECT_THAT(scenario1->Children()[0]->Table()[0][1].As(), testing::StrEq("b")); +// EXPECT_THAT(scenario1->Children()[0]->Table()[1][0].As(), testing::StrEq("c")); +// EXPECT_THAT(scenario1->Children()[0]->Table()[1][1].As(), testing::StrEq("d")); +// } +// } diff --git a/cucumber_cpp/library/engine/test/TestHookExecutor.cpp b/cucumber_cpp/library/engine/test/TestHookExecutor.cpp index db1f2314..9f260859 100644 --- a/cucumber_cpp/library/engine/test/TestHookExecutor.cpp +++ b/cucumber_cpp/library/engine/test/TestHookExecutor.cpp @@ -1,98 +1,98 @@ -#include "cucumber_cpp/library/engine/FailureHandler.hpp" -#include "cucumber_cpp/library/engine/HookExecutor.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp" -#include "cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.hpp" -#include "cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - namespace - { - std::function expectFatalStatement; - } - - struct TestHookExecutor : testing::Test - { - std::optional contextManagerInstance{ std::in_place }; - std::optional hookExecutor{ *contextManagerInstance }; - - test_helper::FailureHandlerFixture failureHandlerFixture{ *contextManagerInstance }; - }; - - TEST_F(TestHookExecutor, Construct) - { - } - - TEST_F(TestHookExecutor, ExecuteProgramHooks) - { - ASSERT_FALSE(contextManagerInstance->ProgramContext().Contains("hookBeforeAll")); - ASSERT_FALSE(contextManagerInstance->ProgramContext().Contains("hookAfterAll")); - - { - auto scope = hookExecutor->BeforeAll(); - EXPECT_TRUE(contextManagerInstance->ProgramContext().Contains("hookBeforeAll")); - } - EXPECT_TRUE(contextManagerInstance->ProgramContext().Contains("hookAfterAll")); - } - - TEST_F(TestHookExecutor, ExecuteBeforeFeature) - { - ASSERT_FALSE(contextManagerInstance->FeatureContext().Contains("hookBeforeFeature")); - ASSERT_FALSE(contextManagerInstance->FeatureContext().Contains("hookAfterFeature")); - - { - auto scope = hookExecutor->FeatureStart(); - EXPECT_TRUE(contextManagerInstance->FeatureContext().Contains("hookBeforeFeature")); - } - EXPECT_TRUE(contextManagerInstance->FeatureContext().Contains("hookAfterFeature")); - } - - TEST_F(TestHookExecutor, ExecuteBeforeScenario) - { - ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookBeforeScenario")); - ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookAfterScenario")); - - { - auto scope = hookExecutor->ScenarioStart(); - EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookBeforeScenario")); - } - EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookAfterScenario")); - } - - TEST_F(TestHookExecutor, ExecuteBeforeStep) - { - ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookBeforeStep")); - ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookAfterStep")); - - { - auto scope = hookExecutor->StepStart(); - EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookBeforeStep")); - } - EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookAfterStep")); - } - - TEST_F(TestHookExecutor, BeforeHookError) - { - contextManagerInstance.emplace(std::set>{ "@errorbefore" }); - hookExecutor.emplace(*contextManagerInstance); - - report::test_helper::ReportForwarderMock reportHandler{ *contextManagerInstance }; - TestAssertionHandlerImpl assertionHandler{ *contextManagerInstance, reportHandler }; - - expectFatalStatement = [this] - { - auto hook = hookExecutor->StepStart(); - }; - - EXPECT_FATAL_FAILURE(expectFatalStatement(), "Expected: false"); - } -} +// #include "cucumber_cpp/library/engine/FailureHandler.hpp" +// #include "cucumber_cpp/library/engine/HookExecutor.hpp" +// #include "cucumber_cpp/library/engine/Result.hpp" +// #include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp" +// #include "cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.hpp" +// #include "cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp" +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include + +// namespace cucumber_cpp::library::engine +// { +// namespace +// { +// std::function expectFatalStatement; +// } + +// struct TestHookExecutor : testing::Test +// { +// std::optional contextManagerInstance{ std::in_place }; +// std::optional hookExecutor{ *contextManagerInstance }; + +// test_helper::FailureHandlerFixture failureHandlerFixture{ *contextManagerInstance }; +// }; + +// TEST_F(TestHookExecutor, Construct) +// { +// } + +// TEST_F(TestHookExecutor, ExecuteProgramHooks) +// { +// ASSERT_FALSE(contextManagerInstance->ProgramContext().Contains("hookBeforeAll")); +// ASSERT_FALSE(contextManagerInstance->ProgramContext().Contains("hookAfterAll")); + +// { +// auto scope = hookExecutor->BeforeAll(); +// EXPECT_TRUE(contextManagerInstance->ProgramContext().Contains("hookBeforeAll")); +// } +// EXPECT_TRUE(contextManagerInstance->ProgramContext().Contains("hookAfterAll")); +// } + +// TEST_F(TestHookExecutor, ExecuteBeforeFeature) +// { +// ASSERT_FALSE(contextManagerInstance->FeatureContext().Contains("hookBeforeFeature")); +// ASSERT_FALSE(contextManagerInstance->FeatureContext().Contains("hookAfterFeature")); + +// { +// auto scope = hookExecutor->FeatureStart(); +// EXPECT_TRUE(contextManagerInstance->FeatureContext().Contains("hookBeforeFeature")); +// } +// EXPECT_TRUE(contextManagerInstance->FeatureContext().Contains("hookAfterFeature")); +// } + +// TEST_F(TestHookExecutor, ExecuteBeforeScenario) +// { +// ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookBeforeScenario")); +// ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookAfterScenario")); + +// { +// auto scope = hookExecutor->ScenarioStart(); +// EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookBeforeScenario")); +// } +// EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookAfterScenario")); +// } + +// TEST_F(TestHookExecutor, ExecuteBeforeStep) +// { +// ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookBeforeStep")); +// ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookAfterStep")); + +// { +// auto scope = hookExecutor->StepStart(); +// EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookBeforeStep")); +// } +// EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookAfterStep")); +// } + +// TEST_F(TestHookExecutor, BeforeHookError) +// { +// contextManagerInstance.emplace(std::set>{ "@errorbefore" }); +// hookExecutor.emplace(*contextManagerInstance); + +// report::test_helper::ReportForwarderMock reportHandler{ *contextManagerInstance }; +// TestAssertionHandlerImpl assertionHandler{ *contextManagerInstance, reportHandler }; + +// expectFatalStatement = [this] +// { +// auto hook = hookExecutor->StepStart(); +// }; + +// EXPECT_FATAL_FAILURE(expectFatalStatement(), "Expected: false"); +// } +// } diff --git a/cucumber_cpp/library/engine/test/TestStep.cpp b/cucumber_cpp/library/engine/test/TestStep.cpp index a8f7e141..764c713f 100644 --- a/cucumber_cpp/library/engine/test/TestStep.cpp +++ b/cucumber_cpp/library/engine/test/TestStep.cpp @@ -3,8 +3,8 @@ #include "cucumber_cpp/library/engine/Step.hpp" #include "cucumber_cpp/library/engine/Table.hpp" #include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp" -#include "cucumber_cpp/library/engine/test_helper/TestRunnerMock.hpp" #include "gmock/gmock.h" +#include "gtest/gtest.h" #include #include #include @@ -37,7 +37,6 @@ namespace cucumber_cpp::library::engine library::engine::test_helper::ContextManagerInstance contextManager; - engine::test_helper::TestRunnerMock testRunnerMock; StepMock step{ contextManager.StepContext(), table, docString }; }; @@ -74,4 +73,36 @@ namespace cucumber_cpp::library::engine { ASSERT_THROW(step.Pending("message"), Step::StepPending); } + + ///////////////////////////////////////// + class FooTest : public testing::TestWithParam + { + // You can implement all the usual fixture class members here. + // To access the test parameter, call GetParam() from class + // TestWithParam. + }; + + // Or, when you want to add parameters to a pre-existing fixture class: + class BaseTest : public testing::Test + { + }; + + class BarTest : public BaseTest + , public testing::WithParamInterface + { + }; + + TEST_P(FooTest, DoesBlah) + { + // Inside a test, access the test parameter with the GetParam() method + // of the TestWithParam class: + } + + TEST_P(FooTest, HasBlahBlah) + { + } + + INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe, + FooTest, + testing::Values("meeny", "miny", "moe")); } diff --git a/cucumber_cpp/library/engine/test/TestTestRunner.cpp b/cucumber_cpp/library/engine/test/TestTestRunner.cpp deleted file mode 100644 index e733f269..00000000 --- a/cucumber_cpp/library/engine/test/TestTestRunner.cpp +++ /dev/null @@ -1,223 +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/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FeatureFactory.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/HookExecutor.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/engine/TestExecution.hpp" -#include "cucumber_cpp/library/engine/TestRunner.hpp" -#include "cucumber_cpp/library/engine/test_helper/TemporaryFile.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include "cucumber_cpp/library/report/StdOutReport.hpp" -#include "gmock/gmock.h" -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct TestExecutionMockInstance : TestExecution - { - TestExecutionMockInstance() - { - reporters.Add("console", std::make_unique()); - reporters.Use("console"); - } - - virtual ~TestExecutionMockInstance() = default; - - MOCK_METHOD(void, StartRunMock, ()); - MOCK_METHOD(void, StartFeatureMock, (const FeatureInfo& featureInfo)); - MOCK_METHOD(void, StartRuleMock, (const RuleInfo& ruleInfo)); - MOCK_METHOD(void, StartScenarioMock, (const ScenarioInfo& scenarioInfo)); - - MOCK_METHOD(void, RunStepMock, (const StepInfo& stepInfo)); - - [[nodiscard]] ProgramScope StartRun() override - { - StartRunMock(); - return testExecutionImpl.StartRun(); - } - - [[nodiscard]] FeatureScope StartFeature(const cucumber_cpp::library::engine::FeatureInfo& featureInfo) override - { - StartFeatureMock(featureInfo); - return testExecutionImpl.StartFeature(featureInfo); - } - - [[nodiscard]] RuleScope StartRule(const cucumber_cpp::library::engine::RuleInfo& ruleInfo) override - { - StartRuleMock(ruleInfo); - return testExecutionImpl.StartRule(ruleInfo); - } - - [[nodiscard]] ScenarioScope StartScenario(const cucumber_cpp::library::engine::ScenarioInfo& scenarioInfo) override - { - StartScenarioMock(scenarioInfo); - return testExecutionImpl.StartScenario(scenarioInfo); - } - - void RunStep(const cucumber_cpp::library::engine::StepInfo& stepInfo) override - { - RunStepMock(stepInfo); - testExecutionImpl.RunStep(stepInfo); - } - - ContextManager contextManager{ std::make_shared() }; - - HookExecutorImpl hookExecutor{ contextManager }; - report::ReportForwarderImpl reporters{ contextManager }; - - TestExecutionImpl testExecutionImpl{ contextManager, reporters, hookExecutor }; - }; - - struct TestTestRunner : testing::Test - { - cucumber_expression::ParameterRegistry parameterRegistry; - StepRegistry stepRegistry{ parameterRegistry }; - FeatureTreeFactory featureTreeFactory{ stepRegistry }; - - testing::StrictMock testExecutionMock; - TestRunnerImpl runner{ featureTreeFactory, testExecutionMock }; - - std::vector> features; - - ContextManager& contextManager = testExecutionMock.contextManager; - }; - - TEST_F(TestTestRunner, StartProgramContext) - { - EXPECT_CALL(testExecutionMock, StartRunMock()); - - runner.Run({}); - } - - TEST_F(TestTestRunner, StartFeatureAndScenarioContext) - { - EXPECT_CALL(testExecutionMock, StartRunMock); - EXPECT_CALL(testExecutionMock, StartFeatureMock); - EXPECT_CALL(testExecutionMock, StartScenarioMock); - - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - tmp << "Feature: Test feature\n" - " Scenario: Test scenario\n"; - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - - runner.Run(features); - } - - TEST_F(TestTestRunner, DontRunEmptyFeature) - { - EXPECT_CALL(testExecutionMock, StartRunMock); - EXPECT_CALL(testExecutionMock, StartFeatureMock).Times(0); - - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - tmp << "Feature: Test feature\n"; - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - - runner.Run(features); - } - - TEST_F(TestTestRunner, StartFeatureContextForEveryFeature) - { - EXPECT_CALL(testExecutionMock, StartRunMock); - EXPECT_CALL(testExecutionMock, StartFeatureMock); - EXPECT_CALL(testExecutionMock, StartScenarioMock).Times(2); - - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - tmp << "Feature: Test feature\n" - " Scenario: Test scenario\n" - " Scenario: Test scenario\n"; - - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - - runner.Run(features); - } - - TEST_F(TestTestRunner, StartScenarioContextForEveryScenario) - { - EXPECT_CALL(testExecutionMock, StartRunMock); - EXPECT_CALL(testExecutionMock, StartFeatureMock).Times(2); - EXPECT_CALL(testExecutionMock, StartScenarioMock).Times(2); - - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - tmp << "Feature: Test feature\n" - " Scenario: Test scenario\n"; - - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - - runner.Run(features); - } - - TEST_F(TestTestRunner, StartRuleAndScenarioForEveryRuleAndScenario) - { - EXPECT_CALL(testExecutionMock, StartRunMock); - EXPECT_CALL(testExecutionMock, StartFeatureMock).Times(2); - EXPECT_CALL(testExecutionMock, StartRuleMock).Times(4); - EXPECT_CALL(testExecutionMock, StartScenarioMock).Times(4); - - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - tmp << "Feature: Test feature\n" - " Rule: Test rule\n" - " Scenario: Test scenario\n" - " Rule: Test rule\n" - " Scenario: Test scenario\n"; - - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - - runner.Run(features); - } - - TEST_F(TestTestRunner, RunEveryStep) - { - EXPECT_CALL(testExecutionMock, StartRunMock); - EXPECT_CALL(testExecutionMock, StartFeatureMock); - EXPECT_CALL(testExecutionMock, StartRuleMock).Times(1); - EXPECT_CALL(testExecutionMock, StartScenarioMock).Times(2); - EXPECT_CALL(testExecutionMock, RunStepMock).Times(4); - - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - tmp << "Feature: Test feature\n" - " Scenario: Test scenario\n" - " Given I have a step\n" - " Given I have a step\n" - " Rule: Test rule\n" - " Scenario: Test scenario\n" - " Given I have a step\n" - " Given I have a step\n"; - - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - runner.Run(features); - } - - TEST_F(TestTestRunner, RunNestedSteps) - { - EXPECT_CALL(testExecutionMock, StartRunMock); - EXPECT_CALL(testExecutionMock, StartFeatureMock); - EXPECT_CALL(testExecutionMock, StartScenarioMock).Times(1); - EXPECT_CALL(testExecutionMock, RunStepMock).Times(3); - - auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - tmp << "Feature: Test feature\n" - " Scenario: Test scenario\n" - " When I call a nested step\n" - " Then the nested step was called\n"; - - features.push_back(featureTreeFactory.Create(tmp.Path(), "")); - - ASSERT_THAT(contextManager.ProgramContext().ExecutionStatus(), testing::Eq(Result::passed)); - - runner.Run(features); - - ASSERT_THAT(contextManager.ProgramContext().ExecutionStatus(), testing::Eq(Result::passed)); - } -} diff --git a/cucumber_cpp/library/engine/test_helper/CMakeLists.txt b/cucumber_cpp/library/engine/test_helper/CMakeLists.txt index 03105f77..8888e8f4 100644 --- a/cucumber_cpp/library/engine/test_helper/CMakeLists.txt +++ b/cucumber_cpp/library/engine/test_helper/CMakeLists.txt @@ -23,5 +23,4 @@ target_sources(cucumber_cpp.library.engine.test_helper PRIVATE TemporaryFile.cpp TemporaryFile.hpp TestExecutionInstance.hpp - TestRunnerMock.hpp ) diff --git a/cucumber_cpp/library/engine/test_helper/TestRunnerMock.hpp b/cucumber_cpp/library/engine/test_helper/TestRunnerMock.hpp deleted file mode 100644 index 40c4b207..00000000 --- a/cucumber_cpp/library/engine/test_helper/TestRunnerMock.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TEST_HELPER_TESTRUNNERMOCK_HPP -#define TEST_HELPER_TESTRUNNERMOCK_HPP - -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/TestRunner.hpp" -#include "gmock/gmock.h" -#include -#include -#include - -namespace cucumber_cpp::library::engine::test_helper -{ - struct TestRunnerMock : TestRunner - { - virtual ~TestRunnerMock() = default; - - MOCK_METHOD(void, Run, (const std::vector>& feature), (override)); - MOCK_METHOD(void, NestedStep, (StepType type, std::string step), (override)); - }; -} - -#endif diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt new file mode 100644 index 00000000..a7b0cd86 --- /dev/null +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -0,0 +1,33 @@ +add_library(cucumber_cpp.library.formatter STATIC ${CCR_EXCLUDE_FROM_ALL}) + +target_sources(cucumber_cpp.library.formatter PRIVATE + Formatter.cpp + Formatter.hpp + GetColorFunctions.hpp + GetColorFunctions.cpp + PrettyPrinter.cpp + PrettyPrinter.hpp + SummaryFormatter.cpp + SummaryFormatter.hpp +) + +target_include_directories(cucumber_cpp.library.formatter PUBLIC + ../../.. +) + +target_link_libraries(cucumber_cpp.library.formatter PUBLIC + # cucumber_cpp.library + # cucumber_cpp.library.util + cucumber_cpp.library.cucumber_expression + cucumber_cpp.library.formatter.helper + rang + cpp-terminal +) + +add_subdirectory(helper) + + +if (CCR_BUILD_TESTS) + # add_subdirectory(test) + # add_subdirectory(test_helper) +endif() diff --git a/cucumber_cpp/library/formatter/Formatter.cpp b/cucumber_cpp/library/formatter/Formatter.cpp new file mode 100644 index 00000000..ab18941b --- /dev/null +++ b/cucumber_cpp/library/formatter/Formatter.cpp @@ -0,0 +1,22 @@ +#include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include + +namespace cucumber_cpp::library::formatter +{ + Formatter::Formatter(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream) + : util::Listener{ broadcaster, [this](const cucumber::messages::envelope& envelope) + { + OnEnvelope(envelope); + } } + , supportCodeLibrary{ supportCodeLibrary } + , broadcaster{ broadcaster } + , eventDataCollector{ eventDataCollector } + , outputStream{ outputStream } + { + } +} diff --git a/cucumber_cpp/library/formatter/Formatter.hpp b/cucumber_cpp/library/formatter/Formatter.hpp new file mode 100644 index 00000000..98b97051 --- /dev/null +++ b/cucumber_cpp/library/formatter/Formatter.hpp @@ -0,0 +1,30 @@ +#ifndef FORMATTER_FORMATTER_HPP +#define FORMATTER_FORMATTER_HPP + +#include "cucumber/messages/envelope.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::formatter +{ + struct Formatter + : util::Listener + { + Formatter(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream = std::cout); + virtual ~Formatter() = default; + + protected: + virtual void OnEnvelope(const cucumber::messages::envelope& envelope) = 0; + + support::SupportCodeLibrary supportCodeLibrary; + util::Broadcaster& broadcaster; + const helper::EventDataCollector& eventDataCollector; + std::ostream& outputStream; + }; +} + +#endif diff --git a/cucumber_cpp/library/formatter/GetColorFunctions.cpp b/cucumber_cpp/library/formatter/GetColorFunctions.cpp new file mode 100644 index 00000000..06f2d31e --- /dev/null +++ b/cucumber_cpp/library/formatter/GetColorFunctions.cpp @@ -0,0 +1,85 @@ + +#include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" +#include "cpp-terminal/color.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "rang.hpp" +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter +{ + namespace + { + template + std::string ColorString(std::string_view sv) + { + return std::format("{}{}{}", Term::color_fg(colour), sv, Term::color_fg(Term::Color::Name::Default)); + } + } + + std::function + ColorFunctions::ForStatus(cucumber::messages::test_step_result_status status) + { + using enum cucumber::messages::test_step_result_status; + + switch (status) + { + case PASSED: + return ColorString; + case SKIPPED: + return ColorString; + case UNKNOWN: + case PENDING: + case UNDEFINED: + return ColorString; + case AMBIGUOUS: + case FAILED: + default: + return ColorString; + } + } + + std::string ColorFunctions::Location(std::string_view sv) + { + return ColorString(sv); + } + + std::string ColorFunctions::Tag(std::string_view sv) + { + return ColorString(sv); + } + + std::string ColorFunctions::DiffAdded(std::string_view sv) + { + return ColorString(sv); + } + + std::string ColorFunctions::DiffRemoved(std::string_view sv) + { + return ColorString(sv); + } + + std::string ColorFunctions::ErrorMessage(std::string_view sv) + { + return ColorString(sv); + } + + std::string ColorFunctions::ErrorStack(std::string_view sv) + { + return ColorString(sv); + } + + ColorFunctions GetColorFunctions(bool useColors) + { + std::string str; + + str += Term::color_fg(Term::Color::Name::Red); + str += "test"; + + // str += rang::fg::red; + + return {}; + } +} diff --git a/cucumber_cpp/library/formatter/GetColorFunctions.hpp b/cucumber_cpp/library/formatter/GetColorFunctions.hpp new file mode 100644 index 00000000..6dac98fb --- /dev/null +++ b/cucumber_cpp/library/formatter/GetColorFunctions.hpp @@ -0,0 +1,25 @@ +#ifndef FORMATTER_GET_COLOR_FUNCTIONS_HPP +#define FORMATTER_GET_COLOR_FUNCTIONS_HPP + +#include "cucumber/messages/test_step_result_status.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::formatter +{ + struct ColorFunctions + { + std::function ForStatus(cucumber::messages::test_step_result_status status); + std::string Location(std::string_view); + std::string Tag(std::string_view); + std::string DiffAdded(std::string_view); + std::string DiffRemoved(std::string_view); + std::string ErrorMessage(std::string_view); + std::string ErrorStack(std::string_view); + }; + + ColorFunctions GetColorFunctions(bool useColors); +} + +#endif diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyPrinter.cpp new file mode 100644 index 00000000..dfcb8d2d --- /dev/null +++ b/cucumber_cpp/library/formatter/PrettyPrinter.cpp @@ -0,0 +1,43 @@ +#include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" +#include "cucumber_cpp/library/formatter/helper/PickleParser.hpp" +#include + +namespace cucumber_cpp::library::formatter +{ + void PrettyPrinter::OnEnvelope(const cucumber::messages::envelope& envelope) + { + if (envelope.test_case_started) + { + CalculateIndent(envelope.test_case_started.value()); + } + } + + void PrettyPrinter::CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted) + { + const auto& testCase = eventDataCollector.GetTestCase(testCaseStarted.test_case_id); + const auto& pickle = eventDataCollector.GetPickle(testCase.pickle_id); + const auto scenarioMap = helper::GetGherkinScenarioMap(eventDataCollector.GetGherkinDocument(pickle.uri)); + const auto pickleStepMap = helper::GetPickleStepMap(pickle); + const auto& scenario = scenarioMap.at(pickle.ast_node_ids[0]); + + const auto maxScenarioLength = scenario.name.length(); + auto maxStepLength = std::size_t{ 0 }; + for (const auto& testStep : testCase.test_steps) + { + if (testStep.pickle_step_id) + { + const auto& pickleStep = pickleStepMap.at(testStep.pickle_step_id.value()); + if (pickleStep.text.length() > maxStepLength) + { + maxStepLength = pickleStep.text.length(); + } + } + } + + scenarioIndent = std::max(maxScenarioLength, maxStepLength) + 2; + } +} diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.hpp b/cucumber_cpp/library/formatter/PrettyPrinter.hpp new file mode 100644 index 00000000..43175f85 --- /dev/null +++ b/cucumber_cpp/library/formatter/PrettyPrinter.hpp @@ -0,0 +1,25 @@ +#ifndef FORMATTER_PRETTY_PRINTER_HPP +#define FORMATTER_PRETTY_PRINTER_HPP + +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber_cpp/library/formatter/Formatter.hpp" +#include + +namespace cucumber_cpp::library::formatter +{ + struct PrettyPrinter + : Formatter + { + using Formatter::Formatter; + + private: + void OnEnvelope(const cucumber::messages::envelope& envelope) override; + + void CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted); + + std::size_t scenarioIndent{ 0 }; + }; +} + +#endif diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp new file mode 100644 index 00000000..692915b8 --- /dev/null +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -0,0 +1,84 @@ +#include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/formatter/helper/IssueHelpers.hpp" +#include "cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp" +#include "cucumber_cpp/library/support/Polyfill.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter +{ + namespace + { + bool IsFailure(cucumber::messages::test_step_result_status status, bool willBeRetries) + { + return status == cucumber::messages::test_step_result_status::AMBIGUOUS || + status == cucumber::messages::test_step_result_status::UNDEFINED || + (status == cucumber::messages::test_step_result_status::FAILED && !willBeRetries); + } + + bool IsWarning(cucumber::messages::test_step_result_status status, bool willBeRetries) + { + return status == cucumber::messages::test_step_result_status::PENDING || + (status == cucumber::messages::test_step_result_status::FAILED && willBeRetries); + } + } + + void SummaryFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) + { + if (envelope.test_run_started) + { + testRunStartedAt = envelope.test_run_started->timestamp; + } + + if (envelope.test_run_finished) + { + const auto testRunFinishedAt = envelope.test_run_finished->timestamp; + const auto duration = testRunFinishedAt - testRunStartedAt; + + LogSummary(duration); + } + } + + void SummaryFormatter::LogSummary(const cucumber::messages::duration& testRunDuration) + { + std::vector failures{}; + std::vector warnings{}; + + const auto attempts = eventDataCollector.GetTestCaseAttempts(); + for (const auto& attempt : attempts) + { + if (IsFailure(attempt.worstTestStepResult.status, attempt.willBeRetried)) + failures.emplace_back(attempt); + + if (IsWarning(attempt.worstTestStepResult.status, attempt.willBeRetried)) + warnings.emplace_back(attempt); + } + + LogIssues(failures, "Failures"); + LogIssues(warnings, "Warnings"); + + outputStream << helper::FormatSummary(attempts, testRunDuration); + } + + void SummaryFormatter::LogIssues(std::span attempts, std::string_view title) + { + if (!attempts.empty()) + { + support::print(outputStream, "{}:\n\n", title); + + auto nr = 1; + for (const auto& issue : attempts) + helper::FormatIssue(outputStream, nr++, issue, supportCodeLibrary) << "\n"; + } + } +} diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.hpp b/cucumber_cpp/library/formatter/SummaryFormatter.hpp new file mode 100644 index 00000000..fb85e878 --- /dev/null +++ b/cucumber_cpp/library/formatter/SummaryFormatter.hpp @@ -0,0 +1,28 @@ +#ifndef FORMATTER_SUMMARY_FORMATTER_HPP +#define FORMATTER_SUMMARY_FORMATTER_HPP + +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/timestamp.hpp" +#include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include +#include + +namespace cucumber_cpp::library::formatter +{ + struct SummaryFormatter + : Formatter + { + using Formatter::Formatter; + + private: + void OnEnvelope(const cucumber::messages::envelope& envelope) override; + void LogSummary(const cucumber::messages::duration& testRunDuration); + void LogIssues(std::span attempts, std::string_view title); + + cucumber::messages::timestamp testRunStartedAt{}; + }; +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt new file mode 100644 index 00000000..d44d0a35 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -0,0 +1,37 @@ +add_library(cucumber_cpp.library.formatter.helper STATIC ${CCR_EXCLUDE_FROM_ALL}) + +target_sources(cucumber_cpp.library.formatter.helper PRIVATE + EventDataCollector.cpp + EventDataCollector.hpp + GherkinDocumentParser.cpp + GherkinDocumentParser.hpp + IndentString.cpp + IndentString.hpp + IssueHelpers.cpp + IssueHelpers.hpp + KeywordType.cpp + KeywordType.hpp + PickleParser.cpp + PickleParser.hpp + SummaryHelpers.hpp + SummaryHelpers.cpp + TestCaseAttemptFormatter.cpp + TestCaseAttemptFormatter.hpp + TestCaseAttemptParser.cpp + TestCaseAttemptParser.hpp +) + +target_include_directories(cucumber_cpp.library.formatter.helper PUBLIC + ../../../.. +) + +target_link_libraries(cucumber_cpp.library.formatter.helper PUBLIC + # cucumber_cpp.library + # cucumber_cpp.library.util + cucumber_cpp.library.cucumber_expression +) + +if (CCR_BUILD_TESTS) + # add_subdirectory(test) + # add_subdirectory(test_helper) +endif() diff --git a/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp b/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp new file mode 100644 index 00000000..a0455340 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp @@ -0,0 +1,138 @@ +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + EventDataCollector::EventDataCollector(util::Broadcaster& broadcaster) + : util::Listener{ broadcaster, [this](const cucumber::messages::envelope& envelope) + { + OnEnvelope(envelope); + } } + {} + + const cucumber::messages::gherkin_document& EventDataCollector::GetGherkinDocument(std::string uri) const + { + return gherkinDocumentMap.at(uri); + } + + const cucumber::messages::pickle& EventDataCollector::GetPickle(std::string id) const + { + return pickleMap.at(id); + } + + const cucumber::messages::test_case& EventDataCollector::GetTestCase(std::string id) const + { + return testCaseMap.at(id); + } + + std::vector EventDataCollector::GetTestCaseAttempts() const + { + std::vector attempts{}; + for (const auto& key : testCaseAttemptDataMap | std::views::keys) + attempts.emplace_back(GetTestCaseAttempt(key)); + return attempts; + } + + void EventDataCollector::OnEnvelope(const cucumber::messages::envelope& envelope) + { + if (envelope.gherkin_document) + { + gherkinDocumentMap.emplace(envelope.gherkin_document->uri.value(), *envelope.gherkin_document); + } + else if (envelope.pickle) + { + pickleMap.emplace(envelope.pickle->id, *envelope.pickle); + } + else if (envelope.undefined_parameter_type) + { + undefinedParameterTypes.emplace_back(*envelope.undefined_parameter_type); + } + else if (envelope.test_case) + { + testCaseMap.emplace(envelope.test_case->id, *envelope.test_case); + } + else if (envelope.test_case_started) + { + InitTestCaseAttempt(*envelope.test_case_started); + } + else if (envelope.attachment) + { + StoreAttachment(*envelope.attachment); + } + else if (envelope.test_step_finished) + { + StoreTestStepResult(*envelope.test_step_finished); + } + else if (envelope.test_case_finished) + { + StoreTestCaseResult(*envelope.test_case_finished); + } + } + + void EventDataCollector::InitTestCaseAttempt(const cucumber::messages::test_case_started& testCaseStarted) + { + testCaseAttemptDataMap.emplace(testCaseStarted.id, + TestCaseAttemptData{ + .attempt = testCaseStarted.attempt, + .willBeRetried = false, + .testCaseId = testCaseStarted.test_case_id, + .worstTestStepResult = { + .status = cucumber::messages::test_step_result_status::UNKNOWN, + }, + }); + } + + void EventDataCollector::StoreAttachment(const cucumber::messages::attachment& attachment) + { + auto& testCaseAttemptData = testCaseAttemptDataMap.at(*attachment.test_case_started_id); + testCaseAttemptData.stepAttachments[*attachment.test_step_id].emplace_back(attachment); + } + + void EventDataCollector::StoreTestStepResult(const cucumber::messages::test_step_finished& testStepFinished) + { + auto& testCaseAttemptData = testCaseAttemptDataMap.at(testStepFinished.test_case_started_id); + testCaseAttemptData.stepResults[testStepFinished.test_step_id] = testStepFinished.test_step_result; + } + + void EventDataCollector::StoreTestCaseResult(const cucumber::messages::test_case_finished& testCaseFinished) + { + auto& testCaseAttemptData = testCaseAttemptDataMap.at(testCaseFinished.test_case_started_id); + + const auto allStepResults = testCaseAttemptData.stepResults | std::views::values; + std::vector stepResults{ allStepResults.begin(), allStepResults.end() }; + + testCaseAttemptData.worstTestStepResult = util::GetWorstTestStepResult(stepResults); + testCaseAttemptData.willBeRetried = testCaseFinished.will_be_retried; + } + + TestCaseAttempt EventDataCollector::GetTestCaseAttempt(const std::string& testCaseStartedId) const + { + const auto& testCaseAttemptData = testCaseAttemptDataMap.at(testCaseStartedId); + const auto& testCase = testCaseMap.at(testCaseAttemptData.testCaseId); + const auto& pickle = pickleMap.at(testCase.pickle_id); + return { + .attempt = testCaseAttemptData.attempt, + .willBeRetried = testCaseAttemptData.willBeRetried, + .gherkinDocument = gherkinDocumentMap.at(pickle.uri), + .pickle = pickle, + .stepAttachments = testCaseAttemptData.stepAttachments, + .stepResults = testCaseAttemptData.stepResults, + .testCase = testCase, + .worstTestStepResult = testCaseAttemptData.worstTestStepResult, + }; + } +} diff --git a/cucumber_cpp/library/formatter/helper/EventDataCollector.hpp b/cucumber_cpp/library/formatter/helper/EventDataCollector.hpp new file mode 100644 index 00000000..a25ce4de --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/EventDataCollector.hpp @@ -0,0 +1,72 @@ +#ifndef HELPER_EVENT_DATA_COLLECTOR_HPP +#define HELPER_EVENT_DATA_COLLECTOR_HPP + +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/undefined_parameter_type.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + struct TestCaseAttemptData + { + std::size_t attempt; + bool willBeRetried; + std::string testCaseId; + std::map> stepAttachments; + std::map stepResults; + cucumber::messages::test_step_result worstTestStepResult; + }; + + struct TestCaseAttempt + { + std::size_t attempt; + bool willBeRetried; + const cucumber::messages::gherkin_document& gherkinDocument; + const cucumber::messages::pickle& pickle; + const std::map>& stepAttachments; + const std::map& stepResults; + const cucumber::messages::test_case& testCase; + cucumber::messages::test_step_result worstTestStepResult; + }; + + struct EventDataCollector : util::Listener + { + explicit EventDataCollector(util::Broadcaster& broadcaster); + + const cucumber::messages::gherkin_document& GetGherkinDocument(std::string uri) const; + const cucumber::messages::pickle& GetPickle(std::string id) const; + const cucumber::messages::test_case& GetTestCase(std::string id) const; + + std::vector GetTestCaseAttempts() const; + + private: + void OnEnvelope(const cucumber::messages::envelope& envelope); + + void InitTestCaseAttempt(const cucumber::messages::test_case_started& testCaseStarted); + void StoreAttachment(const cucumber::messages::attachment& attachment); + void StoreTestStepResult(const cucumber::messages::test_step_finished& testStepFinished); + void StoreTestCaseResult(const cucumber::messages::test_case_finished& testCaseFinished); + + TestCaseAttempt GetTestCaseAttempt(const std::string& testCaseStartedId) const; + + std::map gherkinDocumentMap; + std::map pickleMap; + std::map testCaseMap; + std::map testCaseAttemptDataMap; + std::vector undefinedParameterTypes; + }; +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp new file mode 100644 index 00000000..0c280f50 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp @@ -0,0 +1,112 @@ +#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" +#include "cucumber/messages/background.hpp" +#include "cucumber/messages/feature_child.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/rule_child.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + std::vector ExtractStep(const std::variant tests) + { + return std::visit([](const auto& item) + { + return item.steps; + }, + tests); + } + + std::vector ExtractScenarioFromRuleChild(const cucumber::messages::rule_child& child) + { + if (child.scenario) + return { *child.scenario }; + return {}; + } + + std::vector ExtractScenarioContainers(const cucumber::messages::feature_child& child) + { + if (child.rule) + { + std::vector result; + for (const auto& scenario : child.rule->children | std::views::transform(ExtractScenarioFromRuleChild) | std::views::join) + result.push_back(scenario); + return result; + } + + if (child.scenario) + return { *child.scenario }; + + return {}; + } + + std::variant ExtractRuleContainers(const cucumber::messages::rule_child& child) + { + if (child.background) + return *child.background; + else + return *child.scenario; + } + + std::vector> ExtractStepContainers(const cucumber::messages::feature_child& child) + { + if (child.background) + return { *child.background }; + if (child.rule) + { + auto iter = child.rule->children | std::views::transform(ExtractRuleContainers); + return { iter.begin(), iter.end() }; + } + + return { *child.scenario }; + } + } + + GherkinStepMap GetGherkinStepMap(const cucumber::messages::gherkin_document& gherkinDocument) + { + auto steps = gherkinDocument.feature->children | + std::views::transform(ExtractStepContainers) | std::views::join | + std::views::transform(ExtractStep) | std::views::join; + + GherkinStepMap map; + + for (const auto& step : steps) + map.emplace(step.id, step); + + return map; + } + + GherkinScenarioMap GetGherkinScenarioMap(const cucumber::messages::gherkin_document& gherkinDocument) + { + GherkinScenarioMap map; + + for (const auto& scenario : gherkinDocument.feature->children | std::views::transform(ExtractScenarioContainers) | std::views::join) + map.emplace(scenario.id, scenario); + + return map; + } + + GherkinScenarioLocationMap GetGherkinScenarioLocationMap(const cucumber::messages::gherkin_document& gherkinDocument) + { + GherkinScenarioLocationMap locationMap; + GherkinScenarioMap scenarioMap = GetGherkinScenarioMap(gherkinDocument); + + for (const auto& [id, scenario] : scenarioMap) + { + locationMap.emplace(id, scenario.location); + for (const auto& example : scenario.examples) + for (const auto& tableRow : example.table_body) + locationMap.emplace(tableRow.id, tableRow.location); + } + + return locationMap; + } +} diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp new file mode 100644 index 00000000..49106a7f --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp @@ -0,0 +1,22 @@ +#ifndef HELPER_GHERKIN_DOCUMENT_PARSER_HPP +#define HELPER_GHERKIN_DOCUMENT_PARSER_HPP + +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + using GherkinStepMap = std::map; + using GherkinScenarioMap = std::map; + using GherkinScenarioLocationMap = std::map; + + GherkinStepMap GetGherkinStepMap(const cucumber::messages::gherkin_document& gherkinDocument); + GherkinScenarioMap GetGherkinScenarioMap(const cucumber::messages::gherkin_document& gherkinDocument); + GherkinScenarioLocationMap GetGherkinScenarioLocationMap(const cucumber::messages::gherkin_document& gherkinDocument); +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/IndentString.cpp b/cucumber_cpp/library/formatter/helper/IndentString.cpp new file mode 100644 index 00000000..5ede2363 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/IndentString.cpp @@ -0,0 +1,30 @@ +#include "cucumber_cpp/library/formatter/helper/IndentString.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::string IndentString(const std::string& str, std::size_t indentSize) + { + using std::operator""sv; + + auto lines = str | std::views::split("\n"sv); + auto lineCount = std::distance(lines.begin(), lines.end()); + + if (lineCount == 0) + return ""; + + const auto indent = std::string(indentSize, ' '); + std::string indented = std::format("{}{}", indent, std::string_view{ lines.front().begin(), lines.front().end() }); + + for (const auto line : lines | std::views::drop(1)) + indented += std::format("\n{}{}", indent, std::string_view{ line.begin(), line.end() }); + + return indented; + } +} diff --git a/cucumber_cpp/library/formatter/helper/IndentString.hpp b/cucumber_cpp/library/formatter/helper/IndentString.hpp new file mode 100644 index 00000000..3ae582c9 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/IndentString.hpp @@ -0,0 +1,12 @@ +#ifndef HELPER_INDENT_STRING_HPP +#define HELPER_INDENT_STRING_HPP + +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::string IndentString(const std::string& str, std::size_t indentSize); +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp b/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp new file mode 100644 index 00000000..3cc064ea --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp @@ -0,0 +1,38 @@ + +#include "cucumber_cpp/library/formatter/helper/IssueHelpers.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp" +#include "cucumber_cpp/library/support/Polyfill.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::ostream& FormatIssue(std::ostream& outputStream, std::size_t number, const TestCaseAttempt& testCaseAttempt, support::SupportCodeLibrary supportCodeLibrary, bool printAttachments) + { + using std::operator""sv; + + const auto prefix = std::format("{}) ", number); + const auto formattedTestCaseAttempt = FormatTestCaseAttempt(supportCodeLibrary, testCaseAttempt, printAttachments); + + auto lines = formattedTestCaseAttempt | std::views::split("\n"sv); + + if (std::ranges::distance(lines) == 0) + return outputStream; + + support::print(outputStream, "{}{}\n", prefix, std::string_view{ lines.front().begin(), lines.front().end() }); + + for (const auto line : lines | std::views::drop(1)) + support::print(outputStream, "{:{}}{}\n", "", prefix.size(), std::string_view{ line.begin(), line.end() }); + + return outputStream; + } +} diff --git a/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp b/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp new file mode 100644 index 00000000..91584208 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp @@ -0,0 +1,15 @@ +#ifndef HELPER_ISSUE_HELPERS_HPP +#define HELPER_ISSUE_HELPERS_HPP + +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::ostream& FormatIssue(std::ostream& outputStream, std::size_t number, const TestCaseAttempt& testCaseAttempt, support::SupportCodeLibrary supportCodeLibrary, bool printAttachments = true); +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/KeywordType.cpp b/cucumber_cpp/library/formatter/helper/KeywordType.cpp new file mode 100644 index 00000000..44ba340d --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/KeywordType.cpp @@ -0,0 +1,47 @@ +#include "cucumber_cpp/library/formatter/helper/KeywordType.hpp" +#include "cucumber/gherkin/dialect.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + constexpr std::array stepKeywords{ + "given", + "when", + "then", + "and", + "but", + }; + } + + KeywordType GetStepKeywordType(std::string_view keyword, std::string_view language, std::optional previousKeywordType) + { + const auto& dialect = cucumber::gherkin::keywords(language); + const auto typeIter = std::ranges::find_if(stepKeywords, [&dialect, keyword](std::string_view stepKeyword) + { + return std::ranges::find(dialect.at(stepKeyword), keyword) != dialect.at(stepKeyword).end(); + }); + + switch (std::distance(stepKeywords.begin(), typeIter)) + { + case 0: // given + return KeywordType::precondition; + case 1: // when + return KeywordType::event; + case 2: // then + return KeywordType::outcome; + case 3: // and + case 4: // but + if (previousKeywordType.has_value()) + return *previousKeywordType; + [[fallthrough]]; + default: + return KeywordType::precondition; + } + } +} diff --git a/cucumber_cpp/library/formatter/helper/KeywordType.hpp b/cucumber_cpp/library/formatter/helper/KeywordType.hpp new file mode 100644 index 00000000..5fa9328f --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/KeywordType.hpp @@ -0,0 +1,19 @@ +#ifndef HELPER_KEYWORD_TYPE_HPP +#define HELPER_KEYWORD_TYPE_HPP + +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + enum class KeywordType + { + precondition, + event, + outcome + }; + + KeywordType GetStepKeywordType(std::string_view keyword, std::string_view language, std::optional previousKeywordType); +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/PickleParser.cpp b/cucumber_cpp/library/formatter/helper/PickleParser.cpp new file mode 100644 index 00000000..c81093f3 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/PickleParser.cpp @@ -0,0 +1,37 @@ +#include "cucumber_cpp/library/formatter/helper/PickleParser.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + constexpr auto toPair = [](const cucumber::messages::pickle_step& step) -> std::pair + { + return { step.id, step }; + }; + } + + std::map GetPickleStepMap(const cucumber::messages::pickle& pickle) + { + auto range = pickle.steps | std::views::transform(toPair); + return { range.begin(), range.end() }; + } + + std::string_view GetStepKeyword(const cucumber::messages::pickle_step& pickleStep, const GherkinStepMap& gherkinStepMap) + { + auto first = std::ranges::find_if(pickleStep.ast_node_ids, [&gherkinStepMap](const std::string& id) + { + return gherkinStepMap.contains(id); + }); + return gherkinStepMap.at(*first).keyword; + } +} diff --git a/cucumber_cpp/library/formatter/helper/PickleParser.hpp b/cucumber_cpp/library/formatter/helper/PickleParser.hpp new file mode 100644 index 00000000..f7cdab76 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/PickleParser.hpp @@ -0,0 +1,18 @@ +#ifndef HELPER_PICKLE_PARSER_HPP +#define HELPER_PICKLE_PARSER_HPP + +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::map GetPickleStepMap(const cucumber::messages::pickle& pickle); + + std::string_view GetStepKeyword(const cucumber::messages::pickle_step& pickleStep, const GherkinStepMap& gherkinStepMap); +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp new file mode 100644 index 00000000..c64de752 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp @@ -0,0 +1,90 @@ +#include "cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/support/Duration.hpp" +#include "cucumber_cpp/library/support/Join.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + std::string GetCountSummary(std::span results, std::string_view type) + { + ColorFunctions colorFunctions{}; + std::map counts; + + for (const auto& result : results) + ++counts[result.status]; + + auto values = counts | std::views::values; + const auto total = std::accumulate(values.begin(), values.end(), 0u); + + std::string text = std::format("{} {}{}", total, type, (total == 1 ? "" : "s")); + + if (total > 0) + { + std::vector details; + for (const auto& [status, count] : counts) + { + auto statusStr = std::string{ cucumber::messages::to_string(status) }; + std::transform(statusStr.begin(), statusStr.end(), statusStr.begin(), [](unsigned char c) + { + return std::tolower(c); + }); + details.emplace_back(colorFunctions.ForStatus(status)(std::format("{} {}", count, statusStr))); + } + + text += " " + support::Join(details, ", "); + } + + return text; + } + + std::string GetDurationSummary(const cucumber::messages::duration& duration) + { + const auto total = support::DurationToMilliseconds(duration); + return std::format("{:%Mm %S}s", total); + } + } + + std::string FormatSummary(std::span testCaseAttempts, cucumber::messages::duration testRunDuration) + { + std::vector testCaseResults; + std::vector testStepResults; + cucumber::messages::duration totalStepDuration{}; + + for (const auto& testCaseAttempt : testCaseAttempts) + { + for (const auto& [_, stepResult] : testCaseAttempt.stepResults) + totalStepDuration += stepResult.duration; + + if (!testCaseAttempt.willBeRetried) + { + testCaseResults.emplace_back(testCaseAttempt.worstTestStepResult); + for (const auto& testStep : testCaseAttempt.testCase.test_steps) + if (testStep.pickle_step_id) + testStepResults.emplace_back(testCaseAttempt.stepResults.at(testStep.id)); + } + } + + const auto scenarioSummary = GetCountSummary(testCaseResults, "scenario"); + const auto stepSummary = GetCountSummary(testStepResults, "step"); + const auto durationSummary = std::format("{} (executing steps: {})\n", GetDurationSummary(testRunDuration), GetDurationSummary(totalStepDuration)); + + return support::Join({ scenarioSummary, stepSummary, durationSummary }, "\n"); + } +} diff --git a/cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp b/cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp new file mode 100644 index 00000000..fea98149 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp @@ -0,0 +1,14 @@ +#ifndef HELPER_SUMMARY_HELPERS_HPP +#define HELPER_SUMMARY_HELPERS_HPP + +#include "cucumber/messages/duration.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::string FormatSummary(std::span testCaseAttempts, cucumber::messages::duration testRunDuration); +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp new file mode 100644 index 00000000..39d6cef2 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp @@ -0,0 +1,94 @@ +#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/formatter/helper/IndentString.hpp" +#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + // to be moved tyo LocationHelpers.hpp + std::string FormatLocation(LineAndUri obj, std::optional cwd = std::nullopt) + { + std::string uri = obj.uri; + if (cwd) + uri = std::filesystem::relative(obj.uri, *cwd).string(); + return std::format("{}:{}", uri, obj.line); + } + + std::string GetAttemptText(std::size_t attempt, bool willBeRetried) + { + if (attempt > 0 || willBeRetried) + return std::format(" (attempt {}{})", attempt + 1, willBeRetried ? ", retried" : ""); + return ""; + } + + std::string GetStepMessage(const ParsedTestStep& testStep) + { + using enum cucumber::messages::test_step_result_status; + + switch (testStep.result.status) + { + case AMBIGUOUS: + case FAILED: + return testStep.result.message.value_or(""); + + case UNDEFINED: + return "Undefined. Implement with the following snippet: ''"; + + case PENDING: + return "Pending"; + + default: + return {}; + } + } + + std::string FormatStep(const ParsedTestStep& testStep, bool printAttachments) + { + ColorFunctions colorFunctions; + const auto colorStatus = colorFunctions.ForStatus(testStep.result.status); + + auto identifier = std::format("{}{}", testStep.keyword, testStep.text.value_or("")); + auto text = colorStatus(std::format("{} {}", cucumber::messages::to_string(testStep.result.status), identifier)); + if (testStep.name) + text += colorStatus(*testStep.name); + + if (testStep.actionLocation) + text += std::format(" # {}", colorFunctions.Location(FormatLocation(*testStep.actionLocation, std::filesystem::current_path()))); + + text += '\n'; + + if (testStep.argument) + ; // indent 4 FormatStepArgument append \n + + // indent 4 print attachments append \n + + auto message = GetStepMessage(testStep); + if (!message.empty()) + text += IndentString(colorStatus(message), 4) + "\n"; + + return text; + } + } + + std::string FormatTestCaseAttempt(support::SupportCodeLibrary supportCodeLibrary, const TestCaseAttempt& testCaseAttempt, bool printAttachments) + { + const auto parsed = ParseTestCaseAttempt(supportCodeLibrary, testCaseAttempt); + + auto text = std::format("Scenario: {}", parsed.parsedTestCase.name); + text += GetAttemptText(parsed.parsedTestCase.attempt, testCaseAttempt.willBeRetried); + text += std::format(" {}\n", FormatLocation(parsed.parsedTestCase.sourceLocation.value())); + for (auto const& testStep : parsed.parsedTestSteps) + text += FormatStep(testStep, printAttachments); + return text + '\n'; + } +} diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp new file mode 100644 index 00000000..559a59b3 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp @@ -0,0 +1,13 @@ +#ifndef FORMATTER_TEST_CASE_ATTEMPT_FORMATTER_HPP +#define FORMATTER_TEST_CASE_ATTEMPT_FORMATTER_HPP + +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::string FormatTestCaseAttempt(support::SupportCodeLibrary supportCodeLibrary, const TestCaseAttempt& testCaseAttempt, bool printAttachments); +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp new file mode 100644 index 00000000..9c2ad2d7 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp @@ -0,0 +1,140 @@ +#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/pickle_step_argument.hpp" +#include "cucumber/messages/step_keyword_type.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" +#include "cucumber_cpp/library/formatter/helper/KeywordType.hpp" +#include "cucumber_cpp/library/formatter/helper/PickleParser.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + ParsedTestStep ParseStep(bool isBeforeHook, + const GherkinStepMap& gherkinStepMap, + std::string_view keyword, + KeywordType keywordType, + const cucumber::messages::pickle_step& pickleStep, + std::filesystem::path pickleUri, + support::SupportCodeLibrary supportCode, + const cucumber::messages::test_step& testStep, + const cucumber::messages::test_step_result& testStepResult, + std::span attachments) + { + using std::operator""sv; + ParsedTestStep parsedTestStep{ + .attachments = attachments, + .keyword = std::string{ testStep.pickle_step_id ? keyword : (isBeforeHook ? "Before" : "After") }, + .result = testStepResult, + }; + + if (testStep.hook_id) + { + const auto& definition = supportCode.hookRegistry.GetDefinitionById(testStep.hook_id.value()); + parsedTestStep.actionLocation = { + .uri = definition.hook.source_reference.uri.value(), + .line = definition.hook.source_reference.location->line, + }; + parsedTestStep.name = std::format(" [Hook]"); + } + + if (testStep.step_definition_ids && testStep.step_definition_ids->size() == 1) + { + const auto& definition = supportCode.stepRegistry.GetDefinitionById(testStep.step_definition_ids->front()); + parsedTestStep.actionLocation = { + .uri = definition.uri, + .line = definition.line, + }; + } + + if (testStep.pickle_step_id) + { + parsedTestStep.location = { + .uri = pickleUri, + .line = gherkinStepMap.at(pickleStep.ast_node_ids.front()).location.line + }; + parsedTestStep.text = pickleStep.text; + if (pickleStep.argument) + parsedTestStep.argument = *pickleStep.argument; + } + + // if (testStepResult.status == cucumber::messages::test_step_result_status::UNDEFINED) + // parsedTestStep.snippet = "not supporting snippet generation yet"; + + return parsedTestStep; + } + } + + ParsedTestCaseAttempt ParseTestCaseAttempt(support::SupportCodeLibrary supportCodeLibrary, const TestCaseAttempt& testCaseAttempt) + { + const auto& testCase = testCaseAttempt.testCase; + const auto& pickle = testCaseAttempt.pickle; + const auto& gherkinDocument = testCaseAttempt.gherkinDocument; + + const auto gherkinStepMap = GetGherkinStepMap(gherkinDocument); + const auto gherkinScenarioLocationMap = GetGherkinScenarioLocationMap(gherkinDocument); + const auto pickleStepMap = GetPickleStepMap(pickle); + const auto relativePickleUri = pickle.uri; + + const ParsedTestCase parsedTestCase{ + .attempt = testCaseAttempt.attempt, + .name = pickle.name, + .sourceLocation = LineAndUri{ + .uri = relativePickleUri, + .line = gherkinScenarioLocationMap.at(pickle.ast_node_ids[0]).line, + }, + .worstStepStepResult = testCaseAttempt.worstTestStepResult + }; + + std::vector parsedTestSteps{}; + parsedTestSteps.reserve(testCase.test_steps.size()); + + bool isBeforeHook = true; + std::optional previousKeyWordType = KeywordType::precondition; + + for (const auto& testStep : testCase.test_steps) + { + const auto& testStepResult = testCaseAttempt.stepResults.at(testStep.id); + isBeforeHook = isBeforeHook && testStep.hook_id.has_value(); + + std::string_view keyword; + KeywordType keywordType; + const cucumber::messages::pickle_step* pickleStep{ nullptr }; + if (testStep.pickle_step_id) + { + pickleStep = &pickleStepMap.at(*testStep.pickle_step_id); + keyword = GetStepKeyword(*pickleStep, gherkinStepMap); + keywordType = GetStepKeywordType(keyword, gherkinDocument.feature->language, previousKeyWordType); + } + + parsedTestSteps.emplace_back(ParseStep(isBeforeHook, + gherkinStepMap, + keyword, + keywordType, + *pickleStep, + relativePickleUri, + supportCodeLibrary, + testStep, + testStepResult, + testCaseAttempt.stepAttachments.contains(testStep.id) ? testCaseAttempt.stepAttachments.at(testStep.id) : std::span{})); + previousKeyWordType = keywordType; + } + + return { + .parsedTestCase = parsedTestCase, + .parsedTestSteps = parsedTestSteps, + }; + } +} diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp new file mode 100644 index 00000000..0360f1db --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp @@ -0,0 +1,53 @@ +#ifndef FORMATTER_TEST_CASE_ATTEMPT_PARSER_HPP +#define FORMATTER_TEST_CASE_ATTEMPT_PARSER_HPP + +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/pickle_step_argument.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + struct LineAndUri + { + std::string uri; + std::size_t line; + }; + + struct ParsedTestCase + { + std::size_t attempt; + std::string name; + std::optional sourceLocation; + cucumber::messages::test_step_result worstStepStepResult; + }; + + struct ParsedTestStep + { + std::optional actionLocation; + std::optional argument; + std::span attachments; + std::string keyword; + std::optional name; + const cucumber::messages::test_step_result& result; + std::optional location; + std::optional text; + }; + + struct ParsedTestCaseAttempt + { + ParsedTestCase parsedTestCase; + std::vector parsedTestSteps; + }; + + ParsedTestCaseAttempt ParseTestCaseAttempt(support::SupportCodeLibrary supportCodeLibrary, const TestCaseAttempt& testCaseAttempt); +} + +#endif diff --git a/cucumber_cpp/library/report/StdOutReport.cpp b/cucumber_cpp/library/report/StdOutReport.cpp index f17c17d5..48523207 100644 --- a/cucumber_cpp/library/report/StdOutReport.cpp +++ b/cucumber_cpp/library/report/StdOutReport.cpp @@ -184,7 +184,6 @@ namespace cucumber_cpp::library::report void StdOutReport::ScenarioEnd(engine::Result result, const engine::ScenarioInfo& scenarioInfo, TraceTime::Duration duration) { - using enum engine::Result; std::cout << "\n" diff --git a/cucumber_cpp/library/runtime/CMakeLists.txt b/cucumber_cpp/library/runtime/CMakeLists.txt new file mode 100644 index 00000000..05272f1d --- /dev/null +++ b/cucumber_cpp/library/runtime/CMakeLists.txt @@ -0,0 +1,29 @@ +add_library(cucumber_cpp.library.runtime STATIC ${CCR_EXCLUDE_FROM_ALL}) + +target_sources(cucumber_cpp.library.runtime PRIVATE + Coordinator.cpp + Coordinator.hpp + MakeRuntime.cpp + MakeRuntime.hpp + SerialRuntimeAdapter.cpp + SerialRuntimeAdapter.hpp + TestCaseRunner.cpp + TestCaseRunner.hpp + Worker.cpp + Worker.hpp +) + +target_include_directories(cucumber_cpp.library.runtime PUBLIC + ../../.. +) + +target_link_libraries(cucumber_cpp.library.runtime PUBLIC + # cucumber_cpp.library + # cucumber_cpp.library.util + cucumber_cpp.library.cucumber_expression +) + +if (CCR_BUILD_TESTS) + # add_subdirectory(test) + # add_subdirectory(test_helper) +endif() diff --git a/cucumber_cpp/library/runtime/Coordinator.cpp b/cucumber_cpp/library/runtime/Coordinator.cpp new file mode 100644 index 00000000..2b4ad0ad --- /dev/null +++ b/cucumber_cpp/library/runtime/Coordinator.cpp @@ -0,0 +1,101 @@ +#include "cucumber_cpp/library/runtime/Coordinator.hpp" +#include "cucumber/gherkin/app.hpp" +#include "cucumber/gherkin/exceptions.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/gherkin/pickle_compiler.hpp" +#include "cucumber/gherkin/utils.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/parse_error.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/source.hpp" +#include "cucumber/messages/source_reference.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern.hpp" +#include "cucumber/messages/step_match_argument.hpp" +#include "cucumber/messages/suggestion.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_run_hook_finished.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_run_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber/messages/timestamp.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + Coordinator::Coordinator(std::string testRunStartedId, + util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + std::span sourcedPickles, + std::unique_ptr&& runtimeAdapter, + support::SupportCodeLibrary supportCodeLibrary) + : testRunStartedId{ testRunStartedId } + , broadcaster{ broadcaster } + , idGenerator{ idGenerator } + , sourcedPickles{ sourcedPickles } + , runtimeAdapter{ std::move(runtimeAdapter) } + , supportCodeLibrary{ supportCodeLibrary } + {} + + bool Coordinator::Run() + { + broadcaster.BroadcastEvent({ .test_run_started = cucumber::messages::test_run_started{ + .timestamp = support::TimestampNow(), + .id = std::string{ testRunStartedId }, + } }); + + const auto assembledTestCases = assemble::AssembleTestSuites(supportCodeLibrary, testRunStartedId, broadcaster, sourcedPickles, idGenerator); + const auto success = runtimeAdapter->Run(assembledTestCases); + + broadcaster.BroadcastEvent({ .test_run_finished = cucumber::messages::test_run_finished{ + .success = success, + .timestamp = support::TimestampNow(), + .test_run_started_id = std::string{ testRunStartedId }, + } }); + + return success; + } +} diff --git a/cucumber_cpp/library/runtime/Coordinator.hpp b/cucumber_cpp/library/runtime/Coordinator.hpp new file mode 100644 index 00000000..01aa23d1 --- /dev/null +++ b/cucumber_cpp/library/runtime/Coordinator.hpp @@ -0,0 +1,93 @@ +#ifndef RUNTIME_COORDINATOR_HPP +#define RUNTIME_COORDINATOR_HPP + +#include "cucumber/gherkin/app.hpp" +#include "cucumber/gherkin/exceptions.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/gherkin/pickle_compiler.hpp" +#include "cucumber/gherkin/utils.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/parse_error.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/source.hpp" +#include "cucumber/messages/source_reference.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern.hpp" +#include "cucumber/messages/step_match_argument.hpp" +#include "cucumber/messages/suggestion.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_run_hook_finished.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_run_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber/messages/timestamp.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + struct Coordinator : support::Runtime + { + Coordinator(std::string testRunStartedId, + util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + std::span sourcedPickles, + std::unique_ptr&& runtimeAdapter, + support::SupportCodeLibrary supportCodeLibrary); + + bool Run() override; + + private: + std::string testRunStartedId; + util::Broadcaster& broadcaster; + cucumber::gherkin::id_generator_ptr idGenerator; + + std::span sourcedPickles; + std::unique_ptr runtimeAdapter; + support::SupportCodeLibrary supportCodeLibrary; + }; +} + +#endif diff --git a/cucumber_cpp/library/runtime/MakeRuntime.cpp b/cucumber_cpp/library/runtime/MakeRuntime.cpp new file mode 100644 index 00000000..254be3fd --- /dev/null +++ b/cucumber_cpp/library/runtime/MakeRuntime.cpp @@ -0,0 +1,37 @@ +#include "cucumber_cpp/library/runtime/MakeRuntime.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/runtime/Coordinator.hpp" +#include "cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) + { + return std::make_unique( + testRunStartedId, + broadcaster, + idGenerator, + options, + supportCodeLibrary, + programContext); + } + + std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, std::span sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) + { + const auto testRunStartedId{ idGenerator->next_id() }; + return std::make_unique( + testRunStartedId, + broadcaster, + idGenerator, + sourcedPickles, + MakeAdapter(options, testRunStartedId, broadcaster, supportCodeLibrary, idGenerator, programContext), + supportCodeLibrary); + } +} diff --git a/cucumber_cpp/library/runtime/MakeRuntime.hpp b/cucumber_cpp/library/runtime/MakeRuntime.hpp new file mode 100644 index 00000000..43d52bb4 --- /dev/null +++ b/cucumber_cpp/library/runtime/MakeRuntime.hpp @@ -0,0 +1,20 @@ +#ifndef RUNTIME_MAKE_RUNTIME_HPP +#define RUNTIME_MAKE_RUNTIME_HPP + +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); + + std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, std::span sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); +} + +#endif diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp new file mode 100644 index 00000000..44b69347 --- /dev/null +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp @@ -0,0 +1,53 @@ +#include "cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/runtime/Worker.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include + +namespace cucumber_cpp::library::runtime +{ + SerialRuntimeAdapter::SerialRuntimeAdapter(std::string testRunStartedId, + util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + const support::RunOptions::Runtime& options, + support::SupportCodeLibrary supportCodeLibrary, + Context& programContext) + : testRunStartedId{ testRunStartedId } + , broadcaster{ broadcaster } + , idGenerator{ idGenerator } + , options{ options } + , supportCodeLibrary{ supportCodeLibrary } + , programContext{ programContext } + {} + + bool SerialRuntimeAdapter::Run(std::span assembledTestSuites) + { + bool failing = false; + runtime::Worker worker{ testRunStartedId, broadcaster, idGenerator, options, supportCodeLibrary, programContext }; + + worker.RunBeforeAllHooks(); + + for (const auto& assembledTestSuite : assembledTestSuites) + { + try + { + const auto success = worker.RunTestSuite(assembledTestSuite, failing); + if (!success) + failing = true; + } + catch (...) + { + failing = true; + } + } + + worker.RunAfterAllHooks(); + + return !failing; + } +} diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp new file mode 100644 index 00000000..916d6328 --- /dev/null +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp @@ -0,0 +1,36 @@ +#ifndef RUNTIME_SERIAL_RUNTIME_ADAPTER_HPP +#define RUNTIME_SERIAL_RUNTIME_ADAPTER_HPP + +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include + +namespace cucumber_cpp::library::runtime +{ + struct SerialRuntimeAdapter : support::RuntimeAdapter + { + SerialRuntimeAdapter(std::string testRunStartedId, + util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + const support::RunOptions::Runtime& options, + support::SupportCodeLibrary supportCodeLibrary, + Context& programContext); + + bool Run(std::span assembledTestSuites) override; + + private: + std::string testRunStartedId; + util::Broadcaster& broadcaster; + cucumber::gherkin::id_generator_ptr idGenerator; + const support::RunOptions::Runtime& options; + support::SupportCodeLibrary supportCodeLibrary; + Context& programContext; + }; +} + +#endif diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp new file mode 100644 index 00000000..a1ff3f8c --- /dev/null +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -0,0 +1,291 @@ + +#include "cucumber_cpp/library/runtime/TestCaseRunner.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/step_match_argument.hpp" +#include "cucumber/messages/suggestion.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + namespace + { + cucumber::messages::duration operator+=(cucumber::messages::duration durationA, cucumber::messages::duration durationB) + { + const auto seconds = durationA.seconds + durationB.seconds; + const auto nanos = durationA.nanos + durationB.nanos; + + if (nanos >= support::nanosecondsPerSecond) + return { seconds + 1, nanos - support::nanosecondsPerSecond }; + else + return { seconds, nanos }; + } + + std::vector BuildExpressionParameters(std::span arguments, cucumber_expression::ParameterRegistry& parameterRegistry) + { + std::vector parameters; + + for (const auto& argument : arguments) + { + const auto parameter = parameterRegistry.Lookup(*argument.parameter_type_name); + parameters.push_back(parameter.converter.toAny(argument.group)); + } + + return parameters; + } + + std::vector BuildRegularParameters(std::span arguments) + { + const auto transformedArguments = arguments | std::views::transform([](const auto& argument) + { + return argument.group.value.value(); + }); + return { transformedArguments.begin(), transformedArguments.end() }; + } + } + + TestCaseRunner::TestCaseRunner(util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + const cucumber::messages::gherkin_document& gherkinDocument, + const cucumber::messages::pickle& pickle, + const cucumber::messages::test_case& testCase, + std::size_t retries, + bool skip, + support::SupportCodeLibrary supportCodeLibrary, + Context& testSuiteContext) + : broadcaster{ broadcaster } + , idGenerator{ idGenerator } + , gherkinDocument{ gherkinDocument } + , pickle{ pickle } + , testCase{ testCase } + , maximumAttempts{ 1 + (skip ? 0 : retries) } + , skip{ skip } + , supportCodeLibrary{ supportCodeLibrary } + , testSuiteContext{ testSuiteContext } + {} + + cucumber::messages::test_step_result_status TestCaseRunner::Run() + { + for (std::size_t attempt = 0; attempt < maximumAttempts; ++attempt) + { + testStepResults.clear(); + + const auto willRetry = RunAttempt(attempt, (attempt + 1) < maximumAttempts); + + if (willRetry) + continue; + + return GetWorstStepResult().status; + } + + return cucumber::messages::test_step_result_status::UNKNOWN; + } + + bool TestCaseRunner::RunAttempt(std::size_t attempt, bool moreAttemptsAvailable) + { + Context testCaseContext{ &testSuiteContext }; + const auto currentTestCaseStartedId = idGenerator->next_id(); + bool willRetry = false; + + broadcaster.BroadcastEvent({ .test_case_started = cucumber::messages::test_case_started{ + .attempt = attempt, + .id = currentTestCaseStartedId, + .test_case_id = testCase.id, + .timestamp = support::TimestampNow(), + } }); + + bool seenSteps = false; + bool error = false; + + for (const auto& testStep : testCase.test_steps) + { + broadcaster.BroadcastEvent({ .test_step_started = cucumber::messages::test_step_started{ + .test_case_started_id = currentTestCaseStartedId, + .test_step_id = testStep.id, + .timestamp = support::TimestampNow(), + } }); + + cucumber::messages::test_step_result testStepResult; + + if (testStep.hook_id) + { + testStepResult = RunHook(supportCodeLibrary.hookRegistry.GetDefinitionById(testStep.hook_id.value()), !seenSteps, testCaseContext); + } + else + { + auto pickleStepIter = std::ranges::find(pickle.steps, testStep.pickle_step_id.value(), &cucumber::messages::pickle_step::id); + testStepResult = RunStep(*pickleStepIter, testStep, testCaseContext); + seenSteps = true; + } + testStepResults.emplace_back(testStepResult); + + broadcaster.BroadcastEvent({ .test_step_finished = cucumber::messages::test_step_finished{ + .test_case_started_id = currentTestCaseStartedId, + .test_step_id = testStep.id, + .test_step_result = testStepResult, + .timestamp = support::TimestampNow(), + } }); + } + + willRetry = GetWorstStepResult().status == cucumber::messages::test_step_result_status::FAILED && moreAttemptsAvailable; + + broadcaster.BroadcastEvent({ .test_case_finished = cucumber::messages::test_case_finished{ + .test_case_started_id = currentTestCaseStartedId, + .timestamp = support::TimestampNow(), + .will_be_retried = willRetry, + } }); + + return willRetry; + } + + cucumber::messages::test_step_result TestCaseRunner::RunHook(const HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext) + { + if (ShouldSkipHook(isBeforeHook)) + return { + .duration = cucumber::messages::duration{}, + .status = cucumber::messages::test_step_result_status::SKIPPED, + }; + + return InvokeStep(hookDefinition.factory(testCaseContext)); + } + + std::vector TestCaseRunner::RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext) + { + auto ids = supportCodeLibrary.hookRegistry.FindIds(hookType, pickle.tags); + std::vector results; + results.reserve(ids.size()); + + for (const auto& id : ids) + { + const auto& definition = supportCodeLibrary.hookRegistry.GetDefinitionById(id); + results.emplace_back(InvokeStep(definition.factory(testCaseContext))); + } + + return results; + } + + cucumber::messages::test_step_result TestCaseRunner::RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext) + { + auto stepDefinitions = (*testStep.step_definition_ids) | std::views::transform([&](const std::string& id) + { + return supportCodeLibrary.stepRegistry.StepDefinitions().at(id); + }); + + if (const auto count = testStep.step_definition_ids->size(); count == 0) + { + broadcaster.BroadcastEvent({ .suggestion = cucumber::messages::suggestion{ + .id = idGenerator->next_id(), + .pickle_step_id = pickleStep.id, + .snippets = {}, + } }); + + return { + .duration = cucumber::messages::duration{}, + .status = cucumber::messages::test_step_result_status::UNDEFINED, + }; + } + else if (count > 1) + { + return { + .duration = cucumber::messages::duration{}, + .message = "Ambiguous step definitions", + .status = cucumber::messages::test_step_result_status::AMBIGUOUS, + }; + } + else if (IsSkippingSteps()) + { + return { + .duration = cucumber::messages::duration{}, + .status = cucumber::messages::test_step_result_status::SKIPPED, + }; + } + + auto stepResults = RunStepHooks(pickleStep, HookType::beforeStep, testCaseContext); + + if (util::GetWorstTestStepResult(stepResults).status != cucumber::messages::test_step_result_status::FAILED) + { + const auto& definition = stepDefinitions.front(); + + std::variant, std::vector> parameters{}; + + if (!testStep.step_match_arguments_lists->empty()) + { + if (std::holds_alternative(definition.regex)) + parameters = BuildExpressionParameters(testStep.step_match_arguments_lists->front().step_match_arguments, supportCodeLibrary.parameterRegistry); + else + parameters = BuildRegularParameters(testStep.step_match_arguments_lists->front().step_match_arguments); + } + + const auto result = InvokeStep(definition.factory(testCaseContext, {}, {}), parameters); + stepResults.push_back(result); + } + + const auto afterStepHookResults = RunStepHooks(pickleStep, HookType::afterStep, testCaseContext); + stepResults.reserve(stepResults.size() + afterStepHookResults.size()); + stepResults.insert(stepResults.end(), afterStepHookResults.begin(), afterStepHookResults.end()); + + auto finalStepResult = util::GetWorstTestStepResult(stepResults); + for (const auto& stepResult : stepResults) + { + finalStepResult.duration += stepResult.duration; + } + + return finalStepResult; + } + + cucumber::messages::test_step_result TestCaseRunner::InvokeStep(std::unique_ptr body, const ExecuteArgs& args) + { + return body->ExecuteAndCatchExceptions(args); + } + + cucumber::messages::test_step_result TestCaseRunner::GetWorstStepResult() const + { + if (testStepResults.empty()) + return { + .status = skip ? cucumber::messages::test_step_result_status::SKIPPED : cucumber::messages::test_step_result_status::PASSED, + }; + + return util::GetWorstTestStepResult(testStepResults); + } + + bool TestCaseRunner::ShouldSkipHook(bool isBeforeHook) + { + return skip || (IsSkippingSteps() && isBeforeHook); + } + + bool TestCaseRunner::IsSkippingSteps() + { + return GetWorstStepResult().status != cucumber::messages::test_step_result_status::PASSED; + } +} diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.hpp b/cucumber_cpp/library/runtime/TestCaseRunner.hpp new file mode 100644 index 00000000..2f52fee0 --- /dev/null +++ b/cucumber_cpp/library/runtime/TestCaseRunner.hpp @@ -0,0 +1,114 @@ +#ifndef RUNTIME_TEST_CASE_RUNNER_HPP +#define RUNTIME_TEST_CASE_RUNNER_HPP + +#include "cucumber/gherkin/app.hpp" +#include "cucumber/gherkin/exceptions.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/gherkin/pickle_compiler.hpp" +#include "cucumber/gherkin/utils.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/parse_error.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/source.hpp" +#include "cucumber/messages/source_reference.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern.hpp" +#include "cucumber/messages/step_match_argument.hpp" +#include "cucumber/messages/suggestion.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_run_hook_finished.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_run_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber/messages/timestamp.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + struct TestCaseRunner + { + TestCaseRunner(util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + const cucumber::messages::gherkin_document& gherkinDocument, + const cucumber::messages::pickle& pickle, + const cucumber::messages::test_case& testCase, + std::size_t retries, + bool skip, + support::SupportCodeLibrary supportCodeLibrary, + Context& testSuiteContext); + + cucumber::messages::test_step_result_status Run(); + + bool RunAttempt(std::size_t attempt, bool moreAttemptsAvailable); + + cucumber::messages::test_step_result RunHook(const HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext); + + std::vector RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext); + + cucumber::messages::test_step_result RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext); + + cucumber::messages::test_step_result InvokeStep(std::unique_ptr body, const ExecuteArgs& args = {}); + cucumber::messages::test_step_result GetWorstStepResult() const; + + bool ShouldSkipHook(bool isBeforeHook); + bool IsSkippingSteps(); + + private: + util::Broadcaster& broadcaster; + cucumber::gherkin::id_generator_ptr idGenerator; + const cucumber::messages::gherkin_document& gherkinDocument; + const cucumber::messages::pickle& pickle; + const cucumber::messages::test_case& testCase; + std::size_t maximumAttempts; + bool skip; + support::SupportCodeLibrary supportCodeLibrary; + Context& testSuiteContext; + + std::vector testStepResults; + }; +} + +#endif diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp new file mode 100644 index 00000000..01c1a6a1 --- /dev/null +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -0,0 +1,241 @@ +#include "cucumber_cpp/library/runtime/Worker.hpp" +#include "cucumber/gherkin/app.hpp" +#include "cucumber/gherkin/exceptions.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/gherkin/pickle_compiler.hpp" +#include "cucumber/gherkin/utils.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/parse_error.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/source.hpp" +#include "cucumber/messages/source_reference.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern.hpp" +#include "cucumber/messages/step_match_argument.hpp" +#include "cucumber/messages/suggestion.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_run_hook_finished.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_run_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber/messages/timestamp.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/runtime/TestCaseRunner.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + namespace + { + cucumber::messages::duration operator+=(cucumber::messages::duration durationA, cucumber::messages::duration durationB) + { + const auto seconds = durationA.seconds + durationB.seconds; + const auto nanos = durationA.nanos + durationB.nanos; + + if (nanos >= support::nanosecondsPerSecond) + return { seconds + 1, nanos - support::nanosecondsPerSecond }; + else + return { seconds, nanos }; + } + + const auto to_underlying = [](const auto& value) + { + return static_cast>>(value); + }; + + const auto compare = [](const cucumber::messages::test_step_result& a, const cucumber::messages::test_step_result& b) + { + return to_underlying(a.status) < to_underlying(b.status); + }; + + cucumber::messages::test_step_result GetWorstTestStepResult(std::span testStepResults) + { + if (testStepResults.empty()) + return { .status = cucumber::messages::test_step_result_status::PASSED }; + return *std::ranges::max_element(testStepResults, compare); + } + + inline std::set failingStatuses{ + cucumber::messages::test_step_result_status::AMBIGUOUS, + cucumber::messages::test_step_result_status::FAILED, + cucumber::messages::test_step_result_status::UNDEFINED, + }; + } + + Worker::Worker(std::string_view testRunStartedId, + util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + const support::RunOptions::Runtime& options, + support::SupportCodeLibrary supportCodeLibrary, + Context& programContext) + : testRunStartedId{ testRunStartedId } + , broadcaster{ broadcaster } + , idGenerator{ idGenerator } + , options{ options } + , supportCodeLibrary{ supportCodeLibrary } + , programContext{ programContext } + {} + + std::vector Worker::RunBeforeAllHooks() + { + std::vector results; + const auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::beforeAll); + for (const auto& id : ids) + results.emplace_back(std::move(RunTestHook(id, programContext))); + + if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) + throw std::runtime_error("Failed before all hook"); + + return results; + } + + std::vector Worker::RunAfterAllHooks() + { + std::vector results; + const auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::afterAll); + for (const auto& id : ids) + results.emplace_back(std::move(RunTestHook(id, programContext))); + + if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) + throw std::runtime_error("Failed after all hook"); + + return results; + } + + bool Worker::RunTestSuite(const assemble::AssembledTestSuite& assembledTestSuite, bool failing) + { + Context testSuiteContext{ &programContext }; + + RunBeforeTestSuiteHooks(*assembledTestSuite.gherkinDocument.feature, testSuiteContext); + + auto failed = false; + for (const auto& assembledTestCase : assembledTestSuite.testCases) + failed |= !RunTestCase(assembledTestSuite.gherkinDocument, assembledTestCase, testSuiteContext, failing); + + RunAfterTestSuiteHooks(*assembledTestSuite.gherkinDocument.feature, testSuiteContext); + + return !failed; + } + + bool Worker::RunTestCase(const cucumber::messages::gherkin_document& gherkinDocument, const assemble::AssembledTestCase& assembledTestCase, Context& testSuiteContext, bool failing) + { + TestCaseRunner testCaseRunner{ + broadcaster, + idGenerator, + gherkinDocument, + assembledTestCase.pickle, + assembledTestCase.testCase, + options.retry, + options.dryRun || (options.failFast && failing), + supportCodeLibrary, + testSuiteContext, + }; + + const auto status = testCaseRunner.Run(); + + return !IsStatusFailed(status); + } + + std::vector Worker::RunBeforeTestSuiteHooks(const cucumber::messages::feature& feature, Context& context) + { + std::vector results; + const auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::beforeFeature, feature.tags); + for (const auto& id : ids) + results.emplace_back(std::move(RunTestHook(id, context))); + + if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) + throw std::runtime_error("Failed before feature hook"); + + return results; + } + + std::vector Worker::RunAfterTestSuiteHooks(const cucumber::messages::feature& feature, Context& context) + { + std::vector results; + const auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::afterFeature, feature.tags); + for (const auto& id : ids) + results.emplace_back(std::move(RunTestHook(id, context))); + + if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) + throw std::runtime_error("Failed after feature hook"); + + return results; + } + + cucumber::messages::test_step_result Worker::RunTestHook(std::string id, Context& context) + { + const auto& definition = supportCodeLibrary.hookRegistry.GetDefinitionById(id); + const auto testRunHookStartedid = idGenerator->next_id(); + + broadcaster.BroadcastEvent({ .test_run_hook_started = cucumber::messages::test_run_hook_started{ + .id = testRunHookStartedid, + .test_run_started_id = std::string{ testRunStartedId }, + .hook_id = definition.hook.id, + .timestamp = support::TimestampNow(), + } }); + + auto result = definition.factory(context)->ExecuteAndCatchExceptions(); + + broadcaster.BroadcastEvent({ .test_run_hook_finished = cucumber::messages::test_run_hook_finished{ + .test_run_hook_started_id = testRunHookStartedid, + .result = result, + .timestamp = support::TimestampNow(), + } }); + + return result; + } + + bool Worker::IsStatusFailed(cucumber::messages::test_step_result_status status) + { + if (options.dryRun) + return false; + + if (options.strict && status == cucumber::messages::test_step_result_status::PENDING) + return true; + + return failingStatuses.contains(status); + } + +} diff --git a/cucumber_cpp/library/runtime/Worker.hpp b/cucumber_cpp/library/runtime/Worker.hpp new file mode 100644 index 00000000..298b74c7 --- /dev/null +++ b/cucumber_cpp/library/runtime/Worker.hpp @@ -0,0 +1,103 @@ +#ifndef RUNTIME_WORKER_HPP +#define RUNTIME_WORKER_HPP + +#include "cucumber/gherkin/app.hpp" +#include "cucumber/gherkin/exceptions.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/gherkin/pickle_compiler.hpp" +#include "cucumber/gherkin/utils.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/parse_error.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/source.hpp" +#include "cucumber/messages/source_reference.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern.hpp" +#include "cucumber/messages/step_match_argument.hpp" +#include "cucumber/messages/suggestion.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_run_hook_finished.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_run_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber/messages/timestamp.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + struct Worker + { + Worker(std::string_view testRunStartedId, + util::Broadcaster& broadcaster, + cucumber::gherkin::id_generator_ptr idGenerator, + const support::RunOptions::Runtime& options, + support::SupportCodeLibrary supportCodeLibrary, + Context& programContext); + + std::vector RunBeforeAllHooks(); + std::vector RunAfterAllHooks(); + + bool RunTestSuite(const assemble::AssembledTestSuite& assembledTestSuite, bool failing); + bool RunTestCase(const cucumber::messages::gherkin_document& gherkinDocument, const assemble::AssembledTestCase& assembledTestCase, Context& testSuiteContext, bool failing); + + private: + std::vector RunBeforeTestSuiteHooks(const cucumber::messages::feature& feature, Context& context); + std::vector RunAfterTestSuiteHooks(const cucumber::messages::feature& feature, Context& context); + + cucumber::messages::test_step_result RunTestHook(std::string id, Context& context); + + bool IsStatusFailed(cucumber::messages::test_step_result_status status); + + std::string_view testRunStartedId; + util::Broadcaster& broadcaster; + cucumber::gherkin::id_generator_ptr idGenerator; + const support::RunOptions::Runtime& options; + support::SupportCodeLibrary supportCodeLibrary; + Context& programContext; + }; +} + +#endif diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt new file mode 100644 index 00000000..0189b908 --- /dev/null +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -0,0 +1,26 @@ +add_library(cucumber_cpp.library.support STATIC ${CCR_EXCLUDE_FROM_ALL}) + +target_sources(cucumber_cpp.library.support PRIVATE + Duration.hpp + Duration.cpp + Join.cpp + Join.hpp + SupportCodeLibrary.hpp + Timestamp.cpp + Timestamp.hpp + Types.hpp +) + +target_include_directories(cucumber_cpp.library.support PUBLIC + ../../.. +) + +target_link_libraries(cucumber_cpp.library.support PUBLIC + cucumber_cpp.library + cucumber_cpp.library.util +) + +if (CCR_BUILD_TESTS) + # add_subdirectory(test) + # add_subdirectory(test_helper) +endif() diff --git a/cucumber_cpp/library/support/Duration.cpp b/cucumber_cpp/library/support/Duration.cpp new file mode 100644 index 00000000..510ff747 --- /dev/null +++ b/cucumber_cpp/library/support/Duration.cpp @@ -0,0 +1,46 @@ + +#include "cucumber/messages/duration.hpp" +#include "cucumber_cpp/library/support/Duration.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include +#include + +namespace cucumber_cpp::library::support +{ + namespace + { + std::chrono::milliseconds ToMillis(std::chrono::seconds seconds, std::chrono::nanoseconds nanos) + { + return std::chrono::duration_cast(seconds) + + std::chrono::duration_cast(nanos); + } + + cucumber::messages::duration ToDuration(std::chrono::milliseconds millis) + { + return { + .seconds = millis.count() / millisecondsPerSecond, + .nanos = (millis.count() % millisecondsPerSecond) * nanosecondsPerMillisecond, + }; + } + } + + cucumber::messages::duration MillisecondsToDuration(std::chrono::milliseconds millis) + { + return ToDuration(millis); + } + + std::chrono::milliseconds DurationToMilliseconds(const cucumber::messages::duration& duration) + { + return ToMillis(std::chrono::seconds(duration.seconds), std::chrono::nanoseconds(duration.nanos)); + } + + cucumber::messages::duration& operator+=(cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs) + { + const auto totalNanos = lhs.nanos + rhs.nanos; + lhs.seconds += rhs.seconds; + lhs.seconds += totalNanos / nanosecondsPerSecond; + lhs.nanos = totalNanos % nanosecondsPerSecond; + + return lhs; + } +} diff --git a/cucumber_cpp/library/support/Duration.hpp b/cucumber_cpp/library/support/Duration.hpp new file mode 100644 index 00000000..3701491a --- /dev/null +++ b/cucumber_cpp/library/support/Duration.hpp @@ -0,0 +1,21 @@ +#ifndef SUPPORT_DURATION_HPP +#define SUPPORT_DURATION_HPP + +#include "cucumber/messages/duration.hpp" +#include +#include + +namespace cucumber_cpp::library::support +{ + cucumber::messages::duration MillisecondsToDuration(std::chrono::milliseconds millis); + + std::chrono::milliseconds DurationToMilliseconds(const cucumber::messages::duration& duration); + cucumber::messages::duration& operator+=(cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs); +} + +namespace cucumber::messages +{ + using cucumber_cpp::library::support::operator+=; +}; + +#endif diff --git a/cucumber_cpp/library/support/Join.cpp b/cucumber_cpp/library/support/Join.cpp new file mode 100644 index 00000000..2ff492ca --- /dev/null +++ b/cucumber_cpp/library/support/Join.cpp @@ -0,0 +1,27 @@ +#include "cucumber_cpp/library/support/Join.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::support +{ + std::string Join(std::initializer_list parts, const std::string& separator) + { + return Join({ parts.begin(), parts.end() }, separator); + } + + std::string Join(std::span parts, const std::string& separator) + { + if (parts.size() == 0) + return ""; + + std::string joined = *parts.begin(); + + for (const auto part : parts | std::views::drop(1)) + joined += std::format("{}{}", separator, part); + + return joined; + } +} diff --git a/cucumber_cpp/library/support/Join.hpp b/cucumber_cpp/library/support/Join.hpp new file mode 100644 index 00000000..07b185ac --- /dev/null +++ b/cucumber_cpp/library/support/Join.hpp @@ -0,0 +1,14 @@ +#ifndef SUPPORT_JOIN_HPP +#define SUPPORT_JOIN_HPP + +#include +#include +#include + +namespace cucumber_cpp::library::support +{ + std::string Join(std::initializer_list parts, const std::string& separator); + std::string Join(std::span parts, const std::string& separator); +} + +#endif diff --git a/cucumber_cpp/library/support/Polyfill.hpp b/cucumber_cpp/library/support/Polyfill.hpp new file mode 100644 index 00000000..37792917 --- /dev/null +++ b/cucumber_cpp/library/support/Polyfill.hpp @@ -0,0 +1,18 @@ +#ifndef SUPPORT_POLYFILL_HPP +#define SUPPORT_POLYFILL_HPP + +#include +#include +#include +#include + +namespace cucumber_cpp::library::support +{ + template + void print(std::ostream& outputStream, std::format_string fmt, Args&&... args) + { + outputStream << std::format(fmt, std::forward(args)...); + } +} + +#endif diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp new file mode 100644 index 00000000..ea182a54 --- /dev/null +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -0,0 +1,18 @@ +#ifndef SUPPORT_SUPPORT_CODE_LIBRARY_HPP +#define SUPPORT_SUPPORT_CODE_LIBRARY_HPP + +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" + +namespace cucumber_cpp::library::support +{ + struct SupportCodeLibrary + { + HookRegistry& hookRegistry; + StepRegistry& stepRegistry; + cucumber_expression::ParameterRegistry& parameterRegistry; + }; +} + +#endif diff --git a/cucumber_cpp/library/support/Timestamp.cpp b/cucumber_cpp/library/support/Timestamp.cpp new file mode 100644 index 00000000..e7c0e904 --- /dev/null +++ b/cucumber_cpp/library/support/Timestamp.cpp @@ -0,0 +1,41 @@ + +#include "cucumber/messages/timestamp.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber_cpp/library/support/Duration.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include + +namespace cucumber_cpp::library::support +{ + namespace + { + std::chrono::milliseconds ToMillis(std::chrono::seconds seconds, std::chrono::nanoseconds nanos) + { + return std::chrono::duration_cast(seconds) + + std::chrono::duration_cast(nanos); + } + + std::chrono::milliseconds TimestampToMillis(const cucumber::messages::timestamp& timestamp) + { + return ToMillis(std::chrono::seconds(timestamp.seconds), std::chrono::nanoseconds(timestamp.nanos)); + } + } + + cucumber::messages::timestamp TimestampNow() + { + const auto now = std::chrono::system_clock::now().time_since_epoch(); + const auto nowMillis = std::chrono::duration_cast(now).count(); + const auto seconds = nowMillis / millisecondsPerSecond; + const auto nanos = (nowMillis % millisecondsPerSecond) * nanosecondsPerMillisecond; + return cucumber::messages::timestamp{ + .seconds = seconds, + .nanos = nanos, + }; + } + + cucumber::messages::duration operator-(const cucumber::messages::timestamp& lhs, const cucumber::messages::timestamp& rhs) + { + const auto durationMillis = TimestampToMillis(lhs) - TimestampToMillis(rhs); + return MillisecondsToDuration(durationMillis); + } +} diff --git a/cucumber_cpp/library/support/Timestamp.hpp b/cucumber_cpp/library/support/Timestamp.hpp new file mode 100644 index 00000000..62282282 --- /dev/null +++ b/cucumber_cpp/library/support/Timestamp.hpp @@ -0,0 +1,24 @@ +#ifndef SUPPORT_TIMESTAMP_HPP +#define SUPPORT_TIMESTAMP_HPP + +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/timestamp.hpp" +#include + +namespace cucumber_cpp::library::support +{ + constexpr std::size_t millisecondsPerSecond = 1e3; + constexpr std::size_t nanosecondsPerMillisecond = 1e6; + constexpr std::size_t nanosecondsPerSecond = 1e9; + + cucumber::messages::timestamp TimestampNow(); + + cucumber::messages::duration operator-(const cucumber::messages::timestamp& lhs, const cucumber::messages::timestamp& rhs); +} + +namespace cucumber::messages +{ + using cucumber_cpp::library::support::operator-; +}; + +#endif diff --git a/cucumber_cpp/library/support/Types.hpp b/cucumber_cpp/library/support/Types.hpp new file mode 100644 index 00000000..303dfd50 --- /dev/null +++ b/cucumber_cpp/library/support/Types.hpp @@ -0,0 +1,61 @@ +#ifndef SUPPORT_TYPES_HPP +#define SUPPORT_TYPES_HPP + +#include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::support +{ + struct RunOptions + { + struct Sources + { + std::span paths; + std::string_view tagExpression; + } sources; + + struct Runtime + { + bool dryRun; + bool failFast; + std::size_t retry; + bool strict; + } runtime; + + struct RunEnvironment + { + std::optional cwd; + std::optional> env; + } runEnvironment; + }; + + struct PickleSource + { + std::shared_ptr pickle; + std::shared_ptr gherkinDocument; + std::shared_ptr location; + }; + + struct Runtime + { + virtual ~Runtime() = default; + virtual bool Run() = 0; + }; + + struct RuntimeAdapter + { + virtual ~RuntimeAdapter() = default; + virtual bool Run(std::span assembledTestSuites) = 0; + }; + +} + +#endif diff --git a/cucumber_cpp/library/tag_expression/CMakeLists.txt b/cucumber_cpp/library/tag_expression/CMakeLists.txt index 479264ed..8454d1cd 100644 --- a/cucumber_cpp/library/tag_expression/CMakeLists.txt +++ b/cucumber_cpp/library/tag_expression/CMakeLists.txt @@ -17,6 +17,10 @@ target_include_directories(cucumber_cpp.library.tag_expression PUBLIC ../../.. ) +target_link_libraries(cucumber_cpp.library.tag_expression PUBLIC + cucumber_gherkin_lib +) + if (CCR_BUILD_TESTS) add_subdirectory(test) endif() diff --git a/cucumber_cpp/library/tag_expression/Model.cpp b/cucumber_cpp/library/tag_expression/Model.cpp index 1fb0b62b..8535d45c 100644 --- a/cucumber_cpp/library/tag_expression/Model.cpp +++ b/cucumber_cpp/library/tag_expression/Model.cpp @@ -1,10 +1,14 @@ #include "cucumber_cpp/library/tag_expression/Model.hpp" +#include "cucumber/messages/pickle_tag.hpp" +#include "cucumber/messages/tag.hpp" +#include #include #include #include #include #include #include +#include #include #include #include @@ -16,6 +20,16 @@ namespace cucumber_cpp::library::tag_expression return true; } + bool TrueExpression::Evaluate(std::span tags) const + { + return true; + } + + bool TrueExpression::Evaluate(std::span tags) const + { + return true; + } + TrueExpression::operator std::string() const { return "true"; @@ -30,6 +44,16 @@ namespace cucumber_cpp::library::tag_expression return tags.contains(name); } + bool LiteralExpression::Evaluate(std::span tags) const + { + return std::ranges::find(tags, name, &cucumber::messages::pickle_tag::name) != tags.end(); + } + + bool LiteralExpression::Evaluate(std::span tags) const + { + return std::ranges::find(tags, name, &cucumber::messages::tag::name) != tags.end(); + } + LiteralExpression::operator std::string() const { auto replaceAll = [](std::string& str, std::string_view from, std::string_view to) @@ -65,6 +89,16 @@ namespace cucumber_cpp::library::tag_expression return left->Evaluate(tags) && right->Evaluate(tags); } + bool AndExpression::Evaluate(std::span tags) const + { + return left->Evaluate(tags) && right->Evaluate(tags); + } + + bool AndExpression::Evaluate(std::span tags) const + { + return left->Evaluate(tags) && right->Evaluate(tags); + } + AndExpression::operator std::string() const { if (!left || !right) @@ -83,6 +117,16 @@ namespace cucumber_cpp::library::tag_expression return left->Evaluate(tags) || right->Evaluate(tags); } + bool OrExpression::Evaluate(std::span tags) const + { + return left->Evaluate(tags) || right->Evaluate(tags); + } + + bool OrExpression::Evaluate(std::span tags) const + { + return left->Evaluate(tags) || right->Evaluate(tags); + } + OrExpression::operator std::string() const { if (!left || !right) @@ -100,6 +144,16 @@ namespace cucumber_cpp::library::tag_expression return !operand->Evaluate(tags); } + bool NotExpression::Evaluate(std::span tags) const + { + return !operand->Evaluate(tags); + } + + bool NotExpression::Evaluate(std::span tags) const + { + return !operand->Evaluate(tags); + } + NotExpression::operator std::string() const { if (!operand) diff --git a/cucumber_cpp/library/tag_expression/Model.hpp b/cucumber_cpp/library/tag_expression/Model.hpp index 9ae69a5a..b515b12e 100644 --- a/cucumber_cpp/library/tag_expression/Model.hpp +++ b/cucumber_cpp/library/tag_expression/Model.hpp @@ -1,9 +1,12 @@ #ifndef TAG_EXPRESSION_MODEL_HPP #define TAG_EXPRESSION_MODEL_HPP +#include "cucumber/messages/pickle_tag.hpp" +#include "cucumber/messages/tag.hpp" #include #include #include +#include #include namespace cucumber_cpp::library::tag_expression @@ -13,12 +16,18 @@ namespace cucumber_cpp::library::tag_expression virtual ~Expression() = default; virtual bool Evaluate(const std::set>& tags) const = 0; + virtual bool Evaluate(std::span tags) const = 0; + virtual bool Evaluate(std::span tags) const = 0; + virtual explicit operator std::string() const = 0; }; struct TrueExpression : Expression { bool Evaluate(const std::set>& tags) const override; + bool Evaluate(std::span tags) const override; + bool Evaluate(std::span tags) const override; + explicit operator std::string() const override; }; @@ -27,6 +36,9 @@ namespace cucumber_cpp::library::tag_expression explicit LiteralExpression(std::string name); bool Evaluate(const std::set>& tags) const override; + bool Evaluate(std::span tags) const override; + bool Evaluate(std::span tags) const override; + explicit operator std::string() const override; private: @@ -38,6 +50,9 @@ namespace cucumber_cpp::library::tag_expression AndExpression(std::unique_ptr left, std::unique_ptr right); bool Evaluate(const std::set>& tags) const override; + bool Evaluate(std::span tags) const override; + bool Evaluate(std::span tags) const override; + explicit operator std::string() const override; private: @@ -50,6 +65,9 @@ namespace cucumber_cpp::library::tag_expression OrExpression(std::unique_ptr left, std::unique_ptr right); bool Evaluate(const std::set>& tags) const override; + bool Evaluate(std::span tags) const override; + bool Evaluate(std::span tags) const override; + explicit operator std::string() const override; private: @@ -62,6 +80,9 @@ namespace cucumber_cpp::library::tag_expression explicit NotExpression(std::unique_ptr operand); bool Evaluate(const std::set>& tags) const override; + bool Evaluate(std::span tags) const override; + bool Evaluate(std::span tags) const override; + explicit operator std::string() const override; private: diff --git a/cucumber_cpp/library/test/CMakeLists.txt b/cucumber_cpp/library/test/CMakeLists.txt index 998d657a..1191772a 100644 --- a/cucumber_cpp/library/test/CMakeLists.txt +++ b/cucumber_cpp/library/test/CMakeLists.txt @@ -13,6 +13,5 @@ target_link_libraries(cucumber_cpp.library.test PUBLIC target_sources(cucumber_cpp.library.test PRIVATE TestApplication.cpp TestContext.cpp - TestTagExpression.cpp TestSteps.cpp ) diff --git a/cucumber_cpp/library/test/TestSteps.cpp b/cucumber_cpp/library/test/TestSteps.cpp index 8b4fc419..a4246490 100644 --- a/cucumber_cpp/library/test/TestSteps.cpp +++ b/cucumber_cpp/library/test/TestSteps.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "gtest/gtest.h" @@ -13,8 +14,7 @@ namespace cucumber_cpp::library { struct TestSteps : testing::Test { - - cucumber_expression::ParameterRegistry parameterRegistry; + cucumber_expression::ParameterRegistry parameterRegistry{}; StepRegistry stepRegistry{ parameterRegistry }; }; @@ -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, {}, "")->ExecuteAndCatchExceptions(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, {}, {})->ExecuteAndCatchExceptions(matches.matches); } TEST_F(TestSteps, EscapedParenthesis) diff --git a/cucumber_cpp/library/test/TestTagExpression.cpp b/cucumber_cpp/library/test/TestTagExpression.cpp deleted file mode 100644 index 2e34a24f..00000000 --- a/cucumber_cpp/library/test/TestTagExpression.cpp +++ /dev/null @@ -1,84 +0,0 @@ -#include "cucumber_cpp/library/TagExpression.hpp" -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include -#include -#include - -namespace cucumber_cpp::library -{ - struct TestTagExpression : testing::Test - { - std::set> inputTags = { "@abc", "@def", "@efg" }; - std::set> noTags = {}; - std::set> ignoredTags = { "@abc", "@def", "@efg", "@ignore" }; - }; - - TEST_F(TestTagExpression, EmptyTagExpression) - { - EXPECT_THAT(IsTagExprSelected("", noTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("", inputTags), testing::IsTrue()); - } - - TEST_F(TestTagExpression, EmptyTags) - { - EXPECT_THAT(IsTagExprSelected("", noTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("@foo", noTags), testing::IsFalse()); - } - - TEST_F(TestTagExpression, SingleTag) - { - EXPECT_THAT(IsTagExprSelected("@abc", noTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@abc", inputTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("@abc", ignoredTags), testing::IsTrue()); - - EXPECT_THAT(IsTagExprSelected("@foo", inputTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@foo", ignoredTags), testing::IsFalse()); - } - - TEST_F(TestTagExpression, AndTag) - { - EXPECT_THAT(IsTagExprSelected("@abc and @def", noTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@abc and @def", inputTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("@abc and @def", ignoredTags), testing::IsTrue()); - - EXPECT_THAT(IsTagExprSelected("@abc and @foo", inputTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@abc and @foo", ignoredTags), testing::IsFalse()); - } - - TEST_F(TestTagExpression, OrTag) - { - EXPECT_THAT(IsTagExprSelected("@foo or @def", noTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@abc or @foo", inputTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("@abc or @foo", ignoredTags), testing::IsTrue()); - - EXPECT_THAT(IsTagExprSelected("@fez or @foo", inputTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@fez or @foo", ignoredTags), testing::IsFalse()); - } - - TEST_F(TestTagExpression, AndOrGroupedTag) - { - EXPECT_THAT(IsTagExprSelected("@abc and (@def or @efg)", noTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@abc and (@def or @efg)", inputTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("@abc and (@def or @efg)", ignoredTags), testing::IsTrue()); - - EXPECT_THAT(IsTagExprSelected("@fez and (@def or @efg)", inputTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@fez and (@def or @efg)", ignoredTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@abc and (@fez or @bar)", inputTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@abc and (@fez or @bar)", ignoredTags), testing::IsFalse()); - } - - TEST_F(TestTagExpression, Negating) - { - EXPECT_THAT(IsTagExprSelected("not @ignore", noTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("not @ignore", inputTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("not @ignore", ignoredTags), testing::IsFalse()); - } - - TEST_F(TestTagExpression, WithTagNegating) - { - EXPECT_THAT(IsTagExprSelected("@abc and not @ignore", noTags), testing::IsFalse()); - EXPECT_THAT(IsTagExprSelected("@abc and not @ignore", inputTags), testing::IsTrue()); - EXPECT_THAT(IsTagExprSelected("@abc and not @ignore", ignoredTags), testing::IsFalse()); - } -} diff --git a/cucumber_cpp/library/util/Broadcaster.cpp b/cucumber_cpp/library/util/Broadcaster.cpp new file mode 100644 index 00000000..a3932f20 --- /dev/null +++ b/cucumber_cpp/library/util/Broadcaster.cpp @@ -0,0 +1,41 @@ +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber/messages/envelope.hpp" +#include +#include + +namespace cucumber_cpp::library::util +{ + Listener::Listener(Broadcaster& broadcaster, const std::function& onEvent) + : broadcaster{ broadcaster } + , onEvent{ onEvent } + { + broadcaster.AddListener(this); + } + + Listener::~Listener() + { + broadcaster.RemoveListener(this); + } + + void Listener::Invoke(const cucumber::messages::envelope& envelope) const + { + if (onEvent) + onEvent(envelope); + } + + void Broadcaster::AddListener(Listener* listener) + { + listeners.push_back(listener); + } + + void Broadcaster::RemoveListener(Listener* listener) + { + std::erase(listeners, listener); + } + + void Broadcaster::BroadcastEvent(const cucumber::messages::envelope& envelope) + { + for (auto& listener : listeners) + listener->Invoke(envelope); + } +} diff --git a/cucumber_cpp/library/util/Broadcaster.hpp b/cucumber_cpp/library/util/Broadcaster.hpp new file mode 100644 index 00000000..56090388 --- /dev/null +++ b/cucumber_cpp/library/util/Broadcaster.hpp @@ -0,0 +1,36 @@ +#ifndef LIBRARY_20EVENT_EMITTER_HPP +#define LIBRARY_20EVENT_EMITTER_HPP + +#include "cucumber/messages/envelope.hpp" +#include +#include + +namespace cucumber_cpp::library::util +{ + struct Broadcaster; + + struct Listener + { + explicit Listener(Broadcaster& broadcaster, const std::function& onEvent); + ~Listener(); + + void Invoke(const cucumber::messages::envelope& envelope) const; + + private: + Broadcaster& broadcaster; + std::function onEvent; + }; + + struct Broadcaster + { + void AddListener(Listener* listener); + void RemoveListener(Listener* listener); + + void BroadcastEvent(const cucumber::messages::envelope& envelope); + + private: + std::vector listeners; + }; +} + +#endif diff --git a/cucumber_cpp/library/util/CMakeLists.txt b/cucumber_cpp/library/util/CMakeLists.txt index ce0ae4d4..c3f0ceb0 100644 --- a/cucumber_cpp/library/util/CMakeLists.txt +++ b/cucumber_cpp/library/util/CMakeLists.txt @@ -1,9 +1,17 @@ -add_library(cucumber_cpp.library.util INTERFACE ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.util STATIC ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.util PRIVATE + Broadcaster.cpp + Broadcaster.hpp + GetWorstTestStepResult.hpp + GetWorstTestStepResult.cpp Immoveable.hpp ) -target_include_directories(cucumber_cpp.library.util INTERFACE +target_include_directories(cucumber_cpp.library.util PUBLIC ../../.. ) + +target_link_libraries(cucumber_cpp.library.util PUBLIC + cucumber_gherkin_lib +) diff --git a/cucumber_cpp/library/util/GetWorstTestStepResult.cpp b/cucumber_cpp/library/util/GetWorstTestStepResult.cpp new file mode 100644 index 00000000..966b41ef --- /dev/null +++ b/cucumber_cpp/library/util/GetWorstTestStepResult.cpp @@ -0,0 +1,30 @@ +#include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::util +{ + namespace + { + const auto to_underlying = [](const auto& value) + { + return static_cast>>(value); + }; + + const auto compare = [](const cucumber::messages::test_step_result& a, const cucumber::messages::test_step_result& b) + { + return to_underlying(a.status) < to_underlying(b.status); + }; + } + + cucumber::messages::test_step_result GetWorstTestStepResult(std::span testStepResults) + { + if (testStepResults.empty()) + return { .status = cucumber::messages::test_step_result_status::PASSED }; + + return *std::ranges::max_element(testStepResults, compare); + } +} diff --git a/cucumber_cpp/library/util/GetWorstTestStepResult.hpp b/cucumber_cpp/library/util/GetWorstTestStepResult.hpp new file mode 100644 index 00000000..6471c921 --- /dev/null +++ b/cucumber_cpp/library/util/GetWorstTestStepResult.hpp @@ -0,0 +1,12 @@ +#ifndef UTIL_GET_WORST_TEST_STEP_RESULT_HPP +#define UTIL_GET_WORST_TEST_STEP_RESULT_HPP + +#include "cucumber/messages/test_step_result.hpp" +#include + +namespace cucumber_cpp::library::util +{ + cucumber::messages::test_step_result GetWorstTestStepResult(std::span testStepResults); +} + +#endif diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 1b229006..2721d451 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -2,8 +2,10 @@ set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "") add_subdirectory(nlohmann) # before cucumber +add_subdirectory(agauniyal) add_subdirectory(cliutils) add_subdirectory(cucumber) add_subdirectory(googletest) add_subdirectory(jbeder) +add_subdirectory(jupyter-xeus) add_subdirectory(zeux) diff --git a/external/agauniyal/CMakeLists.txt b/external/agauniyal/CMakeLists.txt new file mode 100644 index 00000000..d2cf05f6 --- /dev/null +++ b/external/agauniyal/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(rang) diff --git a/external/agauniyal/rang/CMakeLists.txt b/external/agauniyal/rang/CMakeLists.txt new file mode 100644 index 00000000..1b9d7d0c --- /dev/null +++ b/external/agauniyal/rang/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(rang INTERFACE) +target_include_directories(rang INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/external/agauniyal/rang/include/rang.hpp b/external/agauniyal/rang/include/rang.hpp new file mode 100644 index 00000000..e30a76d9 --- /dev/null +++ b/external/agauniyal/rang/include/rang.hpp @@ -0,0 +1,562 @@ +#ifndef RANG_DOT_HPP +#define RANG_DOT_HPP + +#if defined(__unix__) || defined(__unix) || defined(__linux__) +#define OS_LINUX +#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64) +#define OS_WIN +#elif defined(__APPLE__) || defined(__MACH__) +#define OS_MAC +#else +#error Unknown Platform +#endif + +#if defined(OS_LINUX) || defined(OS_MAC) +#include + +#elif defined(OS_WIN) + +#if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600) +#error \ + "Please include rang.hpp before any windows system headers or set _WIN32_WINNT at least to _WIN32_WINNT_VISTA" +#elif !defined(_WIN32_WINNT) +#define _WIN32_WINNT _WIN32_WINNT_VISTA +#endif + +#include +#include +#include + +// Only defined in windows 10 onwards, redefining in lower windows since it +// doesn't gets used in lower versions +// https://docs.microsoft.com/en-us/windows/console/getconsolemode +#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING +#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 +#endif + +#endif + +#include +#include +#include +#include +#include + +namespace rang +{ + + /* For better compability with most of terminals do not use any style settings + * except of reset, bold and reversed. + * Note that on Windows terminals bold style is same as fgB color. + */ + enum class style + { + reset = 0, + bold = 1, + dim = 2, + italic = 3, + underline = 4, + blink = 5, + rblink = 6, + reversed = 7, + conceal = 8, + crossed = 9 + }; + + enum class fg + { + black = 30, + red = 31, + green = 32, + yellow = 33, + blue = 34, + magenta = 35, + cyan = 36, + gray = 37, + reset = 39 + }; + + enum class bg + { + black = 40, + red = 41, + green = 42, + yellow = 43, + blue = 44, + magenta = 45, + cyan = 46, + gray = 47, + reset = 49 + }; + + enum class fgB + { + black = 90, + red = 91, + green = 92, + yellow = 93, + blue = 94, + magenta = 95, + cyan = 96, + gray = 97 + }; + + enum class bgB + { + black = 100, + red = 101, + green = 102, + yellow = 103, + blue = 104, + magenta = 105, + cyan = 106, + gray = 107 + }; + + enum class control + { // Behaviour of rang function calls + Off = 0, // toggle off rang style/color calls + Auto = 1, // (Default) autodect terminal and colorize if needed + Force = 2 // force ansi color output to non terminal streams + }; + // Use rang::setControlMode to set rang control mode + + enum class winTerm + { // Windows Terminal Mode + Auto = 0, // (Default) automatically detects wheter Ansi or Native API + Ansi = 1, // Force use Ansi API + Native = 2 // Force use Native API + }; + + // Use rang::setWinTermMode to explicitly set terminal API for Windows + // Calling rang::setWinTermMode have no effect on other OS + + namespace rang_implementation + { + + inline std::atomic& controlMode() noexcept + { + static std::atomic value(control::Auto); + return value; + } + + inline std::atomic& winTermMode() noexcept + { + static std::atomic termMode(winTerm::Auto); + return termMode; + } + + inline bool supportsColor() noexcept + { +#if defined(OS_LINUX) || defined(OS_MAC) + + static const bool result = [] + { + const char* Terms[] = { "ansi", "color", "console", "cygwin", "gnome", + "konsole", "kterm", "linux", "msys", "putty", + "rxvt", "screen", "vt100", "xterm" }; + + const char* env_p = std::getenv("TERM"); + if (env_p == nullptr) + { + return false; + } + return std::any_of(std::begin(Terms), std::end(Terms), + [&](const char* term) + { + return std::strstr(env_p, term) != nullptr; + }); + }(); + +#elif defined(OS_WIN) + // All windows versions support colors through native console methods + static constexpr bool result = true; +#endif + return result; + } + +#ifdef OS_WIN + + inline bool isMsysPty(int fd) noexcept + { + // Dynamic load for binary compability with old Windows + const auto ptrGetFileInformationByHandleEx = reinterpret_cast( + GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), + "GetFileInformationByHandleEx")); + if (!ptrGetFileInformationByHandleEx) + { + return false; + } + + HANDLE h = reinterpret_cast(_get_osfhandle(fd)); + if (h == INVALID_HANDLE_VALUE) + { + return false; + } + + // Check that it's a pipe: + if (GetFileType(h) != FILE_TYPE_PIPE) + { + return false; + } + + // POD type is binary compatible with FILE_NAME_INFO from WinBase.h + // It have the same alignment and used to avoid UB in caller code + struct MY_FILE_NAME_INFO + { + DWORD FileNameLength; + WCHAR FileName[MAX_PATH]; + }; + + auto pNameInfo = std::unique_ptr( + new (std::nothrow) MY_FILE_NAME_INFO()); + if (!pNameInfo) + { + return false; + } + + // Check pipe name is template of + // {"cygwin-","msys-"}XXXXXXXXXXXXXXX-ptyX-XX + if (!ptrGetFileInformationByHandleEx(h, FileNameInfo, pNameInfo.get(), + sizeof(MY_FILE_NAME_INFO))) + { + return false; + } + std::wstring name(pNameInfo->FileName, pNameInfo->FileNameLength / sizeof(WCHAR)); + if ((name.find(L"msys-") == std::wstring::npos && name.find(L"cygwin-") == std::wstring::npos) || name.find(L"-pty") == std::wstring::npos) + { + return false; + } + + return true; + } + +#endif + + inline bool isTerminal(const std::streambuf* osbuf) noexcept + { + using std::cerr; + using std::clog; + using std::cout; +#if defined(OS_LINUX) || defined(OS_MAC) + if (osbuf == cout.rdbuf()) + { + static const bool cout_term = isatty(fileno(stdout)) != 0; + return cout_term; + } + else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) + { + static const bool cerr_term = isatty(fileno(stderr)) != 0; + return cerr_term; + } +#elif defined(OS_WIN) + if (osbuf == cout.rdbuf()) + { + static const bool cout_term = (_isatty(_fileno(stdout)) || isMsysPty(_fileno(stdout))); + return cout_term; + } + else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) + { + static const bool cerr_term = (_isatty(_fileno(stderr)) || isMsysPty(_fileno(stderr))); + return cerr_term; + } +#endif + return false; + } + + template + using enableStd = typename std::enable_if< + std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, + std::ostream&>::type; + +#ifdef OS_WIN + + struct SGR + { // Select Graphic Rendition parameters for Windows console + BYTE fgColor; // foreground color (0-15) lower 3 rgb bits + intense bit + BYTE bgColor; // background color (0-15) lower 3 rgb bits + intense bit + BYTE bold; // emulated as FOREGROUND_INTENSITY bit + BYTE underline; // emulated as BACKGROUND_INTENSITY bit + BOOLEAN inverse; // swap foreground/bold & background/underline + BOOLEAN conceal; // set foreground/bold to background/underline + }; + + enum class AttrColor : BYTE + { // Color attributes for console screen buffer + black = 0, + red = 4, + green = 2, + yellow = 6, + blue = 1, + magenta = 5, + cyan = 3, + gray = 7 + }; + + inline HANDLE getConsoleHandle(const std::streambuf* osbuf) noexcept + { + if (osbuf == std::cout.rdbuf()) + { + static const HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); + return hStdout; + } + else if (osbuf == std::cerr.rdbuf() || osbuf == std::clog.rdbuf()) + { + static const HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); + return hStderr; + } + return INVALID_HANDLE_VALUE; + } + + inline bool setWinTermAnsiColors(const std::streambuf* osbuf) noexcept + { + HANDLE h = getConsoleHandle(osbuf); + if (h == INVALID_HANDLE_VALUE) + { + return false; + } + DWORD dwMode = 0; + if (!GetConsoleMode(h, &dwMode)) + { + return false; + } + dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; + if (!SetConsoleMode(h, dwMode)) + { + return false; + } + return true; + } + + inline bool supportsAnsi(const std::streambuf* osbuf) noexcept + { + using std::cerr; + using std::clog; + using std::cout; + if (osbuf == cout.rdbuf()) + { + static const bool cout_ansi = (isMsysPty(_fileno(stdout)) || setWinTermAnsiColors(osbuf)); + return cout_ansi; + } + else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) + { + static const bool cerr_ansi = (isMsysPty(_fileno(stderr)) || setWinTermAnsiColors(osbuf)); + return cerr_ansi; + } + return false; + } + + inline const SGR& defaultState() noexcept + { + static const SGR defaultSgr = []() -> SGR + { + CONSOLE_SCREEN_BUFFER_INFO info; + WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; + if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), + &info) || + GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), + &info)) + { + attrib = info.wAttributes; + } + SGR sgr = { 0, 0, 0, 0, FALSE, FALSE }; + sgr.fgColor = attrib & 0x0F; + sgr.bgColor = (attrib & 0xF0) >> 4; + return sgr; + }(); + return defaultSgr; + } + + inline BYTE ansi2attr(BYTE rgb) noexcept + { + static const AttrColor rev[8] = { AttrColor::black, AttrColor::red, AttrColor::green, + AttrColor::yellow, AttrColor::blue, AttrColor::magenta, + AttrColor::cyan, AttrColor::gray }; + return static_cast(rev[rgb]); + } + + inline void setWinSGR(rang::bg col, SGR& state) noexcept + { + if (col != rang::bg::reset) + { + state.bgColor = ansi2attr(static_cast(col) - 40); + } + else + { + state.bgColor = defaultState().bgColor; + } + } + + inline void setWinSGR(rang::fg col, SGR& state) noexcept + { + if (col != rang::fg::reset) + { + state.fgColor = ansi2attr(static_cast(col) - 30); + } + else + { + state.fgColor = defaultState().fgColor; + } + } + + inline void setWinSGR(rang::bgB col, SGR& state) noexcept + { + state.bgColor = (BACKGROUND_INTENSITY >> 4) | ansi2attr(static_cast(col) - 100); + } + + inline void setWinSGR(rang::fgB col, SGR& state) noexcept + { + state.fgColor = FOREGROUND_INTENSITY | ansi2attr(static_cast(col) - 90); + } + + inline void setWinSGR(rang::style style, SGR& state) noexcept + { + switch (style) + { + case rang::style::reset: + state = defaultState(); + break; + case rang::style::bold: + state.bold = FOREGROUND_INTENSITY; + break; + case rang::style::underline: + case rang::style::blink: + state.underline = BACKGROUND_INTENSITY; + break; + case rang::style::reversed: + state.inverse = TRUE; + break; + case rang::style::conceal: + state.conceal = TRUE; + break; + default: + break; + } + } + + inline SGR& current_state() noexcept + { + static SGR state = defaultState(); + return state; + } + + inline WORD SGR2Attr(const SGR& state) noexcept + { + WORD attrib = 0; + if (state.conceal) + { + if (state.inverse) + { + attrib = (state.fgColor << 4) | state.fgColor; + if (state.bold) + attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } + else + { + attrib = (state.bgColor << 4) | state.bgColor; + if (state.underline) + attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; + } + } + else if (state.inverse) + { + attrib = (state.fgColor << 4) | state.bgColor; + if (state.bold) + attrib |= BACKGROUND_INTENSITY; + if (state.underline) + attrib |= FOREGROUND_INTENSITY; + } + else + { + attrib = state.fgColor | (state.bgColor << 4) | state.bold | state.underline; + } + return attrib; + } + + template + inline void setWinColorAnsi(std::ostream& os, T const value) + { + os << "\033[" << static_cast(value) << "m"; + } + + template + inline void setWinColorNative(std::ostream& os, T const value) + { + const HANDLE h = getConsoleHandle(os.rdbuf()); + if (h != INVALID_HANDLE_VALUE) + { + setWinSGR(value, current_state()); + // Out all buffered text to console with previous settings: + os.flush(); + SetConsoleTextAttribute(h, SGR2Attr(current_state())); + } + } + + template + inline enableStd setColor(std::ostream& os, T const value) + { + if (winTermMode() == winTerm::Auto) + { + if (supportsAnsi(os.rdbuf())) + { + setWinColorAnsi(os, value); + } + else + { + setWinColorNative(os, value); + } + } + else if (winTermMode() == winTerm::Ansi) + { + setWinColorAnsi(os, value); + } + else + { + setWinColorNative(os, value); + } + return os; + } +#else + template + inline enableStd setColor(std::ostream& os, T const value) + { + return os << "\033[" << static_cast(value) << "m"; + } +#endif + } // namespace rang_implementation + + template + inline rang_implementation::enableStd operator<<(std::ostream& os, + const T value) + { + const control option = rang_implementation::controlMode(); + switch (option) + { + case control::Auto: + return rang_implementation::supportsColor() && rang_implementation::isTerminal(os.rdbuf()) + ? rang_implementation::setColor(os, value) + : os; + case control::Force: + return rang_implementation::setColor(os, value); + default: + return os; + } + } + + inline void setWinTermMode(const rang::winTerm value) noexcept + { + rang_implementation::winTermMode() = value; + } + + inline void setControlMode(const control value) noexcept + { + rang_implementation::controlMode() = value; + } + +} // namespace rang + +#undef OS_LINUX +#undef OS_WIN +#undef OS_MAC + +#endif /* ifndef RANG_DOT_HPP */ diff --git a/external/cucumber/gherkin/CMakeLists.txt b/external/cucumber/gherkin/CMakeLists.txt index fbaf6e59..10c9a94f 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 "7cd0304c39a51ac139a22264ebe2a7ce6c68819d" + GIT_TAG "v37.0.0" ) FetchContent_MakeAvailable(cucumber_gherkin) diff --git a/external/cucumber/messages/CMakeLists.txt b/external/cucumber/messages/CMakeLists.txt index 4b609947..b645a475 100644 --- a/external/cucumber/messages/CMakeLists.txt +++ b/external/cucumber/messages/CMakeLists.txt @@ -1,6 +1,6 @@ FetchContent_Declare(cucumber_messages GIT_REPOSITORY https://github.com/cucumber/messages.git - GIT_TAG "v26.0.1" + GIT_TAG "v31.0.0" OVERRIDE_FIND_PACKAGE ) diff --git a/external/jupyter-xeus/CMakeLists.txt b/external/jupyter-xeus/CMakeLists.txt new file mode 100644 index 00000000..e886ef01 --- /dev/null +++ b/external/jupyter-xeus/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(cpp-terminal) diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt new file mode 100644 index 00000000..88c3701e --- /dev/null +++ b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt @@ -0,0 +1,14 @@ +FetchContent_Declare( + cpp-terminal + GIT_REPOSITORY https://github.com/jupyter-xeus/cpp-terminal + GIT_TAG 48ae2f284084850901c45b6c10a9d68949c1b272 # unreleased main +) + +set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) + +add_compile_options( + -Wno-unused-variable + -Wno-unused-but-set-variable +) + +FetchContent_MakeAvailable(cpp-terminal) From e8e9362170c47d5882955fe274ef680382e37b3c Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 15:35:08 +0000 Subject: [PATCH 002/196] enable ccache --- CMakePresets.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/CMakePresets.json b/CMakePresets.json index 2eb2c925..2fb21843 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -13,7 +13,9 @@ "installDir": "${sourceDir}/.install/${presetName}", "cacheVariables": { "CMAKE_EXPORT_COMPILE_COMMANDS": "On", - "CMAKE_CONFIGURATION_TYPES": "Debug;RelWithDebInfo;Release" + "CMAKE_CONFIGURATION_TYPES": "Debug;RelWithDebInfo;Release", + "CMAKE_C_COMPILER_LAUNCHER": "ccache", + "CMAKE_CXX_COMPILER_LAUNCHER": "ccache" } }, { From 792332edc6fd30463e65782d035480dfd3eaa238 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 15:35:18 +0000 Subject: [PATCH 003/196] disable the build of shared libs --- CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 190f3f9d..ac5a8d8e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,6 +25,8 @@ option(CCR_FETCH_DEPS "Fetch dependencies via FetchContent." On ) option(CCR_BUILD_TESTS "Enable build of the tests" ${CCR_DEFAULTOPT}) option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements" Off) +set(BUILD_SHARED_LIBS OFF) + add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) @@ -71,3 +73,4 @@ else() endif() add_subdirectory(cucumber_cpp) +add_subdirectory(compatibility) From e8c96aa1a05dd7bca26bcd19b0a692bacf0bfa04 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 15:39:10 +0000 Subject: [PATCH 004/196] disable position independent code generation for jbeder/yaml-cpp --- external/jbeder/yaml-cpp/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/external/jbeder/yaml-cpp/CMakeLists.txt b/external/jbeder/yaml-cpp/CMakeLists.txt index 65d76cae..221af32e 100644 --- a/external/jbeder/yaml-cpp/CMakeLists.txt +++ b/external/jbeder/yaml-cpp/CMakeLists.txt @@ -3,4 +3,5 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git GIT_TAG 2f86d13775d119edbb69af52e5f566fd65c6953b # Unreleased ) +set(YAML_ENABLE_PIC OFF) FetchContent_MakeAvailable(yaml-cpp) From 48d99c6280481e62550245c0029f49809fada521 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 15:39:38 +0000 Subject: [PATCH 005/196] add additional launch tags --- .vscode/launch.json | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index a3b4b9e1..dc2737c0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -15,7 +15,11 @@ "@result:UNDEFINED", "@result:FAILED", "@result:OK", - "@fail_feature" + "@fail_feature", + "@ex:2", + "@substep", + "@table_argument", + "@thishasarule" ] }, { @@ -52,7 +56,12 @@ ], "stopAtEntry": false, "cwd": "${workspaceFolder}", - "environment": [], + "environment": [ + { + "name": "ASAN_OPTIONS", + "value": "detect_leaks=0" + } + ], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ From 1ae94680d7a4141f066c4f7a814dfcb639337c4b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 15:40:05 +0000 Subject: [PATCH 006/196] add first version (empty, minimal and data-tables are enabled) of the compatibility kit --- compatibility/CMakeLists.txt | 40 +++ compatibility/ambiguous/ambiguous.feature | 6 + compatibility/ambiguous/ambiguous.ndjson | 13 + compatibility/ambiguous/ambiguous.ts | 9 + compatibility/attachments/attachments.feature | 36 +++ compatibility/attachments/attachments.ndjson | 61 ++++ compatibility/attachments/attachments.ts | 49 ++++ compatibility/attachments/cucumber.jpeg | Bin 0 -> 1444 bytes compatibility/attachments/cucumber.png | Bin 0 -> 1739 bytes compatibility/attachments/document.pdf | Bin 0 -> 10061 bytes compatibility/backgrounds/backgrounds.feature | 17 ++ compatibility/backgrounds/backgrounds.ndjson | 36 +++ compatibility/backgrounds/backgrounds.ts | 13 + compatibility/cdata/cdata.feature | 5 + compatibility/cdata/cdata.ndjson | 12 + compatibility/cdata/cdata.ts | 5 + compatibility/compatibility.cpp | 265 ++++++++++++++++++ compatibility/data-tables/data-tables.cpp | 52 ++++ compatibility/data-tables/data-tables.feature | 13 + compatibility/data-tables/data-tables.ndjson | 15 + compatibility/data-tables/data-tables.ts | 10 + compatibility/data-tables/out.ndjson | 14 + compatibility/doc-strings/doc-strings.feature | 31 ++ compatibility/doc-strings/doc-strings.ndjson | 24 ++ compatibility/doc-strings/doc-strings.ts | 5 + compatibility/empty/empty.cpp | 6 + compatibility/empty/empty.feature | 7 + compatibility/empty/empty.ndjson | 9 + compatibility/empty/out.ndjson | 8 + .../examples-tables-attachment/cucumber.jpeg | Bin 0 -> 1444 bytes .../examples-tables-attachment/cucumber.png | Bin 0 -> 1739 bytes .../examples-tables-attachment.feature | 10 + .../examples-tables-attachment.ndjson | 21 ++ .../examples-tables-attachment.ts | 10 + .../examples-tables-undefined.ndjson | 41 +++ .../examples-tables-undefined.ts | 14 + .../examples-undefined.feature | 17 ++ .../examples-tables/examples-tables.feature | 37 +++ .../examples-tables/examples-tables.ndjson | 80 ++++++ .../examples-tables/examples-tables.ts | 23 ++ .../global-hooks-afterall-error.feature | 6 + .../global-hooks-afterall-error.ndjson | 27 ++ .../global-hooks-afterall-error.ts | 25 ++ .../global-hooks-attachments.feature | 5 + .../global-hooks-attachments.ndjson | 20 ++ .../global-hooks-attachments.ts | 13 + .../global-hooks-beforeall-error.feature | 6 + .../global-hooks-beforeall-error.ndjson | 22 ++ .../global-hooks-beforeall-error.ts | 25 ++ .../global-hooks/global-hooks.feature | 10 + .../global-hooks/global-hooks.ndjson | 31 ++ compatibility/global-hooks/global-hooks.ts | 25 ++ compatibility/hooks-attachment/.gitattributes | 4 + compatibility/hooks-attachment/cucumber.svg | 7 + .../hooks-attachment/hooks-attachment.feature | 7 + .../hooks-attachment/hooks-attachment.ndjson | 20 ++ .../hooks-attachment/hooks-attachment.ts | 20 ++ .../hooks-conditional.feature | 16 ++ .../hooks-conditional.ndjson | 36 +++ .../hooks-conditional/hooks-conditional.ts | 21 ++ compatibility/hooks-named/hooks-named.feature | 8 + compatibility/hooks-named/hooks-named.ndjson | 18 ++ compatibility/hooks-named/hooks-named.ts | 13 + .../hooks-skipped/hooks-skipped.feature | 26 ++ .../hooks-skipped/hooks-skipped.ndjson | 59 ++++ compatibility/hooks-skipped/hooks-skipped.ts | 33 +++ .../hooks-undefined/hooks-undefined.feature | 5 + .../hooks-undefined/hooks-undefined.ndjson | 18 ++ .../hooks-undefined/hooks-undefined.ts | 9 + compatibility/hooks/hooks.feature | 8 + compatibility/hooks/hooks.ndjson | 29 ++ compatibility/hooks/hooks.ts | 17 ++ compatibility/markdown/markdown.feature.md | 46 +++ compatibility/markdown/markdown.ndjson | 35 +++ compatibility/markdown/markdown.ts | 25 ++ compatibility/minimal/minimal.cpp | 6 + compatibility/minimal/minimal.feature | 10 + compatibility/minimal/minimal.ndjson | 12 + compatibility/minimal/minimal.ts | 5 + compatibility/minimal/out.ndjson | 11 + .../multiple-features-reversed-1.feature | 10 + .../multiple-features-reversed-2.feature | 10 + .../multiple-features-reversed-3.feature | 10 + .../multiple-features-reversed.arguments.txt | 1 + .../multiple-features-reversed.ndjson | 64 +++++ .../multiple-features-reversed.ts | 5 + .../multiple-features-1.feature | 10 + .../multiple-features-2.feature | 10 + .../multiple-features-3.feature | 10 + .../multiple-features.ndjson | 64 +++++ .../multiple-features/multiple-features.ts | 5 + .../parameter-types/parameter-types.feature | 11 + .../parameter-types/parameter-types.ndjson | 13 + .../parameter-types/parameter-types.ts | 19 ++ compatibility/pending/pending.feature | 18 ++ compatibility/pending/pending.ndjson | 30 ++ compatibility/pending/pending.ts | 13 + .../regular-expression.feature | 9 + .../regular-expression.ndjson | 16 ++ .../regular-expression/regular-expression.ts | 6 + .../retry-ambiguous.arguments.txt | 1 + .../retry-ambiguous/retry-ambiguous.feature | 3 + .../retry-ambiguous/retry-ambiguous.ndjson | 13 + .../retry-ambiguous/retry-ambiguous.ts | 9 + .../retry-pending/retry-pending.arguments.txt | 1 + .../retry-pending/retry-pending.feature | 3 + .../retry-pending/retry-pending.ndjson | 12 + compatibility/retry-pending/retry-pending.ts | 5 + .../retry-undefined.arguments.txt | 1 + .../retry-undefined/retry-undefined.feature | 3 + .../retry-undefined/retry-undefined.ndjson | 12 + .../retry-undefined/retry-undefined.ts | 1 + compatibility/retry/retry.arguments.txt | 1 + compatibility/retry/retry.feature | 18 ++ compatibility/retry/retry.ndjson | 53 ++++ compatibility/retry/retry.ts | 25 ++ .../rules-backgrounds.feature | 23 ++ .../rules-backgrounds.ndjson | 44 +++ .../rules-backgrounds/rules-backgrounds.ts | 13 + compatibility/rules/rules.feature | 29 ++ compatibility/rules/rules.ndjson | 47 ++++ compatibility/rules/rules.ts | 28 ++ compatibility/skipped/skipped.feature | 15 + compatibility/skipped/skipped.ndjson | 24 ++ compatibility/skipped/skipped.ts | 13 + .../stack-traces/stack-traces.feature | 10 + .../stack-traces/stack-traces.ndjson | 12 + compatibility/stack-traces/stack-traces.ts | 5 + .../test-run-exception.arguments.txt | 1 + .../test-run-exception.feature | 8 + .../test-run-exception.ndjson | 7 + .../test-run-exception/test-run-exception.ts | 3 + compatibility/undefined/undefined.feature | 20 ++ compatibility/undefined/undefined.ndjson | 39 +++ compatibility/undefined/undefined.ts | 9 + .../unknown-parameter-type.feature | 7 + .../unknown-parameter-type.ndjson | 13 + .../unknown-parameter-type.ts | 6 + .../unused-steps/unused-steps.feature | 6 + .../unused-steps/unused-steps.ndjson | 13 + compatibility/unused-steps/unused-steps.ts | 9 + 141 files changed, 2652 insertions(+) create mode 100644 compatibility/CMakeLists.txt create mode 100644 compatibility/ambiguous/ambiguous.feature create mode 100644 compatibility/ambiguous/ambiguous.ndjson create mode 100644 compatibility/ambiguous/ambiguous.ts create mode 100644 compatibility/attachments/attachments.feature create mode 100644 compatibility/attachments/attachments.ndjson create mode 100644 compatibility/attachments/attachments.ts create mode 100644 compatibility/attachments/cucumber.jpeg create mode 100644 compatibility/attachments/cucumber.png create mode 100644 compatibility/attachments/document.pdf create mode 100644 compatibility/backgrounds/backgrounds.feature create mode 100644 compatibility/backgrounds/backgrounds.ndjson create mode 100644 compatibility/backgrounds/backgrounds.ts create mode 100644 compatibility/cdata/cdata.feature create mode 100644 compatibility/cdata/cdata.ndjson create mode 100644 compatibility/cdata/cdata.ts create mode 100644 compatibility/compatibility.cpp create mode 100644 compatibility/data-tables/data-tables.cpp create mode 100644 compatibility/data-tables/data-tables.feature create mode 100644 compatibility/data-tables/data-tables.ndjson create mode 100644 compatibility/data-tables/data-tables.ts create mode 100644 compatibility/data-tables/out.ndjson create mode 100644 compatibility/doc-strings/doc-strings.feature create mode 100644 compatibility/doc-strings/doc-strings.ndjson create mode 100644 compatibility/doc-strings/doc-strings.ts create mode 100644 compatibility/empty/empty.cpp create mode 100644 compatibility/empty/empty.feature create mode 100644 compatibility/empty/empty.ndjson create mode 100644 compatibility/empty/out.ndjson create mode 100644 compatibility/examples-tables-attachment/cucumber.jpeg create mode 100644 compatibility/examples-tables-attachment/cucumber.png create mode 100644 compatibility/examples-tables-attachment/examples-tables-attachment.feature create mode 100644 compatibility/examples-tables-attachment/examples-tables-attachment.ndjson create mode 100644 compatibility/examples-tables-attachment/examples-tables-attachment.ts create mode 100644 compatibility/examples-tables-undefined/examples-tables-undefined.ndjson create mode 100644 compatibility/examples-tables-undefined/examples-tables-undefined.ts create mode 100644 compatibility/examples-tables-undefined/examples-undefined.feature create mode 100644 compatibility/examples-tables/examples-tables.feature create mode 100644 compatibility/examples-tables/examples-tables.ndjson create mode 100644 compatibility/examples-tables/examples-tables.ts create mode 100644 compatibility/global-hooks-afterall-error/global-hooks-afterall-error.feature create mode 100644 compatibility/global-hooks-afterall-error/global-hooks-afterall-error.ndjson create mode 100644 compatibility/global-hooks-afterall-error/global-hooks-afterall-error.ts create mode 100644 compatibility/global-hooks-attachments/global-hooks-attachments.feature create mode 100644 compatibility/global-hooks-attachments/global-hooks-attachments.ndjson create mode 100644 compatibility/global-hooks-attachments/global-hooks-attachments.ts create mode 100644 compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.feature create mode 100644 compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.ndjson create mode 100644 compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.ts create mode 100644 compatibility/global-hooks/global-hooks.feature create mode 100644 compatibility/global-hooks/global-hooks.ndjson create mode 100644 compatibility/global-hooks/global-hooks.ts create mode 100644 compatibility/hooks-attachment/.gitattributes create mode 100644 compatibility/hooks-attachment/cucumber.svg create mode 100644 compatibility/hooks-attachment/hooks-attachment.feature create mode 100644 compatibility/hooks-attachment/hooks-attachment.ndjson create mode 100644 compatibility/hooks-attachment/hooks-attachment.ts create mode 100644 compatibility/hooks-conditional/hooks-conditional.feature create mode 100644 compatibility/hooks-conditional/hooks-conditional.ndjson create mode 100644 compatibility/hooks-conditional/hooks-conditional.ts create mode 100644 compatibility/hooks-named/hooks-named.feature create mode 100644 compatibility/hooks-named/hooks-named.ndjson create mode 100644 compatibility/hooks-named/hooks-named.ts create mode 100644 compatibility/hooks-skipped/hooks-skipped.feature create mode 100644 compatibility/hooks-skipped/hooks-skipped.ndjson create mode 100644 compatibility/hooks-skipped/hooks-skipped.ts create mode 100644 compatibility/hooks-undefined/hooks-undefined.feature create mode 100644 compatibility/hooks-undefined/hooks-undefined.ndjson create mode 100644 compatibility/hooks-undefined/hooks-undefined.ts create mode 100644 compatibility/hooks/hooks.feature create mode 100644 compatibility/hooks/hooks.ndjson create mode 100644 compatibility/hooks/hooks.ts create mode 100644 compatibility/markdown/markdown.feature.md create mode 100644 compatibility/markdown/markdown.ndjson create mode 100644 compatibility/markdown/markdown.ts create mode 100644 compatibility/minimal/minimal.cpp create mode 100644 compatibility/minimal/minimal.feature create mode 100644 compatibility/minimal/minimal.ndjson create mode 100644 compatibility/minimal/minimal.ts create mode 100644 compatibility/minimal/out.ndjson create mode 100644 compatibility/multiple-features-reversed/multiple-features-reversed-1.feature create mode 100644 compatibility/multiple-features-reversed/multiple-features-reversed-2.feature create mode 100644 compatibility/multiple-features-reversed/multiple-features-reversed-3.feature create mode 100644 compatibility/multiple-features-reversed/multiple-features-reversed.arguments.txt create mode 100644 compatibility/multiple-features-reversed/multiple-features-reversed.ndjson create mode 100644 compatibility/multiple-features-reversed/multiple-features-reversed.ts create mode 100644 compatibility/multiple-features/multiple-features-1.feature create mode 100644 compatibility/multiple-features/multiple-features-2.feature create mode 100644 compatibility/multiple-features/multiple-features-3.feature create mode 100644 compatibility/multiple-features/multiple-features.ndjson create mode 100644 compatibility/multiple-features/multiple-features.ts create mode 100644 compatibility/parameter-types/parameter-types.feature create mode 100644 compatibility/parameter-types/parameter-types.ndjson create mode 100644 compatibility/parameter-types/parameter-types.ts create mode 100644 compatibility/pending/pending.feature create mode 100644 compatibility/pending/pending.ndjson create mode 100644 compatibility/pending/pending.ts create mode 100644 compatibility/regular-expression/regular-expression.feature create mode 100644 compatibility/regular-expression/regular-expression.ndjson create mode 100644 compatibility/regular-expression/regular-expression.ts create mode 100644 compatibility/retry-ambiguous/retry-ambiguous.arguments.txt create mode 100644 compatibility/retry-ambiguous/retry-ambiguous.feature create mode 100644 compatibility/retry-ambiguous/retry-ambiguous.ndjson create mode 100644 compatibility/retry-ambiguous/retry-ambiguous.ts create mode 100644 compatibility/retry-pending/retry-pending.arguments.txt create mode 100644 compatibility/retry-pending/retry-pending.feature create mode 100644 compatibility/retry-pending/retry-pending.ndjson create mode 100644 compatibility/retry-pending/retry-pending.ts create mode 100644 compatibility/retry-undefined/retry-undefined.arguments.txt create mode 100644 compatibility/retry-undefined/retry-undefined.feature create mode 100644 compatibility/retry-undefined/retry-undefined.ndjson create mode 100644 compatibility/retry-undefined/retry-undefined.ts create mode 100644 compatibility/retry/retry.arguments.txt create mode 100644 compatibility/retry/retry.feature create mode 100644 compatibility/retry/retry.ndjson create mode 100644 compatibility/retry/retry.ts create mode 100644 compatibility/rules-backgrounds/rules-backgrounds.feature create mode 100644 compatibility/rules-backgrounds/rules-backgrounds.ndjson create mode 100644 compatibility/rules-backgrounds/rules-backgrounds.ts create mode 100644 compatibility/rules/rules.feature create mode 100644 compatibility/rules/rules.ndjson create mode 100644 compatibility/rules/rules.ts create mode 100644 compatibility/skipped/skipped.feature create mode 100644 compatibility/skipped/skipped.ndjson create mode 100644 compatibility/skipped/skipped.ts create mode 100644 compatibility/stack-traces/stack-traces.feature create mode 100644 compatibility/stack-traces/stack-traces.ndjson create mode 100644 compatibility/stack-traces/stack-traces.ts create mode 100644 compatibility/test-run-exception/test-run-exception.arguments.txt create mode 100644 compatibility/test-run-exception/test-run-exception.feature create mode 100644 compatibility/test-run-exception/test-run-exception.ndjson create mode 100644 compatibility/test-run-exception/test-run-exception.ts create mode 100644 compatibility/undefined/undefined.feature create mode 100644 compatibility/undefined/undefined.ndjson create mode 100644 compatibility/undefined/undefined.ts create mode 100644 compatibility/unknown-parameter-type/unknown-parameter-type.feature create mode 100644 compatibility/unknown-parameter-type/unknown-parameter-type.ndjson create mode 100644 compatibility/unknown-parameter-type/unknown-parameter-type.ts create mode 100644 compatibility/unused-steps/unused-steps.feature create mode 100644 compatibility/unused-steps/unused-steps.ndjson create mode 100644 compatibility/unused-steps/unused-steps.ts diff --git a/compatibility/CMakeLists.txt b/compatibility/CMakeLists.txt new file mode 100644 index 00000000..132dea89 --- /dev/null +++ b/compatibility/CMakeLists.txt @@ -0,0 +1,40 @@ +function(add_compatibility_kit name) + set(libname compatibility.${name}.test) + add_executable(${libname}) + + if(CCR_BUILD_TESTS) + add_test(NAME ${libname} COMMAND ${libname}) + endif() + + target_link_libraries(${libname} PRIVATE + cucumber_cpp + gtest_main + ) + + target_include_directories(${libname} PUBLIC + $ + ) + + target_sources(${libname} PRIVATE + compatibility.cpp + ${name}/${name}.cpp + ) + + string(REPLACE "-" "_" KITNAME ${name}) + + target_compile_definitions(${libname} PRIVATE + KIT_NAME=${KITNAME} + KIT_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}/${name}" + KIT_NDJSON_FILE="${CMAKE_CURRENT_SOURCE_DIR}/${name}/${name}.ndjson" + ) +endfunction() + +file(GLOB kits RELATIVE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/*) +foreach(kit ${kits}) + if (IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/${kit}) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${kit}/${kit}.cpp) + message(STATUS "Adding compatibility kit: ${kit}") + add_compatibility_kit(${kit}) + endif() + endif() +endforeach() diff --git a/compatibility/ambiguous/ambiguous.feature b/compatibility/ambiguous/ambiguous.feature new file mode 100644 index 00000000..f021e17c --- /dev/null +++ b/compatibility/ambiguous/ambiguous.feature @@ -0,0 +1,6 @@ +Feature: Ambiguous steps + Multiple step definitions that match a pickle step result in an AMBIGUOUS status, since Cucumnber cannot determine + which one to execute. + + Scenario: Multiple step definitions for a step + Given a step with multiple definitions diff --git a/compatibility/ambiguous/ambiguous.ndjson b/compatibility/ambiguous/ambiguous.ndjson new file mode 100644 index 00000000..66be9159 --- /dev/null +++ b/compatibility/ambiguous/ambiguous.ndjson @@ -0,0 +1,13 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Ambiguous steps\n Multiple step definitions that match a pickle step result in an AMBIGUOUS status, since Cucumnber cannot determine\n which one to execute.\n\n Scenario: Multiple step definitions for a step\n Given a step with multiple definitions\n","uri":"samples/ambiguous/ambiguous.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Ambiguous steps","description":" Multiple step definitions that match a pickle step result in an AMBIGUOUS status, since Cucumnber cannot determine\n which one to execute.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"Multiple step definitions for a step","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step with multiple definitions"}],"examples":[]}}]},"comments":[],"uri":"samples/ambiguous/ambiguous.feature"}} +{"pickle":{"id":"3","uri":"samples/ambiguous/ambiguous.feature","location":{"line":5,"column":3},"astNodeIds":["1"],"tags":[],"name":"Multiple step definitions for a step","language":"en","steps":[{"id":"2","text":"a step with multiple definitions","type":"Context","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"REGULAR_EXPRESSION","source":"^a (.*?) with (.*?)$"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"5","pattern":{"type":"REGULAR_EXPRESSION","source":"^a step with (.*)$"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":7}}}} +{"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["4","5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":2,"value":"step","children":[]}},{"group":{"start":12,"value":"multiple definitions","children":[]}}]},{"stepMatchArguments":[{"group":{"start":12,"value":"multiple definitions","children":[]},"parameterTypeName":""}]}]}],"testRunStartedId":"6"}} +{"testCaseStarted":{"id":"9","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"9","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"9","testStepId":"8","testStepResult":{"status":"AMBIGUOUS","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"9","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"6","timestamp":{"seconds":0,"nanos":5000000},"success":false}} diff --git a/compatibility/ambiguous/ambiguous.ts b/compatibility/ambiguous/ambiguous.ts new file mode 100644 index 00000000..d80f1727 --- /dev/null +++ b/compatibility/ambiguous/ambiguous.ts @@ -0,0 +1,9 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given(/^a (.*?) with (.*?)$/, function () { + // first one +}) + +Given(/^a step with (.*)$/, function () { + // second one +}) diff --git a/compatibility/attachments/attachments.feature b/compatibility/attachments/attachments.feature new file mode 100644 index 00000000..aa44b74d --- /dev/null +++ b/compatibility/attachments/attachments.feature @@ -0,0 +1,36 @@ +Feature: Attachments + It is sometimes useful to take a screenshot while a scenario runs or capture some logs. + + Cucumber lets you `attach` arbitrary files during execution, and you can + specify a content type for the contents. + + Formatters can then render these attachments in reports. + + Attachments must have a body and a content type. + + Scenario: Strings can be attached with a media type + Beware that some formatters such as @cucumber/react use the media type + to determine how to display an attachment. + + When the string "hello" is attached as "application/octet-stream" + + Scenario: Log text + When the string "hello" is logged + + Scenario: Log ANSI coloured text + When text with ANSI escapes is logged + + Scenario: Log JSON + When the following string is attached as "application/json": + ``` + {"message": "The big question", "foo": "bar"} + ``` + + Scenario: Byte arrays are base64-encoded regardless of media type + When an array with 10 bytes is attached as "text/plain" + + Scenario: Attaching PDFs with a different filename + When a PDF document is attached and renamed + + Scenario: Attaching URIs + When a link to "https://cucumber.io" is attached diff --git a/compatibility/attachments/attachments.ndjson b/compatibility/attachments/attachments.ndjson new file mode 100644 index 00000000..7973606e --- /dev/null +++ b/compatibility/attachments/attachments.ndjson @@ -0,0 +1,61 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Attachments\n It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type.\n\n Scenario: Strings can be attached with a media type\n Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.\n\n When the string \"hello\" is attached as \"application/octet-stream\"\n\n Scenario: Log text\n When the string \"hello\" is logged\n\n Scenario: Log ANSI coloured text\n When text with ANSI escapes is logged\n\n Scenario: Log JSON\n When the following string is attached as \"application/json\":\n ```\n {\"message\": \"The big question\", \"foo\": \"bar\"}\n ```\n\n Scenario: Byte arrays are base64-encoded regardless of media type\n When an array with 10 bytes is attached as \"text/plain\"\n\n Scenario: Attaching PDFs with a different filename\n When a PDF document is attached and renamed\n\n Scenario: Attaching URIs\n When a link to \"https://cucumber.io\" is attached\n","uri":"samples/attachments/attachments.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Attachments","description":" It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Cucumber lets you `attach` arbitrary files during execution, and you can\n specify a content type for the contents.\n\n Formatters can then render these attachments in reports.\n\n Attachments must have a body and a content type.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Strings can be attached with a media type","description":" Beware that some formatters such as @cucumber/react use the media type\n to determine how to display an attachment.","steps":[{"id":"0","location":{"line":15,"column":5},"keyword":"When ","keywordType":"Action","text":"the string \"hello\" is attached as \"application/octet-stream\""}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":17,"column":3},"keyword":"Scenario","name":"Log text","description":"","steps":[{"id":"2","location":{"line":18,"column":5},"keyword":"When ","keywordType":"Action","text":"the string \"hello\" is logged"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":20,"column":3},"keyword":"Scenario","name":"Log ANSI coloured text","description":"","steps":[{"id":"4","location":{"line":21,"column":5},"keyword":"When ","keywordType":"Action","text":"text with ANSI escapes is logged"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":23,"column":3},"keyword":"Scenario","name":"Log JSON","description":"","steps":[{"id":"6","location":{"line":24,"column":6},"keyword":"When ","keywordType":"Action","text":"the following string is attached as \"application/json\":","docString":{"location":{"line":25,"column":8},"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}","delimiter":"```"}}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":29,"column":3},"keyword":"Scenario","name":"Byte arrays are base64-encoded regardless of media type","description":"","steps":[{"id":"8","location":{"line":30,"column":5},"keyword":"When ","keywordType":"Action","text":"an array with 10 bytes is attached as \"text/plain\""}],"examples":[]}},{"scenario":{"id":"11","tags":[],"location":{"line":32,"column":3},"keyword":"Scenario","name":"Attaching PDFs with a different filename","description":"","steps":[{"id":"10","location":{"line":33,"column":5},"keyword":"When ","keywordType":"Action","text":"a PDF document is attached and renamed"}],"examples":[]}},{"scenario":{"id":"13","tags":[],"location":{"line":35,"column":3},"keyword":"Scenario","name":"Attaching URIs","description":"","steps":[{"id":"12","location":{"line":36,"column":5},"keyword":"When ","keywordType":"Action","text":"a link to \"https://cucumber.io\" is attached"}],"examples":[]}}]},"comments":[],"uri":"samples/attachments/attachments.feature"}} +{"pickle":{"id":"15","uri":"samples/attachments/attachments.feature","location":{"line":11,"column":3},"astNodeIds":["1"],"tags":[],"name":"Strings can be attached with a media type","language":"en","steps":[{"id":"14","text":"the string \"hello\" is attached as \"application/octet-stream\"","type":"Action","astNodeIds":["0"]}]}} +{"pickle":{"id":"17","uri":"samples/attachments/attachments.feature","location":{"line":17,"column":3},"astNodeIds":["3"],"tags":[],"name":"Log text","language":"en","steps":[{"id":"16","text":"the string \"hello\" is logged","type":"Action","astNodeIds":["2"]}]}} +{"pickle":{"id":"19","uri":"samples/attachments/attachments.feature","location":{"line":20,"column":3},"astNodeIds":["5"],"tags":[],"name":"Log ANSI coloured text","language":"en","steps":[{"id":"18","text":"text with ANSI escapes is logged","type":"Action","astNodeIds":["4"]}]}} +{"pickle":{"id":"21","uri":"samples/attachments/attachments.feature","location":{"line":23,"column":3},"astNodeIds":["7"],"tags":[],"name":"Log JSON","language":"en","steps":[{"id":"20","text":"the following string is attached as \"application/json\":","type":"Action","argument":{"docString":{"content":"{\"message\": \"The big question\", \"foo\": \"bar\"}"}},"astNodeIds":["6"]}]}} +{"pickle":{"id":"23","uri":"samples/attachments/attachments.feature","location":{"line":29,"column":3},"astNodeIds":["9"],"tags":[],"name":"Byte arrays are base64-encoded regardless of media type","language":"en","steps":[{"id":"22","text":"an array with 10 bytes is attached as \"text/plain\"","type":"Action","astNodeIds":["8"]}]}} +{"pickle":{"id":"25","uri":"samples/attachments/attachments.feature","location":{"line":32,"column":3},"astNodeIds":["11"],"tags":[],"name":"Attaching PDFs with a different filename","language":"en","steps":[{"id":"24","text":"a PDF document is attached and renamed","type":"Action","astNodeIds":["10"]}]}} +{"pickle":{"id":"27","uri":"samples/attachments/attachments.feature","location":{"line":35,"column":3},"astNodeIds":["13"],"tags":[],"name":"Attaching URIs","language":"en","steps":[{"id":"26","text":"a link to \"https://cucumber.io\" is attached","type":"Action","astNodeIds":["12"]}]}} +{"stepDefinition":{"id":"28","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the string {string} is attached as {string}"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"29","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the string {string} is logged"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":11}}}} +{"stepDefinition":{"id":"30","pattern":{"type":"CUCUMBER_EXPRESSION","source":"text with ANSI escapes is logged"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":15}}}} +{"stepDefinition":{"id":"31","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the following string is attached as {string}:"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":21}}}} +{"stepDefinition":{"id":"32","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an array with {int} bytes is attached as {string}"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":28}}}} +{"stepDefinition":{"id":"33","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a PDF document is attached and renamed"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":37}}}} +{"stepDefinition":{"id":"34","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a link to {string} is attached"},"sourceReference":{"uri":"samples/attachments/attachments.ts","location":{"line":47}}}} +{"testRunStarted":{"id":"35","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"36","pickleId":"15","testSteps":[{"id":"37","pickleStepId":"14","stepDefinitionIds":["28"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":11,"value":"\"hello\"","children":[{"start":12,"value":"hello","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"},{"group":{"start":34,"value":"\"application/octet-stream\"","children":[{"start":35,"value":"application/octet-stream","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"38","pickleId":"17","testSteps":[{"id":"39","pickleStepId":"16","stepDefinitionIds":["29"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":11,"value":"\"hello\"","children":[{"start":12,"value":"hello","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"40","pickleId":"19","testSteps":[{"id":"41","pickleStepId":"18","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"42","pickleId":"21","testSteps":[{"id":"43","pickleStepId":"20","stepDefinitionIds":["31"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":36,"value":"\"application/json\"","children":[{"start":37,"value":"application/json","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"44","pickleId":"23","testSteps":[{"id":"45","pickleStepId":"22","stepDefinitionIds":["32"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"10","children":[]},"parameterTypeName":"int"},{"group":{"start":38,"value":"\"text/plain\"","children":[{"start":39,"value":"text/plain","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"46","pickleId":"25","testSteps":[{"id":"47","pickleStepId":"24","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"35"}} +{"testCase":{"id":"48","pickleId":"27","testSteps":[{"id":"49","pickleStepId":"26","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"\"https://cucumber.io\"","children":[{"start":11,"value":"https://cucumber.io","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"35"}} +{"testCaseStarted":{"id":"50","testCaseId":"36","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"37","timestamp":{"seconds":0,"nanos":2000000}}} +{"attachment":{"testCaseStartedId":"50","testStepId":"37","body":"hello","contentEncoding":"IDENTITY","mediaType":"application/octet-stream","timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"37","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":4000000}}} +{"testCaseFinished":{"testCaseStartedId":"50","timestamp":{"seconds":0,"nanos":5000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"51","testCaseId":"38","timestamp":{"seconds":0,"nanos":6000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"39","timestamp":{"seconds":0,"nanos":7000000}}} +{"attachment":{"testCaseStartedId":"51","testStepId":"39","body":"hello","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"39","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testCaseFinished":{"testCaseStartedId":"51","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"52","testCaseId":"40","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"52","testStepId":"41","timestamp":{"seconds":0,"nanos":12000000}}} +{"attachment":{"testCaseStartedId":"52","testStepId":"41","body":"This displays a \u001b[31mr\u001b[0m\u001b[91ma\u001b[0m\u001b[33mi\u001b[0m\u001b[32mn\u001b[0m\u001b[34mb\u001b[0m\u001b[95mo\u001b[0m\u001b[35mw\u001b[0m","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain","timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepFinished":{"testCaseStartedId":"52","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":14000000}}} +{"testCaseFinished":{"testCaseStartedId":"52","timestamp":{"seconds":0,"nanos":15000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"53","testCaseId":"42","timestamp":{"seconds":0,"nanos":16000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"53","testStepId":"43","timestamp":{"seconds":0,"nanos":17000000}}} +{"attachment":{"testCaseStartedId":"53","testStepId":"43","body":"{\"message\": \"The big question\", \"foo\": \"bar\"}","contentEncoding":"IDENTITY","mediaType":"application/json","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"53","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"53","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"54","testCaseId":"44","timestamp":{"seconds":0,"nanos":21000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"54","testStepId":"45","timestamp":{"seconds":0,"nanos":22000000}}} +{"attachment":{"testCaseStartedId":"54","testStepId":"45","body":"AAECAwQFBgcICQ==","contentEncoding":"BASE64","mediaType":"text/plain","timestamp":{"seconds":0,"nanos":23000000}}} +{"testStepFinished":{"testCaseStartedId":"54","testStepId":"45","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":24000000}}} +{"testCaseFinished":{"testCaseStartedId":"54","timestamp":{"seconds":0,"nanos":25000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"55","testCaseId":"46","timestamp":{"seconds":0,"nanos":26000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"47","timestamp":{"seconds":0,"nanos":27000000}}} +{"attachment":{"testCaseStartedId":"55","testStepId":"47","body":"JVBERi0xLjQKJdPr6eEKMSAwIG9iago8PC9UaXRsZSAoVW50aXRsZWQgZG9jdW1lbnQpCi9Qcm9kdWNlciAoU2tpYS9QREYgbTExNiBHb29nbGUgRG9jcyBSZW5kZXJlcik+PgplbmRvYmoKMyAwIG9iago8PC9jYSAxCi9CTSAvTm9ybWFsPj4KZW5kb2JqCjUgMCBvYmoKPDwvRmlsdGVyIC9GbGF0ZURlY29kZQovTGVuZ3RoIDE2Nz4+IHN0cmVhbQp4nF2P0QrCMAxF3/MV+YF1TdM2LYgPgu5Z6R+oGwg+bP4/mK64gU1Jw73cQ0potTrSlrzD+xtmMBJW9feqSFjrNmAblgn6gXH6QPUleyRyjMsTRrj+EcTVqwy7Sspow844FegvivAm1iNYRqB9L+MlJxLOWCqkIzZOhD0nLA88WMtyxPICMexijoE10wyfViMZCkRW0maEuCUSubDrjXQu+osv96M5GgplbmRzdHJlYW0KZW5kb2JqCjIgMCBvYmoKPDwvVHlwZSAvUGFnZQovUmVzb3VyY2VzIDw8L1Byb2NTZXQgWy9QREYgL1RleHQgL0ltYWdlQiAvSW1hZ2VDIC9JbWFnZUldCi9FeHRHU3RhdGUgPDwvRzMgMyAwIFI+PgovRm9udCA8PC9GNCA0IDAgUj4+Pj4KL01lZGlhQm94IFswIDAgNTk2IDg0Ml0KL0NvbnRlbnRzIDUgMCBSCi9TdHJ1Y3RQYXJlbnRzIDAKL1BhcmVudCA2IDAgUj4+CmVuZG9iago2IDAgb2JqCjw8L1R5cGUgL1BhZ2VzCi9Db3VudCAxCi9LaWRzIFsyIDAgUl0+PgplbmRvYmoKNyAwIG9iago8PC9UeXBlIC9DYXRhbG9nCi9QYWdlcyA2IDAgUj4+CmVuZG9iago4IDAgb2JqCjw8L0xlbmd0aDEgMTY5OTYKL0ZpbHRlciAvRmxhdGVEZWNvZGUKL0xlbmd0aCA4MDA5Pj4gc3RyZWFtCnic7XoJeFRF9u+pureXrN0J2TrppG+nkw6kA4EECEtMOhugkT1gwiSSAJGAIEtAQVGaGVCJKI4LDuiI+6CO0lnADi4wMjojLjDquAsIjOLMIOgoruS+X1V3gIj65sv7z3uf75u+Ob86derUqapTp869N93EiKgPQKWBo8srRtFH9C4R80Pad/SE8ZN9g357HRE/gvrq0ZOnlIY/Y1qH9rdQHzh+cm7esjHbj6F9Ner1U8vHVk+4Ze4XaNpHFHPbzPkNCxlny9DuRXv5zMuXaPfa3/wHkXEXqOqShbPnv7S8ZhNRVBzql81uaF5ISRQG+4XQt86et/ySu6oLu4jsOUTmQ02z5i97puTkEkwY45m3NDU2zDoY9zzscTP0hzZBEJsf5kR/zJEymuYvWRa/nu0nMtRDVj9vwcyGRE885qc0ob1tfsOyhYb2KB/aLkRdu6xhfmNi/aD34Qw7ZOULFzQv0bNpA/h5on3h4saFmW+M3UmUaSWKeAYyhczEKYaYroMXvqymz6iQfksmyK2US1Nh7ffQNaCukPzoWcLmD3zQ31TUNY7KrPTN1m+utEpJj0+1lESGahy7FuxXgIvRGFwMI14EFHrhNACXoWFxwwzSZi5fPI+02YsbLyWtqXHGYtLmNSy5jLQzY5PBtmmRI6Z9uqXwC3OKWYrvO5yVLcoXJ4zc/s3WU7OtZBajh501My79QBQX8kCciCWUZukboipqpCXwT5Br1nX9sLjOsqAo17Ob4SGzYZMhH1NJCZbKX+gSHms28AijysVHpe95ZOz4cePJC7tLDK91TWT5piLW5hWbgdFUt+FJsWuYTdAXpVRLivRCTtALcv1xQR+iB+v2p+TZWTymcmnjYuiejaG5CD2OlTJJkRScY6y0UICWMXoqTQURxf9fvTb87y52549fylPqIulgE00Tu6riTNJc8oV4Bm9eHuI5RVNTiFewF31DvHqWjoGSoRXkjeCISmgxzaEGmkdjsXtTEReLqRmSBSQicgiidhBiqAGtQrKAltByWggtjc6n+ZDPhu5lQI36g85Y02gStGbTUvANkPasndF7GJp5GGEQLg0zaJK2zx2tDLXF4AU2QB6c4QA55rzQeHMwQhPamkOjN8vVXA6cRQOM5xzh/38+6mF5zv/PbDRTZa/6ERXz4ZRh2EE2ULLhd2RT3bh7kP4R6Kgou+boR0W7KPnf0SkQIqIt9BibQ4/RTnqWnUCvrdRJHfRnSqRyuotW0G10HSJ1GiRrsaeTEMHldBuz6R3I6Pciku+ll6F7EV1DOyiBJekf00pao7yGXmsoitIRHRMQKTeyC/WlyDoH1F8hF1yIyFnIfHq1fpN+i/4APUidyp/1UxSB0zET18v6J4a39PcQ0bV0O22kA+yWsG04URfh3HUqv0VMbVLqVKbP1r/BDJx0BeagImZfZru4B9Yb6SOWxFYoZbByv+7X/wgtO9UhNjfRDjaEjeZOQ60+Vn+ZEjDGMljdSG20HVeAnqZ3WKThhP6AfoJslINTthL+eIXtUrpOreoqhscM8FI/Go6WBfQM/Yn2MRf7A19giDTkGbyGK/XXkREH0RTM9nfo+SH7kl+Da6XyvDpKL8WZX0O/Ft6m5+gDlsxy2Xg2lffjC/jdymJkzhx5EmfhLK2l38D6fuZh23kk36vcrz6qfmtM7TqoR2NH3HQn7q1/YFFYqcaa2S/ZG+wwL+PT+Z38kHKb+rD6qqkBq74YWeJGepS+ZLFsGJvIfsGa2Ap2Hfs128heZvvYUV7Cq/il/LjSpCxSnlZLcU1Wm9VfGa413GA82lXd9ceuv3R9qefp19JExMMqzP52uhsr66S99DauA3SIGVgEi8alMSebwq7CdQ27kd3HtrCHWQdG2ccOsY/ZZ+wL9i1HouRGnsKdPB2Xiy/mV/Db+F18L659/J/8ayVRSVc8yhClUKlRFmBW1yk349qmfKAmq3tVHX7OM2wwbDZsMTxqeNZwwhhp+iVusS99d/+p7FP7u6jr+q4NXW1dHfoHyP42xJSdHHgSmYi81YDcvQw5/0HE+WssEr5LZtmsiF0Iz0xnc9kitgyeXM02sQfl3B9nT8FLb7LjmHMUt8s5D+BDeCkfj+ti3sgX8Zv5LbyDv8G/UUxKhGJR4pVsZbRSpzQqS5TlygbFr7ykvK8cUk4q3+HS1XDVoaarbtWjjlanq0vVu9WP1I8MtYYXDX8zhhvnG681BoyfmoaaikwTTBNNdab1pu2m1831iM7dtI2eOPvss4PKKqVC2UY38XzVxl/hryCep9MsZSxHpPIt7Hp+NevgGYZlxpF8JBtHJ1Q3fP0838xP8pHKWFbJJtNcPihozRinPoKiUN1Nx9SnsLZXYHmZMZJdw48bI6kNjwXDMeZzykDVo7xI7ygHmEm9l95Vw1kiO8Z/p0xAFDytFhmqyancRY8ri9jVtI1X4JHjW/M6xPE49gjyQhXLY18peErk4xBFBcph+hVdyt+iYzjH19MdbJY6m26ifLYCT+AP4VT0M1xmzDbGsxf4HLWF92EdxNWHsbrhLIMphjhazeqUTcbj/G3c3faq4bRf+T1mv5c/roxVTxgmsSacgKvpWlqkr6Llhmr1VTabFDaVMtWDyG4rlDzViXIlskotctp2nO4dyAMlylhIkhA5FyIupiBDbML1G+QJFRE0B2f8ImSxV6jDWMUDNNsQzZB1kI1f7JpE0/SHaKM+my7Tb6H+yAfX6StgcQv9jdbTFram6yrcR9NwcvazCw2j+F7DKL0/b+Fv88l8Q8/9hbczWRL9HdfjqBThOa5FfZMmU7G+Tv8rorsvMuxGmkEX0BGs8hOMMEbZRfld43irPkpZiPUeoIn673QHC6cmfR6Np6foQZOBGkwe7LGfvYr1XkWNfJK+RGnsmgM/rIcXvPDWUuSftd6yKVUl3uKi8wpHjhg+rGDI4Py8QQNzB/TP8WT365vlzsxwpTs1R1qqPSXZlpSYEB/XJzbGaomOiowIDzObjAZV4YxyKlyj6jW/u96vul1jxvQXdVcDBA1nCer9GkSjeur4tXqppvXU9ELzku9peoOa3tOazKoVUmH/HK3CpflfLndpATZtYjX4G8tdNZr/mOTHSv5myUeBdzrRQatIairX/Kxeq/CPuryppaK+HOZaI8LLXGWN4f1zqDU8AmwEOH+ia2ErSyxikuGJFSNa8QQchUn5k13lFX6bq1zMwK9kVjTM8k+YWF1RnuJ01vTP8bOyma4ZfnKV+i0eqUJlchi/scxvksNoc8Rq6AatNWdXy7qAlWbUeyJnuWY11Fb7lYYaMUaMB+OW+xOvPJJ0pgrjsWXV153dmqK0VCTN0US1peU6zX/PxOqzW50Ca2pgA3155qj6llEYeh2cWDlZw2h8TU21n63BkJpYiVhVcH2NrgohqZ+r+cNcpa6mlrn12JrkFj9NWu5sS072duoHKblCa6mqdjn9xSmumoZye2sctUxa3m7zaraeLf1zWq0xQce2RltCTGTU2Uzj6TbJSXXBVU467VkmZuQ6HwHh12ZqmEm1C2saJqBxGLXMHAY1fGoYevlnYUfm+MPK6lusI4Rc9PcbMq0ureULQgS4jv2zp6QhJDFmWr8gwYo4OR1qaO/m/R6PPztbhIipDHuKORbJ+pD+OZcHuMu10KqhgPtoAnzbUDMiF+53OsUG3xDw0gxU/L6J1cG6RjNS2sib66nx83rRsqu7JX6KaPF1t5zuXu9CJHfIJ+54v9l9+s9iTehT0TTCzxJ+orkx2F452VU5cVq1VtFSH/JtZVWPWrB92Om2EOfvU1atpPAQx1MU2YqgrD2tLCrVkX41E39GGdSzAiYzolJKmDbKb60fE8SacKfz3+wU0E+IXrI40y00Tf8IT8/6yB71HtOLbFEwYdwqK6umtbSE92hDqAUHPD9UIOKpqtqplflpCk5mJv4C+q5hgmpS/F64rEwoIP6ColC1h2JKiK/BR0Rn/5xRSHQtLaNc2qiW+paGgO6b4dKsrpZO/ix/tmVhRX134AT0HTek+Eetq4GvmtgIHApOpa0udv3EVi+7fvK06k4r3vyvr6pu44yX1ZfWtGagrbpTI/JKKRdSIRQVTVSokmGRbdws9VM6vUQ+2apKgazPDDCSMnO3jNHMAA/KrN0yDpkalHmlTHxEjimrqj47euSRrOkvb3h4b6HaCLO5N69CeIT5aYFRIYoMC+udbdNPC0ywHRUe/p+xjZc8S0RE72yfs9yevjXDtjUy8vtKvbTdUyBsx0RF/cds94mO7p3tc5bb07fhBiRGq/V/yHZPQQRCMik2tne2z1luT99GImxS4uJ6Z/uc5Vp6Do2wSU1I6J3tPj89mAW2taSk/yHbMT1HQtg4bbbe2Y7/adsxsJ1pt/fOduL3BT33LRapJFvTemc7+acHi0NIDnC5emf7nOX2HCwRIZnndvfOtuOnB7Mh/of269c7287vC9J61FIQ7iNycnpnO+P7Aq1HLRXhXpaX1zvb5yw3s0ctHfFfOWxY72z3/74gu0fNjfifXFTUO9uDvy8Y0HMkhGRtRUXvbA//viC/50gIyVmVvfp3Kt6yvy/o6ds8EZJcfkmEixRxq3bGOGMyAeIrkO80Zdd3XgN9S5q6S3wDMpBI3WHYAb39XpuRR0aWTjFJNJoiIsBLZAH96w7BEBhvjOCMhsgoNEtE87cdgkHzt94YwRl4Gl6vSb5mhwV4c7umMjXA2BNGjfFchSngtzGmYQYB/ag3wmrlU8hssXBh47OOyEjJHOqIipLMd5AYBdMFiWBg0bx9Y5LHetIjP3WF1s9Bp47UfWgttBZScXHhqcJBA5nn9AcOGOKMd8bwPl2paktXiiHqsce++ReeAiv1o2qaWoRsmsru9iY6yB7Ppyh1hrqwKRGNyqWGBWGNEeb4gH5EDh0DxjtJcKl2gVmxbxu+iTuZrA6KHWEbZC+JHZtcYp8YW2ubZG+InZ/cYF9mXBZ/kp9MslICs0QlJk5IqE9YmKAk2C03W++xcqtVTbGHm2gHf4SYvqtDOAL+3OWNtlqNU6yMsdv72NWIRLw3dIhtSRTuERsA5qvtUXB1ojcqoL8nPQXmEzlLMH+XLosSpsKysgf7o1hUsgO19kz3YFE+keYaPNDBHAnwrrdWGErIt5rFENZoYd9qFjJrhsmbkT3YYSo2jTcppkgZH5GixaRFRPAppiSxVSa7GN2EfkbwYlxTgpiGyZY2uCDJM876efcu1HnGnkJxBLJFHs/JRUI29hiAio+dqkND8bHY4bl1hacWFbKY2OHDY4djE+sILR62aDFLNBpd6RRjpfw8iokzORMS8vOGMqc7y+1KNyoX78j5pPPjruMs7r2/smj23dHwtjUz1516h0+MHDZ17YqH2dTE+zuYgykskvXt2t/1tVXbuqOJ3X5tWdND4iwU60eVVkTCQKXV2ydReiFJok1i34D+udyDrG7G3c1kdjMZ3Yyrm0nvZpzdjAbGu1Jwanpc+oiwC8LKM6amN6avCLspbHXGQ30ezXlWiQpLTE5KHFiZ80aiIYVP4dyax8KTas21YbXhtRG1kbVRc81zw+aGz42YGzk3qsPdkWXJcmdkZfQbmjEtvCZilntW3yWuJRm+jFvD74q8pe8dObcPfCD84cj7sx7o2+5+zp0g1yK2KL2bcXUzGd1MaL3G7iUYuxdl7F4mDkFA3++NTRs+zZyVGRmuJmvueDViQGpygD/iTbfliBBx2Ipt423TbVtte21Gi81hW2A7YFMdtvU2bnsapxtZPBj73jihbmVexq1sH+PErIyLs9AelzBYnglrdMxgxgbUps5L5an2eJMqpiE6gfmwQxwYwXj7WCzg7AMiHMksOcPm7ZM0OE90HyLyiy0piCJibQkiem2a6GnTRC+bVazKJqNXtGLvd/BfkEn/bLtMhxnZMLTNPnxfNssWY4r+YI52CKOSEf2zxfETJsB8vl1YyU6WM3DiJNbn7crjxXm+PJ4njncGyamQVSY2Leh8LoNErkhGi0PMTZNRqGVYrGLJFjl3iyaULQH9G69bTMESLca3RApjFqMY2ZJ+gFgxjUemsw0Knca6RWO7T6Q4ex4rysXjrHWLPMF0ukicyc/P5M5ji3E8URYfW4TTiVO8aLHniPWULHBK8YfDmoijWrbc683qn+YyxOW4Y6yx1j5WxZgepaVQWF9TCjP0B6TFoeqMdqVQuisq0twvPIX1zQoLN3rUFHJYU1MYYT5I4UGQCTzbs2rVKjo9m7pFrG7xorozAqHUp0DmgiGDs9xZA/iQwUMLhg7Nz0tISDS5RW6Ij0tMwJXG4+NECnEXt1nWXrVi2ZDMW5/fOL5kWPavJ1/99LQYf2TznBVzExJyU1bvvGPqnOev3vs2O89+6eLG8vNcSZl5568aN3p5X4dnzFWzkybVTipw2VP7hGfkl6yonbb5ot+LDJKhf8azDRspkTk6KRJ3K7EDEYEQY+5mTN2MsZsJF2Hucg8OE1EyGYzPxohFRoUzhRKsYR5LuDHBrkRYrOmUzqJiZW6OlfEQGy76x2ZGMt1krgirqDctNPlMN+Ol3KSZ7jH5TbtM+0xGk7gziHuLScSViBSTuJFER0vmKxlykpHpHOEkYw/MCW+EiD2TUWZ1EeAyse/gcymJDW295MwtWO7M50esxwpFhi+0Hvkct+Fj4j4cgzQek59vfUHk8pBqZqLYBveQGNeQ/JiCmPx4V0yc2EFuTb6wcMa8nNWr27dt6+Ppm3bvZmtR43185jpmmtd147pTt47NwfNTJ1UpyGRJjn1PKf3oIIgr/do8qY5OJUtJbRvp8AYUV3tsfJ6lpL8injJyJWrABaCtoJ2K+M3JdCUNcitwJcgH2graCdoHwtswULRqoAWgzaCDokVJVextmsNakqXY0NeG82VREuk4SAcp5ADmgsaDpoPWgzaDjFJPSBaAVoJ2gk7IFq+S2HZLPuae2HaDLNrnzsuT1YZgtbZOVtsvqgmWYycGy/Lzg2ojgmqDBgfFA0qDZVZOsIzNzPOJMjwqb1cJHkKwyARMfCGQ8T+ShTG85NyjxJMfxBVjSOJVYtsz3HmbdyoqMYUrjGaRQ9+lsLaomLyScK7z4xRLDv4JPxZs4cfao2PyNpdcwA/RVtBOkMIP4fqAf0Ar+UHhc2AxaDNoJ2gv6DjIyA/iOoBrP99PFv4+5YKKQdNBm0E7QcdBJv4+0MrfE/8rlij4YhDn7wGt/F0s612ghb8D7h3+Dqb2WlvB8LxOyXhyQ4wjM8QkpoSY2IS8AH+17et+iCg3dhoR9aSSjsfvfCW9LXOQI6AktRXOcQT44XbN47inZCB/nfwgjpm8jpFfJw00AVQPWggygnsD3BvkA90MugfkByHKgFaQxveAXgK9QQNBXtAEkJnva8MwAb63zV3qKEngr/A/4a3ZwV/mf5blS/x5Wb7In5PlCyjTUO7hz7elOagkAu2EPlaUVpS5aDfwP7RnxDr0khi+E75zAHNBxaDxoOmg9SAj38nT22Y5YmHkSdpjxnswb6OPZfkQ3Wcm71yH112GANQEuEecBw6wWdvs5l73ho2oCnDfdAs4Ae7V68AJcF+5CpwA97zLwQlwz5oLToB72nRwAtzjq8ABAvzuJzKyHAXjL2VaiYVfAS9dAS9dAS9dQSq/Qlz0tSrmdmdbdjY8tsnr6Zft8O1gvqeYbxLz3cd8jcx3DfOtYr5C5ruY+TzMZ2e+NObzMt+TbBhc4WPejh7V4d4k5tvDfI8xXzPzuZkvk/kymE9jBd4Ad7adny+LClm0l4hDh/K8ImQfC3fCo07EvBM5YSdwL0iXNS+UtPSgsi1NlOnt2cXB+oAReQtKxvDd6Lgb27CbDoBUbNBuhNFuGNkNAxZgMWg6aBfoOEgHGaGdjomvl2gB5oKKQdNBK0HHQUY5neMgTgtCU9wqJ5YbmvR4UeO7cYkfQzi505tqtVs91jHKejuzpLHxaXoaLyD5f7fYGHNMgEVt/zLqqy+jKKwkjN/E11MqNuLmULm+7etUR4D9ps39pKMknt1BaSqijg0nN8tEOYyaZX0I2c2iHEx2/ijKvDb7VHSztLlzHDtYtOi13fG1/YjjY3uAgz1qf9LxphZQWZvjr5A8ut3xun2t44XcgBmSp9x40Wxz7NCkaqd9mOOxPVJ1FRo2tTmuEcV2x9X20Y5L7bKhMdhwcTNqXotjknuaYwzsldtnOLzNsLndUWy/2FEY1Boi+mx3DMQUPEE2G5PtZ5eDutKkwSkFAdbkzTFtMFXjHWqoKc+UY3KaHKZUU4opzhxrtpqjzZHmcLPZbDSrZm4mc1xAP+j1iOeJOKP8calRlT9glLyVk/wJpPxZI2dmTheQv49SySsnl7JK/66ZVDlD85+c7Aqw8InT/AZXKfPHVlJlVal/mKcyYNIn+Qs8lX7ThF9UtzJ2Uw2kfn59gFFVdYDpQrQmRXxH20mMxay5MUWUfdfcWFNDSQmXFycVxxbFDB9V/gNQH8Izj42epB58qn9D5eRq/yOpNf48weipNZX+W8WXuJ3sM3aioryTfSqKmupOpYh9VjFJyJWi8pqaygCbKvVIY59CDxHzqdQz48Ys9EgzpwX1NgX1MtEfehmigF5YGGVKvcywMKmnMqHX2pxRUd6akSF1EjVqljrNidrZOnsyoZOZKXUSfLRH6uxJ8Akdf5FUsduhkmaXKiyZ7FLFzpKlytQzKrkhlbWnVdbKkRR2Rsce1Ik62K0TdRA6nn/301iK5+H2kTUza8UX4PWuikZQvf+Gy5uS/L4ZmtY6syb0zbi7fsbMJlE2NPprXI3l/pmucq11ZO0PNNeK5pGu8laqraiqbq31Npa3jfSOrHA1lNe0j54wuKDHWGtPjzV4wg8YmyCMDRZjjS74geYC0TxajFUgxioQY432jpZjkYzxCdWtZiqtKasNlu08IhzxWp/irClNsC4sksE70pl0TcoOPK1soQhPjT/SVeqPAomm/iX9S0QTzpRoiha/cgg1JV0z0pmyg20JNVkhjnGVkmfJ0uallFQxpzz414wPREuWCocH0dP8Yx+0Vfi9DeXNS4gq/dmTK/3FE6dVt5pMkNaLJflHdMsiIirw+B8UDoBwhBAqymlFISsUsrCwkOK5+780VJaJU+DjT7YzbxpbQs01ij+tsoojFVSFvk7egWcpcXtorsECm5mHNXfbCE3b4wm9YpFYczctWRriQr5YEiqDPdGludslpz/CWZ7THlsCg+KjkMLEx6AoeM1nlGT4Z8Qu+sqsi1+k610URmH6KQqncPnbywhgJF6pTlEURQGjJVooGmglCzAG+B0eQ2OAfSgWGEd9gPHAbymB4oCJFA9MAn5DNkoEn0w28CmUDLRLTKUUYBrZ9a/x6CtQo1SgEw+2X1M6aUAX8CvKICcwk9KBbuCXlEUuYF+8B35J/cgNzJbooSz9JOVQX2B/iQMoG5hLHuBA6g8cBPyC8mgAMJ9ygYNpoP45DZE4lAYBCygfOIwG6/+i4RJH0BDgSImFNBR4HhUAi2gYsJiG65+Rl0YAS2gksJQKgWXAT6mczgNWUBFwFBXrJ2g0eYFjqAR4PpUCL5BYSWXAC6kcOJZG6cdpnMTxNBo4gcYAJ9L5+ic0SeJkugBYRZX6MZpCY4FTJV5E44DVNF7/J9XQBOA04DH6BU0EX0uTgXVUBbxY4nSaov+D6mkqsIEuAs4A/p1mUg1wFk0DNtIvgJdQrf4xzZbYRHXAOXSxfpTmUj34SyXOowbgfJoB+WU0E7hA4kKapX9Ei6gRuJhmA5slLqEm/UNaSnOAl9Nc4BXAv9EyuhS4nOYDr6TLgFdJXEELgFfTQuA1tEg/Qisl+qgZuIqWAH9JS3Xxm8LLgaslrqEr9EN0LS0DXkfLgdfTlcC1dJX+AbXQCuANdDUk64Af0I10DfAmWglcT6uANwMP0q/pl8Bb6FfAW2m1foBuk3g7rQFuoOuAd9D1aP0N8ABtpLXATdSi76c76QbgXbQO+FuJd9NNwM20HngP3Qy8F/g+3Ue/Bt5PtwAfoFuBD9Jt+nv0EN2uv0u/ow3ALXQH8GGJj9BvgI/SRuDv6U7gYxIfp7uAW+m3QD/dDWwFvkNttBnYTvcAO+g+/W3aRvfrb9F2iU/QA8AAPQjspIeAOyQ+SVuAT9HD+pv0ND0CfEbiTnoUuIt+D/wDPQZ8lh4H7qat+hv0R/IDn6NW/a/0vMQ/URvwz9Suv04vUAdwD20DvkjbgS/RE8CXKQB8hTqBeyXuox3Av9BTwFfpaf01eg34Kr1OzwD/SjuBb9Au/S/0psS36Fng27Qb+A79EfiuxPfoOeD79DxwP/1J30cHJB6kF/S99AHtAR6iF4GHJR6hl4B/o5eBH9IrwI9on/4KHZX4Mf0F+Hd6VX+Z/kGvAf8p8Ri9DvyE3tBfouP0JvCExE/pLeBn9DbwX/QO8HOJX9B7+ot0kt4Hfkn7gV8B99DXdAD4DR0EfksfAL+TeIoO6y9QFx0B6vQ34H9z+n8+p3/6M8/p//i3c/rHP5LTPz4npx/9kZz+0Tk5/cN/I6cfOZ3TF/fI6Yd/JKcfljn98Dk5/ZDM6YfOyumHZE4/JHP6obNy+gfn5PSDMqcflDn94M8wp7/9/yinv/7fnP7fnP6zy+k/9+f0n29O/7Hn9P/m9P/m9B/O6X/++ef0/wVVj3DwCmVuZHN0cmVhbQplbmRvYmoKOSAwIG9iago8PC9UeXBlIC9Gb250RGVzY3JpcHRvcgovRm9udE5hbWUgL0FBQUFBQStBcmlhbE1UCi9GbGFncyA0Ci9Bc2NlbnQgOTA1LjI3MzQ0Ci9EZXNjZW50IC0yMTEuOTE0MDYKL1N0ZW1WIDQ1Ljg5ODQzOAovQ2FwSGVpZ2h0IDcxNS44MjAzMQovSXRhbGljQW5nbGUgMAovRm9udEJCb3ggWy02NjQuNTUwNzggLTMyNC43MDcwMyAyMDAwIDEwMDUuODU5MzhdCi9Gb250RmlsZTIgOCAwIFI+PgplbmRvYmoKMTAgMCBvYmoKPDwvVHlwZSAvRm9udAovRm9udERlc2NyaXB0b3IgOSAwIFIKL0Jhc2VGb250IC9BQUFBQUErQXJpYWxNVAovU3VidHlwZSAvQ0lERm9udFR5cGUyCi9DSURUb0dJRE1hcCAvSWRlbnRpdHkKL0NJRFN5c3RlbUluZm8gPDwvUmVnaXN0cnkgKEFkb2JlKQovT3JkZXJpbmcgKElkZW50aXR5KQovU3VwcGxlbWVudCAwPj4KL1cgWzAgWzc1MF0gNTUgWzYxMC44Mzk4NF0gNzIgWzU1Ni4xNTIzNF0gODcgWzI3Ny44MzIwM11dCi9EVyA1MDA+PgplbmRvYmoKMTEgMCBvYmoKPDwvRmlsdGVyIC9GbGF0ZURlY29kZQovTGVuZ3RoIDI1MD4+IHN0cmVhbQp4nF2Qy2rEIBSG9z7FWU4Xg0lmMtNFEMqUQha90LQPYPQkFRoVYxZ5+3pJU6ig8PP/n+dCb+1jq5UH+uaM6NDDoLR0OJvFCYQeR6VJWYFUwm8qvWLiltAAd+vscWr1YEjTAND34M7erXB4kKbHO0JfnUSn9AiHz1sXdLdY+40Tag8FYQwkDuGnZ25f+IRAE3ZsZfCVX4+B+Ut8rBahSrrM3QgjcbZcoON6RNIU4TBonsJhBLX851eZ6gfxxV1Mn64hXRT1mUV1vk/qUid2S5W/zF6ivmQos9fTls5+LBqXs08kFufCMGmDaYrYv9K4L9kaG6l4fwAdQH9hCmVuZHN0cmVhbQplbmRvYmoKNCAwIG9iago8PC9UeXBlIC9Gb250Ci9TdWJ0eXBlIC9UeXBlMAovQmFzZUZvbnQgL0FBQUFBQStBcmlhbE1UCi9FbmNvZGluZyAvSWRlbnRpdHktSAovRGVzY2VuZGFudEZvbnRzIFsxMCAwIFJdCi9Ub1VuaWNvZGUgMTEgMCBSPj4KZW5kb2JqCnhyZWYKMCAxMgowMDAwMDAwMDAwIDY1NTM1IGYgCjAwMDAwMDAwMTUgMDAwMDAgbiAKMDAwMDAwMDM4MiAwMDAwMCBuIAowMDAwMDAwMTA4IDAwMDAwIG4gCjAwMDAwMDk2MDYgMDAwMDAgbiAKMDAwMDAwMDE0NSAwMDAwMCBuIAowMDAwMDAwNTkwIDAwMDAwIG4gCjAwMDAwMDA2NDUgMDAwMDAgbiAKMDAwMDAwMDY5MiAwMDAwMCBuIAowMDAwMDA4Nzg3IDAwMDAwIG4gCjAwMDAwMDkwMjEgMDAwMDAgbiAKMDAwMDAwOTI4NSAwMDAwMCBuIAp0cmFpbGVyCjw8L1NpemUgMTIKL1Jvb3QgNyAwIFIKL0luZm8gMSAwIFI+PgpzdGFydHhyZWYKOTc0NQolJUVPRgo=","contentEncoding":"BASE64","mediaType":"application/pdf","fileName":"renamed.pdf","timestamp":{"seconds":0,"nanos":28000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}} +{"testCaseFinished":{"testCaseStartedId":"55","timestamp":{"seconds":0,"nanos":30000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"56","testCaseId":"48","timestamp":{"seconds":0,"nanos":31000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"49","timestamp":{"seconds":0,"nanos":32000000}}} +{"attachment":{"testCaseStartedId":"56","testStepId":"49","body":"https://cucumber.io","contentEncoding":"IDENTITY","mediaType":"text/uri-list","timestamp":{"seconds":0,"nanos":33000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":34000000}}} +{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":35000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"35","timestamp":{"seconds":0,"nanos":36000000},"success":true}} diff --git a/compatibility/attachments/attachments.ts b/compatibility/attachments/attachments.ts new file mode 100644 index 00000000..e0e9310a --- /dev/null +++ b/compatibility/attachments/attachments.ts @@ -0,0 +1,49 @@ +import { When } from '@cucumber/fake-cucumber' +import fs from 'node:fs' + +When( + 'the string {string} is attached as {string}', + async function (text: string, mediaType: string) { + await this.attach(text, mediaType) + } +) + +When('the string {string} is logged', async function (text: string) { + await this.log(text) +}) + +When('text with ANSI escapes is logged', async function () { + await this.log( + 'This displays a \x1b[31mr\x1b[0m\x1b[91ma\x1b[0m\x1b[33mi\x1b[0m\x1b[32mn\x1b[0m\x1b[34mb\x1b[0m\x1b[95mo\x1b[0m\x1b[35mw\x1b[0m' + ) +}) + +When( + 'the following string is attached as {string}:', + async function (mediaType: string, text: string) { + await this.attach(text, mediaType) + } +) + +When( + 'an array with {int} bytes is attached as {string}', + async function (size: number, mediaType: string) { + const data = [...Array(size).keys()] + const buffer = Buffer.from(data) + await this.attach(buffer, mediaType) + } +) + +When('a PDF document is attached and renamed', async function () { + await this.attach( + fs.createReadStream(import.meta.dirname + '/document.pdf'), + { + mediaType: 'application/pdf', + fileName: 'renamed.pdf', + } + ) +}) + +When('a link to {string} is attached', async function (uri: string) { + await this.link(uri) +}) diff --git a/compatibility/attachments/cucumber.jpeg b/compatibility/attachments/cucumber.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e833d6c77d33e620ae49d01c6341ad7e5543bbd1 GIT binary patch literal 1444 zcmex=ma3|jiIItm zOAI4iKMQ#V{6EAX$ibk;pvlar#K0uT$SlbC{|JK&&=V}oKwrQC2Ma43I|Cy#)Bjrx z93W3JFj%5R(9qV{Ntcq}Ky~tP0uLAPSj53Js=3YaT3(saOkCY9;`5A|n$EGYdN> z$V3JPCPrqUBN+tQg%k~il>!AEoq|My6AOzQCy9zF8yTB~CKo{+&uGtZX0w)!P@n75 z+pl=GZ!o)2TKe4kXH-t@v#4EXEL!JE?zOu6r)a^it+7!xlS1t8{b#8CcjV%O8K0Lw zw!5=@{{4E%lYXmb|9XFR)y|pcq!QHyPd&4_T>Dx#CekoQenQBu?LV{I?)a=Z|5`qv zFCb50m1$zkt9Lo_J0{jC=okGqK3#m~$Ha_v^0sbzj}ElmKM-8K@73}%+E1_Sj1$h= za&Sv<=wdZVW0znRzq!_L_bo^lOYMz6H&?{%)XoFh(vNfajh)BRw*tXt#N5h?h}`dZpTv+M2|iZeX+YndxfsS7m0HMKkjMk z3R-;5(6ii>*OK{q>6sTyS60rQly}eE#LW&5BG%-(C1!D;fUT^{(mfa_yh< z{#u>M^5=OJ`#1Z*!uSb$%D$efQTh0vq2T`C;?gU-q$)Q@ZJHi;`qC^B2V>vgrM{Op zz2Y^CHRFrdI~bc)vyq`s=08J-S2f$|3$=&W)J@FMUY+(-GI%Z`A@L?@)(B$FN=Q(t=#l1^;xf6 ztKqtSx0-4T?$s+Kr!hS|dgkT)$(;KdOXF_sl{kL&qTE`s1rg3$Iy;OF^4Vh9&`X%c- z?@Ir1`muNQ?HGfD#&eeSnRs0PY8_Wf0x zYr%isu|McfyL=m%q*VpMX&9LEAPE|n37MD~SOi&xgcSot42=?%9Sb43h7p*8_w1>- z6Mg&7uRHgQ{q+{+?($T>Bl_*Z+yh@{H3b$5c{NME=w}I8|8lFOYSF`(6w%|A@+BXl zmusIkS2)Mu_D0Bf{>CWJM_W~opFJsmFzB$zE1{4Rf8~=jG8xtK!v7@pU6p$h?!dst zAi;cKJ=ASXzzoOCisUvHRt7dfAw$K$LSa!QBS)~?m_cs48@=pNOiq~YFVP2{3ZGA2 z+`D6=>s#ATRX)mE@f(tl=gZwXkQQv**LRUIg?&x;o5PWIH+oN(d8?&{>zqn{JdOYJ zpDR~h9MUx8cipWf_R8znWtJ&uK2N+Xmx{{Kw?qP#Uw literal 0 HcmV?d00001 diff --git a/compatibility/attachments/cucumber.png b/compatibility/attachments/cucumber.png new file mode 100644 index 0000000000000000000000000000000000000000..2760899aa0ce16aeaaea93d3118a32e7ac5bec8d GIT binary patch literal 1739 zcmV;+1~mDJP)G+r@kQ0VOJz1t=x5=pe3owlM@Ei<#*ZtpGI zgKd}Iv=WU6L@O5L5Qs#)gFvk*7OQ`VCsjOx5<>9^RV-lQA0`qL#Hi6fjMC@#y`7oe zS+={g-EDP}m+f}vz4x2%_`Ec`caU@Gwv+u9&;ebh;EPxToe6TQSS7$RIEHqf|0i z0O+ z#n5`Lu?_+l5a?76FBT_ov)oVPu)W&2iyqQk6q$E2`kQlqo37e8-)c+O?QiP<=H+=W zgC{tLKe`UI8wSZ$`}IIZS7^VP!&SQ9uqqj{=4V~1HP0i=kNxUV+pBnmxV8-q#Bxu7a+-a2h-P!vK`Neu>T>`ydm{x2F*T=U zFja;RD&fUT?=ygOHKmEIjm9$XXg4i$m^A1Td}8TX_oouO^1 zftu}jxiw*Sqct@^-zMG^t)7q>c1_O=)^$NOakew|C1vwbb(rq-=>$Sb3W6-RZHa~m zCJ-n2XtkjN1C$hf07a9cuvH0K135T-J;#nfDUID~Sv*Yz!r1hE3|a|Q4_9AFEWLHK zWz~oq!$8|OVjZ(mf6Y*Z+@G&h%4HXA<`4i@t!XO}{GYwU7nxru-K+|Xtu%RHv$V5t zB%F}kxeBctB<7atq+W_2XaHsIuS|aaB@`qli21Zmg1ge~X)cg!Z$@+@T2Kf6wH3yz z&3Yvsd2vMF!t&A=G!b@&(o#XX>H~4DCJ(w*r-TNAhSk2Rta1dYR_3O9WK; zw#R~odmNn4Rrysbu1(x67Wr<38~#FiG1`n}wO&7`@#)Xk3f`-e^xfhbRWBNCp0aZ6 zG7j-w;#H4aR;4%|7`O;-+)Hl;uEM%I>mVnijiapH>2)nQ~8L|KJMiw<@G*t1yFW9;lFYc#uS`DwjogG)sqA zR1ZXz{)UVWQ#sGl0$Aj@M%qBOs8U>G?P+&{nu~!`wccEicA`NtKbHn#g?=au3r^~) z3?!GiqlV-fJj2A0p}mr}21v220F29iQ1fLV>-W%pUD-_S!;=aJIQ+*`W9=9^HPvL{ z0;-_op8_N*ciN&xk6wP@c=5J5d#{rWB-U?ag_zePQ!sYV&o8}(nae94B=$6zwdRg$ z{h^gdI-K$AkW4|MJIzg%@h!p>_RSE#rObbMsUUTO7bj4~!21~&^MANd_d&CC2wth| hfX@)Vpvv|h{{nxzjlgvW5(5AL002ovPDHLkV1gBrLk9o= literal 0 HcmV?d00001 diff --git a/compatibility/attachments/document.pdf b/compatibility/attachments/document.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4647f3c9d70173713ad061f405a1aa9146615677 GIT binary patch literal 10061 zcmaKy2Q*x3*T;z{AzBC$Z6bObgBb=PdM9dh#uzQSQKAdcd#}-3^e%cQdJTeTiRdMW z&NuG8?|a|!ecxGUtvP2u``ORg`}yy)X3hKH_{zc7#0idY05Ge)v48KJn093Mdu!dRv?F9bYNy@?siCP3G zWd%bbBoJ_069g_$0byf?GzWkne8R#2N2CJ+W{vBbrXM+u+sXA%a_Q*txv!JrB!nY~ z@ZFyFCT$@ljlEBSrri`7BDeeSAZ@T$_R!+ z0Q|h%s4(IvZ`58p0`9g$6&I+6bZ~+rU%?#yFmjlr3^0~YHhkX8-kKY9K_&VB zD2AVl3;M5;*>!v1sxJ9^zo@Z3v1IYVoWN$TZ9yzSE{5-Y9C-x+%FO5&MxFFAXwg0Z z`In+9DNkMnGe_VNYy@3qMoeh86L4~I3q0rK+G@bV30oZkY%rI7Z#C3#@d>(EAG{oO z&)83Gg|-PeZA}$@R4W*1;altuDkOZF=bJOgCN(a7%t?GC{?+B!|1j3m&&!>fSmfQs z)_80|T!08%C-8UuD_VXp2b*tIp!vuwPw>`>+F+liasy(I-2AIFspI_6IX>PKv%#@F zN%!-i!9?@ZuS-|h(e~tpTQTpef40>$o06!gQ*9*<+n5{3$dY1Rz8|X}`A~lq)ASLq zDQE{Ou`aGyy$*EG|Ni}v0>xT#JuetCBtUU0v}4b690(C5N~K+w@E(fhPFV+PleK-= zW#XO^*A%hs7z&V37FsXh&1$js>Z*Dl@SBq#C z)YXP|=Q^Rs*pm7bIAWmWc=(HLS&f`*q;hSXM1Un#6WPvdpU`m%y@$^Y?vuK$@S-JV z!R)&pPMC5U3Psk;g;D|;N{j4!jVIQ@?4QDFc;K-ebr#Xj^F15$4)7d9v_tj<)8aV> z$eF?xZic_8RhOEOdLcfSnM-eHQ5IV1BM%qXx=~$aB>EEr1SRQn(Um}uYJaAl7Cq4q-b)UPxod0ajNgQ zdi{!Q<(nkMG4ech`H*8Vr7=8*YkN`JZ0zmd1>fhih4LLN){HkkglB!ic@YuMEgMam z1}~|3<&yw%V^zc??zuH=fn4o#I{%C`98lY$+cY9kBx(A%sxYDV>%A+MtpUv{R8mbX z=ye6Tr+BklLWFNj*5mKt%RzGHpy4d66lr*+yBaZhXwj1xOnN#8oNQ+z<3jXOu_~X0 zF$WzSj_{F!^Qh5)J;clK3%?PqN%KcFrjx1%_)))0(#PE?Qr=R)h&b_i%wy_CsY@T$ z_AFyUUectkph4H0$6Ot`#7MrTi0AMPPU{h*Nn(Wkq*}T47T$ur9l0H~;#EVGUl2b& zT8V?rib7sht+<$|EjGAE-4JgSGB>SnNJ};`pD&HG5Yc6G98;uXn*e^XfyOVsfLNm;!~-M?{47qg$2nB`8aFD}UJ%dkm1DW{Xu z9ANN1PnWaRP{c__~sKsj#MZ@*}QZZmmTb>l*6UJ|FU3v~pMc zZ+yJoS;hIO)jhg#eZQ;Ps8qVe8qoLfKvm>vL;~jV!|UMRAo5ELL!%(x?@yWo*iL4m ziLu8?hkm$bPexpalqJQgzD`h zvlaHzyruRFuXWh`Tl%Hqg>vDKUT+k31q!9%(hEqYy;5%@6|JJ)lsVuWbLzgI`fEUq*4C3wEy!1Quf#h|nJEbppeB9~qD zgQmQ_qGsx};gd4(f|1Zyjm49`vNoh=(a+7935o75)}b5cMoPBr+?HcV83r=wiPrtl z-aHUfYG+xe6Mj?lVqaZFpfv)HZx~c$UkeQo<-ICWAv4mPVc}c*n(vXxSnRGr?KQ$<#bpJt0ts2VezEJMu()8b zsRVku+!pPqT{abNUzFkNTkBI_VE5lZy5rKRTpuL|HBb92v^<2~igusBVSm&rZ#q{J zq>nSIVCFbSSNHIP-Y}#R`^N&UnR17`ZPi+mBWe90lyF(xm4!BP+xS~Q`C;lgN3&mB zN1KX*oWHTkYP=PqMzw&KRX*;=09H?Vb8d4vm8uf!(#piN-E>xdvCVW&gl0jp{B+ZF z1(WOucIn(-96N4a;O*rn0f^pc4|+;zCUriKYoVnf)UCKIH$6^GodUAl#6}Rqp#5 zktF5-?BcQhz*07xzi(!e+A2XH)h64c?w;~8;fne(kDloeq_>*qiDU7;%Iy;-X}^9Owm6~bU$1>8}6h?OdbcC=Ln~APTFSO^kdI#vKiUqU59^}B&*BcW?hh; zv~&1fjfbCeX3)jlB4WE@d-T~ueDwN#J&*RIYwDS`LedpznWSedqgrLF(k)wd6OemH zDn*ohjJV_%Nf?*ZoaDLwnrpGJoOaL&O=j@LJ`x+RfYFmCyr5u1G}ia20?kfkMoY+v zfn>NxOsj;&P6~!dNg#Ri!57u<8MWW4STq>Et!5wLN=Hx#ml*XgUp0$jIAR}<9-5qP zr~Vijvda?$OY3}m!7!H)mqbK6!vyKI42}p1!y)8J4a3q{33Fi8%h>|AHGL~@)Q9&T zlRLe}xQ}o4VC(v+M2_3mS*=@9{>jv_*ip>Bg_~c>x5<>DkB?Phw^XH_dFO_lE*9tz zYh&;k<)A(|VipWKb`or8La@Re)F zLa@+1tNZ-k*>7NM^6bVjE5VPS>m1&vdM|q(tN-J%S8B1|PbAl@F`P zFQ}xdtCGu}csayhaL^QqAsi^$Du--iy38HBEjO@A!Dg=q8-3zUr`+kxKAD22G%w== zo`R&pu-6hUNy>#{komZ4m5Aw?^u?6y8g=^Naf#Zz9|FVPqupE8y6N}cSQ=q?zFjZ8 zNKm10qfXO;YIm7?C@R)wly*d=Xa#F&*m;JR;XW(MC_eAf%PY%I_;fOx@|{dXQD@Ie zUg@{<7B}oTbeixucUzstrOp>j|d-Ier)1IPshzbkrVM*b3ZU&QHKkFLU(k*%J zuXlV($z94>`U6kwenZ4{bwTbkdXTA38=xbEA4+X$zQ2A=6DcC7>mO9~&^-i7_W2^} z2AYW7FjzbGG0W%D{GP}BD;_%XJr{u=Rz_|UX-%4PDguzNxY?=OjX}wE4vrkXuZ0O0 z2?0KPsVfsDWeG|*?A~<?xUP<$>>9_ z7;eY*#z3Uy4q%a#i{V*%j;I29__peL2mYPL1fK{J_i0|`mSTb{AB$K7QIyQiFd!Vvgmy=ZWc(LOWmlpjq;Kdot5h9s+-wm+7BH!g76Ai zMVXECU15%PrJ+*-_oD$*_LPe+_rPcdJda*!3#>rT3v z37!{VhlY(@7D&T7;~ieH?fwK z+_xe`4YvK1!TGEm-*Vx?`mL%BxUo4eL>!~%CF>tbYG3T!%Ma&9UuBEh=S9r87+`qH zr!>lYU~ostEW&2dTdoaKqot)1lt8k)DRgx0@+Rol+XNj%6t-c@VFCTjyz&x9_0;b~ zkLtfsbUbl=YEUlQY8r^?>e!Ue4Q`($kqST5d(X0556XvOp$j@3gRNCZ z^visv+6oGZ8FGoDz?{SkHZmG-_JqguxlKG5aeDt{RJ0&s%F~>qhD_Doex;~W6eIcP zF*X6kYM4v!fv|p&rTpuVXIqtht>@$)?y;TZSf=RsTcr%_{bm&Crx`6^b_!^$U?C14 z$P2)Pg%huz`FKeXP-FB8%Gwn<`)XI(Qnm&QCYKO|P{SxqfWg$Mdd3+Px^GfVY=hD> z7K{u{^~k(qG8C#*?^R1MFe=FuDr+TW($8}6>F!rL6l*9}2Bw;0d;2G~7K`)WyIJ@V zUZ)eTRuWb@e>poHB+<-=46>Fb-6N(%TQA;Kv2)7iW6f-1dV#r40+r|TPUH-Il;2rZ zg?*W$rCxWFKC>H&_qz;Vr)ZQfpd`^Hg06z1KT)aKyoO;{)LCpSs zl_bsRXGt;0ZwjA~$n=lm;)j*9E1#U+7mz)!N}65if(OD$wsAOU>dZm%yifHvN#S`{ zLLtFCP?AT#jsmy~1P6EJ_*lvJ`_FTa*2>n{_gdPj%zrPAAJl<)X42gPmBXkYPP-w2 zb5q@yf=PZX>@g?1Uzbj5HRc<0f)~B4wI{__*-5vh<25BZU*=8EGpV>elg(B%Uh=v5 zwBT1_)4Moq;>`18F|0UMcHcB@F$}A>N!8BMP46P80>Vb zt!6N;yn>P(l~(eRRQ9v?$}0;geVwtE!?m#5m0IH3=vo!WVaKlZfYIwg3h z+0TYpuUSu8>sq^8ON9=@bd3a!)TrxDAU5t#3n;aPC@#P8XW1YOdP}Wn*Sbr>LaWDU z61lWKJa~f~GU~;e${&&cQS{q2mNoyGfbV&QI2A>Q>!lX^@(ucf$ ze=>}V$LBUh>Y$~jMgbF+SrLtTAT(cXIn65YBDGPn#O5%&k>_64RKd&jchCH)7ql_X zqB_L!0tkjj$SN#NR(*K2<J;%_z4`d!Xsczs+$ z;car1_Sli+k%OA%elvV4?PvM7ONij8*XC;PuOmI#QtCa=`$r`(8uB9Ya6%SKO};zG8RIbG@pu%*4zb&h*Qcl-1a=PU+H#$(tRU z2{=NiJlf=!!Dji6w*IcyFW>KrAH*+ARau72NAsQYJEeZI@|am?dExaBv(A3(Uw@7a z#!d$q)z&F#&I3O_NPsh84gNavvwZ%}(hm!}2Sd&ep{Z06Hav6`%q_%f`}zTmSVjMR z1&?P@haxfWLCxP6a=K$_yC1`4RyB<0c^2NLy0Ef>-m`_7MvE`yRn!$GHOJVM9j%ed z6WlxyA+uWP`#{K=~bG@nIwOckE{aKAKUzr#=Sv zT0}8K4aV+ACGWrK;zDn&AJ*SumR&AC5Lqdm>o8pRPWT~z^;WyJKO^=B9`od@y_M1W zJZK^Py?2Ftt73EXpW*inTMI1nJ0kfOP?p_O`*oH@3LRv zz3yDLIgyBSqD3>teZ9aL{Q$J7`wh}**aQ{Y^E>MXrvc%GxOPiOJbv`O>FxFhc7~sD z)6hceTgFT}6et21Mutzc)k&>i>C)vbKro9oMq1 zJDce`Lxk!+Z*L~<9_$*m79`m|I$G@W2p@96KPokD6TV{1!kFxUGKpYdT+MN26^CNn=I&fi$Au5^TRgemgT;*o%K3^^0v~m&F&qDJ8;Uu6 z3^J5B1kA}dP|ALil^zjXo@kD~{)aHwP_mGMrmq2%@Wj*>SeX(iQ;uC#)|%{tX~p?J zO{QAb>c02_8gLV=&aFJOaC-^_oO{>LQaCF3NLt@=ZxsV`m`muxmbz!uSRt$HuRgWz zZmUcCT;~s8=jPCVSQNEj&5pol;v1BJhNU1paps1zsT7=VmqVr4WpypOOcI}pr66~Ial^YD)Aylan13uR}XTpw4a-qDv?LEMXG*g zV@j-&yDdvrhl(F%r0w8V90X5ZVZCNK>eEz3XcT!T6>u9(FrDKG#d}Tq8>-iHJFDU6 zdU<*JsAxF(Y>L$|N_t4o%ZBxod;&?vdCn|osfo)zNF--^+(*{)kXGXc;}vZuPh|Lq zTpEv$-r_<i!k|0^bOAY!rdxZ*+SS8`5`poU^l=V?9|zQnr-xf;=@rvinTJ6gSRJ}bsFk(E7pN3~}pCU0Ph zhxOC5_}T)EF9Xq}&Qg6eQ^5kWr6f*70w26jC$&O;uKbP$^StRHUp^@dk$Tl~8!(s} zbJhAJ<%>Wyvk3e67_-k4>v?hNpaP+@nJ_{L`2^fR?D5Gf zIBku_SquJ;Vfi4|>$zCA>mHHY)f0l%me-W)cx<2QiVcH9L`thdoUS@*Nny9-Mp;hUTSh6*kv9^ zaenPVi*dA;BOizM(^auntw*eWFUIpe%`+5;bZUQ?q|v?Sx0(~x+1;1P>^f`~XL4rN z&3Q3U5^9*ctxqZiyI{dBusfe0#4(~bC{j`(l|;{B&|!XR&Va{-bw*7i4)^h7CZmw{ z^f_#|W$|702__{}xaTDfk-MLv06AcZdsri{kVk6tbnc}5aD(ktj(R2uVV$9xfW=Gl z1GWJG6Ibknug!8|oFu1_VKcUu>~%l>P{KwQ`!)TKSB6I3!5GYxN%AzyFSiJ(?q9w8 zWsb&`;o$3Mk$D5fkLHiVLdRpKv_eMKCuFQQ$!C~X8o^A)ij>EmrCbKbYU@WT79!twWjQChNycDy1g{sgJbK8v*O zL*A$SzP)Hn6XzF9yt1n%jc8hu@fV;JF}SZ9gMq}F∨WEz|Va{bk-|&;_RAeM9+J zUnh0UX026ZO7^YamIZ5tFXz1xw3~EOReU=#MGVLU&?oU#=?!Q&VOGMa9g3{|}-(rTcEVg5D)DI{a z2GWDRGn4V{waEAz^x84pWqc%zrU@5vy=C+T^*opZy{sEeGZf(2bRQ7&>;3n3xehtByZm`RwV*79njVs~e!)Ed` z;YH~-tZ&NoXtbHSzJwFj7w*SIi)C%{Z8`Yu97yZ~Sa9T+txx2H)bjqO(lY!JZ9$Wd z?q>Zu#8Mn;b2b@_2~w}=Ym!RS<3n-cI9 zwjH50Myt%cFL0csJ&^^?GTKnrwi)*kO{uyhF(kR-koS?7m80R(c zIKR7oLwk9?hVqQyn)s}&`*Z`ke0eNnCvq*jB#li^lS_Wwa*V!tmKv21n> zIWRmXIB*O$9CN=2MaFzX^}(gDA^Pdq3=-`OpZdO+JS|#VtZN9tHt-cWHTu>u-?F#k zg|~2l_XW@B=IvR%<3*oH%%q4gQvvODblS~06nfp|{k(t{nm$Q>+P@j-Oauu{)-!d1p-7KZuoz;VzBBwu( zGi;S^D}Nr}uHT)sBb(kg=cqeOjYzwL|Arc%fB!xIC)99<9!MY@;SLsdNLzul+F$c0JmcRloYIBFCpvZ;0 zrxE)DdEkIRcsapfEub zk(E%lm6nxIgxR484--`O7Ra}MnAP4oqEeT&F}1xzI#dy67AUUdEr3}R#n~WGfR3^Q zioLP0F#|CFV;ah+=45AQg}6g_09<$M$Ll-jLzfTCr4Imu0lE+n7biats*w5sK5l?6 z7!2VAfw_55xBPr4WP^_nHHs>zKB^!RuK{4LyBPnd!@pod{~vUCCq~vXRzqlXO#k@lV>sE{OE+1y!pwuC+l-%Lw!84+cXP;}JYhW}G195Vmt850;vz9?0pz?(k=tBTq|bz2P^i#wnS0J__|f6=t71Hu#+ z)d1YMs6P1X06@TC9x%WZ@J|~E#Rc6R0Gof>c=%CR&wtoJT&RxucN-MKg~ECM-Npst z{qJ~SD5|^u!w>Sm_(7omwJtv&O0xek7s|zr%JkpkLAm+=WnH8L4AtZgckG**g&V3e ns1{VUwMD&4{_NjB+U+0Jazw%$kbl$)%Et@FWnhq0mcsoXXWVEI literal 0 HcmV?d00001 diff --git a/compatibility/backgrounds/backgrounds.feature b/compatibility/backgrounds/backgrounds.feature new file mode 100644 index 00000000..a99e739b --- /dev/null +++ b/compatibility/backgrounds/backgrounds.feature @@ -0,0 +1,17 @@ +Feature: Backgrounds + Though not recommended, Backgrounds can be used to share context steps between Scenarios. The Background steps + are prepended to the steps in each Scenario when they are compiled to Pickles. Only one Background at the Feature + level is supported. + + Background: + Given an order for "eggs" + And an order for "milk" + And an order for "bread" + + Scenario: one scenario + When an action + Then an outcome + + Scenario: another scenario + When an action + Then an outcome \ No newline at end of file diff --git a/compatibility/backgrounds/backgrounds.ndjson b/compatibility/backgrounds/backgrounds.ndjson new file mode 100644 index 00000000..dd1446a6 --- /dev/null +++ b/compatibility/backgrounds/backgrounds.ndjson @@ -0,0 +1,36 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Backgrounds\n Though not recommended, Backgrounds can be used to share context steps between Scenarios. The Background steps\n are prepended to the steps in each Scenario when they are compiled to Pickles. Only one Background at the Feature\n level is supported.\n\n Background:\n Given an order for \"eggs\"\n And an order for \"milk\"\n And an order for \"bread\"\n\n Scenario: one scenario\n When an action\n Then an outcome\n\n Scenario: another scenario\n When an action\n Then an outcome","uri":"samples/backgrounds/backgrounds.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Backgrounds","description":" Though not recommended, Backgrounds can be used to share context steps between Scenarios. The Background steps\n are prepended to the steps in each Scenario when they are compiled to Pickles. Only one Background at the Feature\n level is supported.","children":[{"background":{"id":"3","location":{"line":6,"column":3},"keyword":"Background","name":"","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"eggs\""},{"id":"1","location":{"line":8,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"milk\""},{"id":"2","location":{"line":9,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"bread\""}]}},{"scenario":{"id":"6","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"one scenario","description":"","steps":[{"id":"4","location":{"line":12,"column":5},"keyword":"When ","keywordType":"Action","text":"an action"},{"id":"5","location":{"line":13,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"an outcome"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":15,"column":3},"keyword":"Scenario","name":"another scenario","description":"","steps":[{"id":"7","location":{"line":16,"column":5},"keyword":"When ","keywordType":"Action","text":"an action"},{"id":"8","location":{"line":17,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"an outcome"}],"examples":[]}}]},"comments":[],"uri":"samples/backgrounds/backgrounds.feature"}} +{"pickle":{"id":"15","uri":"samples/backgrounds/backgrounds.feature","location":{"line":11,"column":3},"astNodeIds":["6"],"tags":[],"name":"one scenario","language":"en","steps":[{"id":"10","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]},{"id":"11","text":"an order for \"milk\"","type":"Context","astNodeIds":["1"]},{"id":"12","text":"an order for \"bread\"","type":"Context","astNodeIds":["2"]},{"id":"13","text":"an action","type":"Action","astNodeIds":["4"]},{"id":"14","text":"an outcome","type":"Outcome","astNodeIds":["5"]}]}} +{"pickle":{"id":"21","uri":"samples/backgrounds/backgrounds.feature","location":{"line":15,"column":3},"astNodeIds":["9"],"tags":[],"name":"another scenario","language":"en","steps":[{"id":"16","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]},{"id":"17","text":"an order for \"milk\"","type":"Context","astNodeIds":["1"]},{"id":"18","text":"an order for \"bread\"","type":"Context","astNodeIds":["2"]},{"id":"19","text":"an action","type":"Action","astNodeIds":["7"]},{"id":"20","text":"an outcome","type":"Outcome","astNodeIds":["8"]}]}} +{"stepDefinition":{"id":"22","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an order for {string}"},"sourceReference":{"uri":"samples/backgrounds/backgrounds.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"23","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an action"},"sourceReference":{"uri":"samples/backgrounds/backgrounds.ts","location":{"line":7}}}} +{"stepDefinition":{"id":"24","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an outcome"},"sourceReference":{"uri":"samples/backgrounds/backgrounds.ts","location":{"line":11}}}} +{"testRunStarted":{"id":"25","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"26","pickleId":"15","testSteps":[{"id":"27","pickleStepId":"10","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"eggs\"","children":[{"start":14,"value":"eggs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"28","pickleStepId":"11","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"milk\"","children":[{"start":14,"value":"milk","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"29","pickleStepId":"12","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"bread\"","children":[{"start":14,"value":"bread","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"30","pickleStepId":"13","stepDefinitionIds":["23"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"31","pickleStepId":"14","stepDefinitionIds":["24"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"25"}} +{"testCase":{"id":"32","pickleId":"21","testSteps":[{"id":"33","pickleStepId":"16","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"eggs\"","children":[{"start":14,"value":"eggs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"34","pickleStepId":"17","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"milk\"","children":[{"start":14,"value":"milk","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"35","pickleStepId":"18","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"bread\"","children":[{"start":14,"value":"bread","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"36","pickleStepId":"19","stepDefinitionIds":["23"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"37","pickleStepId":"20","stepDefinitionIds":["24"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"25"}} +{"testCaseStarted":{"id":"38","testCaseId":"26","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"27","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"27","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"28","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"28","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"29","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"29","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"30","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"30","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"31","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"31","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"38","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"39","testCaseId":"32","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"33","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"33","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"34","timestamp":{"seconds":0,"nanos":16000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"34","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":17000000}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"35","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"35","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"36","timestamp":{"seconds":0,"nanos":20000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"36","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"37","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"37","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testCaseFinished":{"testCaseStartedId":"39","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"25","timestamp":{"seconds":0,"nanos":25000000},"success":true}} diff --git a/compatibility/backgrounds/backgrounds.ts b/compatibility/backgrounds/backgrounds.ts new file mode 100644 index 00000000..cb475f6e --- /dev/null +++ b/compatibility/backgrounds/backgrounds.ts @@ -0,0 +1,13 @@ +import { Given, When, Then } from '@cucumber/fake-cucumber' + +Given('an order for {string}', () => { + // no-op +}) + +When('an action', () => { + // no-op +}) + +Then('an outcome', () => { + // no-op +}) diff --git a/compatibility/cdata/cdata.feature b/compatibility/cdata/cdata.feature new file mode 100644 index 00000000..ca75bff7 --- /dev/null +++ b/compatibility/cdata/cdata.feature @@ -0,0 +1,5 @@ +Feature: cdata + Cucumber xml formatters should be able to handle xml cdata elements. + + Scenario: cdata + Given I have 42 in my belly diff --git a/compatibility/cdata/cdata.ndjson b/compatibility/cdata/cdata.ndjson new file mode 100644 index 00000000..fe2f10f5 --- /dev/null +++ b/compatibility/cdata/cdata.ndjson @@ -0,0 +1,12 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: cdata\n Cucumber xml formatters should be able to handle xml cdata elements.\n\n Scenario: cdata\n Given I have 42 in my belly\n","uri":"samples/cdata/cdata.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"cdata","description":" Cucumber xml formatters should be able to handle xml cdata elements.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"cdata","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"Given ","keywordType":"Context","text":"I have 42 in my belly"}],"examples":[]}}]},"comments":[],"uri":"samples/cdata/cdata.feature"}} +{"pickle":{"id":"3","uri":"samples/cdata/cdata.feature","location":{"line":4,"column":3},"astNodeIds":["1"],"tags":[],"name":"cdata","language":"en","steps":[{"id":"2","text":"I have 42 in my belly","type":"Context","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I have {int} in my belly"},"sourceReference":{"uri":"samples/cdata/cdata.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":7,"value":"42","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"5"}} +{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":true}} diff --git a/compatibility/cdata/cdata.ts b/compatibility/cdata/cdata.ts new file mode 100644 index 00000000..aebf8e5a --- /dev/null +++ b/compatibility/cdata/cdata.ts @@ -0,0 +1,5 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('I have {int} in my belly', function (cukeCount: number) { + // no-op +}) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp new file mode 100644 index 00000000..35a88163 --- /dev/null +++ b/compatibility/compatibility.cpp @@ -0,0 +1,265 @@ +#include "cucumber/messages/envelope.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/api/RunCucumber.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "library/support/Duration.hpp" +#include "nlohmann/json.hpp" +#include "nlohmann/json_fwd.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifndef KIT_FOLDER +#error KIT_FOLDER is not defined +#define KIT_FOLDER "" +#endif + +#ifndef KIT_NDJSON_FILE +#error KIT_NDJSON_FILE is not defined +#define KIT_NDJSON_FILE "" +#endif + +namespace compatibility +{ + namespace + { + struct Devkit + { + std::vector paths; + std::string tagExpression; + std::size_t retry; + std::filesystem::path ndjsonFile; + }; + + Devkit LoadDevkit() + { + return { + .paths = { KIT_FOLDER }, + .tagExpression = "", + .retry = 0, + .ndjsonFile = KIT_NDJSON_FILE + }; + } + + void SanitizeExpectedJson(nlohmann::json& json) + { + for (auto& [key, value] : json.items()) + { + if (value.is_object()) + SanitizeExpectedJson(value); + else if (value.is_array()) + { + for (auto& item : value) + if (item.is_object()) + SanitizeExpectedJson(item); + } + else if (key == "uri") + { + json[key] = std::regex_replace(value.get(), std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); + json[key] = std::regex_replace(value.get(), std::regex(R"(\.ts$)"), ".cpp"); + } + else if (key == "line") + json.erase(key); + else if (key == "start") + json.erase(key); + } + } + + void SanitizeActualJson(nlohmann::json& json) + { + for (auto& [key, value] : json.items()) + { + if (value.is_object()) + SanitizeActualJson(value); + else if (value.is_array()) + { + for (auto& item : value) + if (item.is_object()) + SanitizeActualJson(item); + } + else if (key == "line") + json.erase(key); + } + } + + struct BroadcastListener + { + BroadcastListener(std::filesystem::path ndjsonin, std::filesystem::path ndout, cucumber_cpp::library::util::Broadcaster& broadcaster) + : listener(broadcaster, [this](const cucumber::messages::envelope& envelope) + { + OnEvent(envelope); + }) + , ndjsonin{ std::move(ndjsonin) } + , ndout(std::move(ndout)) + { + while (!ifs.eof()) + { + std::string line; + std::getline(ifs, line); + + if (line.empty()) + continue; + + auto json = nlohmann::json::parse(line); + + if (json.contains("meta")) + continue; + + SanitizeExpectedJson(json); + + expectedEnvelopes.emplace_back(std::move(json)); + } + } + + void OnEvent(const cucumber::messages::envelope& envelope) + { + nlohmann::json actualJson{}; + to_json(actualJson, envelope); + + ofs << envelope.to_json() << "\n"; + + if (expectedEnvelopes.empty()) + { + std::cerr << "Unexpected envelope: " << actualJson.dump() << "\n\n"; + return; + } + + SanitizeActualJson(actualJson); + + const auto expectedJson = expectedEnvelopes.front(); + expectedEnvelopes.pop_front(); + + const auto expectedMessage = expectedJson.items().begin().key(); + + const auto diff = nlohmann::json::diff(expectedJson, actualJson); + + if (actualJson.contains(expectedMessage)) + { + if (actualJson[expectedMessage] == expectedJson[expectedMessage]) + { + std::cout << "matching!!!! " << expectedMessage << "\n\n"; + } + else + { + std::cerr << std::format("Mismatch {}: {}\n", expectedMessage, diff.dump()); + std::cerr << "expected: " << expectedJson[expectedMessage] << "\n"; + std::cerr << "actual : " << actualJson[expectedMessage] << "\n\n"; + } + } + else + { + std::cerr << std::format("Missing {}: {}\n", expectedMessage, diff.dump()); + } + } + + private: + cucumber_cpp::library::util::Listener listener; + std::filesystem::path ndjsonin; + std::ifstream ifs{ ndjsonin }; + + std::filesystem::path ndout; + std::ofstream ofs{ ndout }; + + std::list expectedEnvelopes; + }; + + bool IsFeatureFile(const std::filesystem::directory_entry& entry) + { + return std::filesystem::is_regular_file(entry) && entry.path().has_extension() && entry.path().extension() == ".feature"; + } + + std::vector GetFeatureFiles(std::vector paths) + { + std::vector files; + + for (const auto feature : paths) + if (std::filesystem::is_directory(feature)) + for (const auto& entry : std::filesystem::directory_iterator{ feature } | std::views::filter(IsFeatureFile)) + files.emplace_back(entry.path()); + else + files.emplace_back(feature); + + return files; + } + + struct StopwatchIncremental : cucumber_cpp::library::support::Stopwatch + { + virtual ~StopwatchIncremental() = default; + + void Start() override + { + } + + std::chrono::nanoseconds Duration() override + { + return current; + } + + std::chrono::nanoseconds current{ std::chrono::milliseconds{ 1 } }; + }; + + struct TimestampGeneratorIncremental : cucumber_cpp::library::support::TimestampGenerator + { + virtual ~TimestampGeneratorIncremental() = default; + + std::chrono::milliseconds Now() override + { + return current++; + } + + std::chrono::milliseconds current{ 0 }; + }; + + void RunDevkit(Devkit devkit) + { + devkit.paths = GetFeatureFiles(devkit.paths); + + cucumber_cpp::library::support::RunOptions runOptions{ + .sources = { + .paths = devkit.paths, + .tagExpression = devkit.tagExpression, + }, + .runtime = { + .retry = devkit.retry, + }, + }; + + cucumber_cpp::library::cucumber_expression::ParameterRegistry parameterRegistry{}; + + auto contextStorageFactory{ std::make_shared() }; + auto programContext{ std::make_unique(contextStorageFactory) }; + + cucumber_cpp::library::util::Broadcaster broadcaster; + + BroadcastListener broadcastListener{ devkit.ndjsonFile, devkit.ndjsonFile.parent_path() / "out.ndjson", broadcaster }; + + cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster); + // EXPECT_NONFATAL_FAILURE(cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster), ""); + // EXPECT_FATAL_FAILURE(cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster), ""); + } + } +} + +TEST(CompatibilityTest, KIT_NAME) +{ + compatibility::StopwatchIncremental stopwatch; + compatibility::TimestampGeneratorIncremental timestampGenerator; + compatibility::RunDevkit(compatibility::LoadDevkit()); +} diff --git a/compatibility/data-tables/data-tables.cpp b/compatibility/data-tables/data-tables.cpp new file mode 100644 index 00000000..ab88df3a --- /dev/null +++ b/compatibility/data-tables/data-tables.cpp @@ -0,0 +1,52 @@ +#include "cucumber/messages/pickle_table_row.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include +#include +#include + +STEP(R"(the following table is transposed:)") +{ + std::vector transposedTable; + transposedTable.reserve(this->table->front().cells.size()); + for (std::size_t colIdx = 0; colIdx < this->table->front().cells.size(); ++colIdx) + transposedTable.emplace_back().cells.resize(this->table->size()); + + for (std::size_t rowIdx = 0; rowIdx < this->table->size(); ++rowIdx) + for (std::size_t colIdx = 0; colIdx < this->table->begin()[rowIdx].cells.size(); ++colIdx) + transposedTable[colIdx].cells[rowIdx] = this->table->begin()[rowIdx].cells[colIdx]; + + context.Insert(transposedTable); +} + +STEP(R"(it should be:)") +{ + std::span expected = context.Get>(); + const auto& actual = *table; + const auto rows = expected.size(); + ASSERT_THAT(rows, testing::Eq(actual.size())); + for (auto rowIdx = 0; rowIdx < rows; ++rowIdx) + { + const auto columns = expected[rowIdx].cells.size(); + ASSERT_THAT(columns, testing::Eq(actual[rowIdx].cells.size())); + for (auto colIdx = 0; colIdx < columns; ++colIdx) + { + const auto& expectedCell = expected[rowIdx].cells[colIdx]; + const auto& actualCell = actual[rowIdx].cells[colIdx]; + EXPECT_THAT(expectedCell.value, testing::StrEq(actualCell.value)) << "at row " << rowIdx << " column " << colIdx; + } + } +} + +// import assert from 'node:assert' +// import { DataTable, When, Then } from '@cucumber/fake-cucumber' + +// When('the following table is transposed:', function (table: DataTable) { +// this.transposed = table.transpose() +// }) + +// Then('it should be:', function (expected: DataTable) { +// assert.deepStrictEqual(this.transposed.raw(), expected.raw()) +// } diff --git a/compatibility/data-tables/data-tables.feature b/compatibility/data-tables/data-tables.feature new file mode 100644 index 00000000..2822419e --- /dev/null +++ b/compatibility/data-tables/data-tables.feature @@ -0,0 +1,13 @@ +Feature: Data Tables + Data Tables can be placed underneath a step and will be passed as the last + argument to the step definition. + + They can be used to represent richer data structures, and can be transformed to other data-types. + + Scenario: transposed table + When the following table is transposed: + | a | b | + | 1 | 2 | + Then it should be: + | a | 1 | + | b | 2 | diff --git a/compatibility/data-tables/data-tables.ndjson b/compatibility/data-tables/data-tables.ndjson new file mode 100644 index 00000000..de951db0 --- /dev/null +++ b/compatibility/data-tables/data-tables.ndjson @@ -0,0 +1,15 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Data Tables\n Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.\n\n Scenario: transposed table\n When the following table is transposed:\n | a | b |\n | 1 | 2 |\n Then it should be:\n | a | 1 |\n | b | 2 |\n","uri":"samples/data-tables/data-tables.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Data Tables","description":" Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.","children":[{"scenario":{"id":"6","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"transposed table","description":"","steps":[{"id":"2","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"the following table is transposed:","dataTable":{"location":{"line":9,"column":7},"rows":[{"id":"0","location":{"line":9,"column":7},"cells":[{"location":{"line":9,"column":9},"value":"a"},{"location":{"line":9,"column":13},"value":"b"}]},{"id":"1","location":{"line":10,"column":7},"cells":[{"location":{"line":10,"column":9},"value":"1"},{"location":{"line":10,"column":13},"value":"2"}]}]}},{"id":"5","location":{"line":11,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"it should be:","dataTable":{"location":{"line":12,"column":7},"rows":[{"id":"3","location":{"line":12,"column":7},"cells":[{"location":{"line":12,"column":9},"value":"a"},{"location":{"line":12,"column":13},"value":"1"}]},{"id":"4","location":{"line":13,"column":7},"cells":[{"location":{"line":13,"column":9},"value":"b"},{"location":{"line":13,"column":13},"value":"2"}]}]}}],"examples":[]}}]},"comments":[],"uri":"samples/data-tables/data-tables.feature"}} +{"pickle":{"id":"9","uri":"samples/data-tables/data-tables.feature","location":{"line":7,"column":3},"astNodeIds":["6"],"tags":[],"name":"transposed table","language":"en","steps":[{"id":"7","text":"the following table is transposed:","type":"Action","argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"b"}]},{"cells":[{"value":"1"},{"value":"2"}]}]}},"astNodeIds":["2"]},{"id":"8","text":"it should be:","type":"Outcome","argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"1"}]},{"cells":[{"value":"b"},{"value":"2"}]}]}},"astNodeIds":["5"]}]}} +{"stepDefinition":{"id":"10","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the following table is transposed:"},"sourceReference":{"uri":"samples/data-tables/data-tables.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"11","pattern":{"type":"CUCUMBER_EXPRESSION","source":"it should be:"},"sourceReference":{"uri":"samples/data-tables/data-tables.ts","location":{"line":8}}}} +{"testRunStarted":{"id":"12","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"13","pickleId":"9","testSteps":[{"id":"14","pickleStepId":"7","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"15","pickleStepId":"8","stepDefinitionIds":["11"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"12"}} +{"testCaseStarted":{"id":"16","testCaseId":"13","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"16","testStepId":"14","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"16","testStepId":"14","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"16","testStepId":"15","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"16","testStepId":"15","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testCaseFinished":{"testCaseStartedId":"16","timestamp":{"seconds":0,"nanos":6000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"12","timestamp":{"seconds":0,"nanos":7000000},"success":true}} diff --git a/compatibility/data-tables/data-tables.ts b/compatibility/data-tables/data-tables.ts new file mode 100644 index 00000000..e778898a --- /dev/null +++ b/compatibility/data-tables/data-tables.ts @@ -0,0 +1,10 @@ +import assert from 'node:assert' +import { DataTable, When, Then } from '@cucumber/fake-cucumber' + +When('the following table is transposed:', function (table: DataTable) { + this.transposed = table.transpose() +}) + +Then('it should be:', function (expected: DataTable) { + assert.deepStrictEqual(this.transposed.raw(), expected.raw()) +}) diff --git a/compatibility/data-tables/out.ndjson b/compatibility/data-tables/out.ndjson new file mode 100644 index 00000000..a72cd150 --- /dev/null +++ b/compatibility/data-tables/out.ndjson @@ -0,0 +1,14 @@ +{"source":{"data":"Feature: Data Tables\n Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.\n\n Scenario: transposed table\n When the following table is transposed:\n | a | b |\n | 1 | 2 |\n Then it should be:\n | a | 1 |\n | b | 2 |\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"6","keyword":"Scenario","location":{"column":3,"line":7},"name":"transposed table","steps":[{"dataTable":{"location":{"column":7,"line":9},"rows":[{"cells":[{"location":{"column":9,"line":9},"value":"a"},{"location":{"column":13,"line":9},"value":"b"}],"id":"0","location":{"column":7,"line":9}},{"cells":[{"location":{"column":9,"line":10},"value":"1"},{"location":{"column":13,"line":10},"value":"2"}],"id":"1","location":{"column":7,"line":10}}]},"id":"2","keyword":"When ","keywordType":"Action","location":{"column":5,"line":8},"text":"the following table is transposed:"},{"dataTable":{"location":{"column":7,"line":12},"rows":[{"cells":[{"location":{"column":9,"line":12},"value":"a"},{"location":{"column":13,"line":12},"value":"1"}],"id":"3","location":{"column":7,"line":12}},{"cells":[{"location":{"column":9,"line":13},"value":"b"},{"location":{"column":13,"line":13},"value":"2"}],"id":"4","location":{"column":7,"line":13}}]},"id":"5","keyword":"Then ","keywordType":"Outcome","location":{"column":5,"line":11},"text":"it should be:"}],"tags":[]}}],"description":" Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Data Tables","tags":[]},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.feature"}} +{"pickle":{"astNodeIds":["6"],"id":"9","language":"en","location":{"column":3,"line":7},"name":"transposed table","steps":[{"argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"b"}]},{"cells":[{"value":"1"},{"value":"2"}]}]}},"astNodeIds":["2"],"id":"7","text":"the following table is transposed:","type":"Action"},{"argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"1"}]},{"cells":[{"value":"b"},{"value":"2"}]}]}},"astNodeIds":["5"],"id":"8","text":"it should be:","type":"Outcome"}],"tags":[],"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.feature"}} +{"stepDefinition":{"id":"10","pattern":{"source":"the following table is transposed:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":10},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.cpp"}}} +{"stepDefinition":{"id":"11","pattern":{"source":"it should be:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":24},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.cpp"}}} +{"testRunStarted":{"id":"12","timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"13","pickleId":"9","testRunStartedId":"12","testSteps":[{"id":"14","pickleStepId":"7","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"15","pickleStepId":"8","stepDefinitionIds":["11"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"16","testCaseId":"13","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"16","testStepId":"14","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"16","testStepId":"14","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"16","testStepId":"15","timestamp":{"nanos":4000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"16","testStepId":"15","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":5000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"16","timestamp":{"nanos":6000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"testRunStartedId":"12","timestamp":{"nanos":7000000,"seconds":0}}} diff --git a/compatibility/doc-strings/doc-strings.feature b/compatibility/doc-strings/doc-strings.feature new file mode 100644 index 00000000..c3917189 --- /dev/null +++ b/compatibility/doc-strings/doc-strings.feature @@ -0,0 +1,31 @@ +Feature: Doc strings + Doc strings are a way to supply long, sometimes multi-line, text to a step. They are passed as the last argument + to the step definition. + + Scenario: a doc string with standard delimiter + Three double quotes above and below are the standard delimiter for doc strings. + + Given a doc string: + """ + Here is some content + And some more on another line + """ + + Scenario: a doc string with backticks delimiter + Backticks can also be used, like Markdown, but are less widely supported by editors. + + Given a doc string: + ``` + Here is some content + And some more on another line + ``` + + Scenario: a doc string with media type + The media type can be optionally specified too, following the opening delimiter. + + Given a doc string: + """application/json + { + "foo": "bar" + } + """ \ No newline at end of file diff --git a/compatibility/doc-strings/doc-strings.ndjson b/compatibility/doc-strings/doc-strings.ndjson new file mode 100644 index 00000000..95b8c7b4 --- /dev/null +++ b/compatibility/doc-strings/doc-strings.ndjson @@ -0,0 +1,24 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Doc strings\n Doc strings are a way to supply long, sometimes multi-line, text to a step. They are passed as the last argument\n to the step definition.\n\n Scenario: a doc string with standard delimiter\n Three double quotes above and below are the standard delimiter for doc strings.\n\n Given a doc string:\n \"\"\"\n Here is some content\n And some more on another line\n \"\"\"\n\n Scenario: a doc string with backticks delimiter\n Backticks can also be used, like Markdown, but are less widely supported by editors.\n\n Given a doc string:\n ```\n Here is some content\n And some more on another line\n ```\n\n Scenario: a doc string with media type\n The media type can be optionally specified too, following the opening delimiter.\n\n Given a doc string:\n \"\"\"application/json\n {\n \"foo\": \"bar\"\n }\n \"\"\"","uri":"samples/doc-strings/doc-strings.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Doc strings","description":" Doc strings are a way to supply long, sometimes multi-line, text to a step. They are passed as the last argument\n to the step definition.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"a doc string with standard delimiter","description":" Three double quotes above and below are the standard delimiter for doc strings.","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"a doc string:","docString":{"location":{"line":9,"column":5},"content":"Here is some content\nAnd some more on another line","delimiter":"\"\"\""}}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":14,"column":3},"keyword":"Scenario","name":"a doc string with backticks delimiter","description":" Backticks can also be used, like Markdown, but are less widely supported by editors.","steps":[{"id":"2","location":{"line":17,"column":5},"keyword":"Given ","keywordType":"Context","text":"a doc string:","docString":{"location":{"line":18,"column":5},"content":"Here is some content\nAnd some more on another line","delimiter":"```"}}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":23,"column":3},"keyword":"Scenario","name":"a doc string with media type","description":" The media type can be optionally specified too, following the opening delimiter.","steps":[{"id":"4","location":{"line":26,"column":5},"keyword":"Given ","keywordType":"Context","text":"a doc string:","docString":{"location":{"line":27,"column":5},"content":"{\n \"foo\": \"bar\"\n}","delimiter":"\"\"\"","mediaType":"application/json"}}],"examples":[]}}]},"comments":[],"uri":"samples/doc-strings/doc-strings.feature"}} +{"pickle":{"id":"7","uri":"samples/doc-strings/doc-strings.feature","location":{"line":5,"column":3},"astNodeIds":["1"],"tags":[],"name":"a doc string with standard delimiter","language":"en","steps":[{"id":"6","text":"a doc string:","type":"Context","argument":{"docString":{"content":"Here is some content\nAnd some more on another line"}},"astNodeIds":["0"]}]}} +{"pickle":{"id":"9","uri":"samples/doc-strings/doc-strings.feature","location":{"line":14,"column":3},"astNodeIds":["3"],"tags":[],"name":"a doc string with backticks delimiter","language":"en","steps":[{"id":"8","text":"a doc string:","type":"Context","argument":{"docString":{"content":"Here is some content\nAnd some more on another line"}},"astNodeIds":["2"]}]}} +{"pickle":{"id":"11","uri":"samples/doc-strings/doc-strings.feature","location":{"line":23,"column":3},"astNodeIds":["5"],"tags":[],"name":"a doc string with media type","language":"en","steps":[{"id":"10","text":"a doc string:","type":"Context","argument":{"docString":{"content":"{\n \"foo\": \"bar\"\n}","mediaType":"application/json"}},"astNodeIds":["4"]}]}} +{"stepDefinition":{"id":"12","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a doc string:"},"sourceReference":{"uri":"samples/doc-strings/doc-strings.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"13","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"14","pickleId":"7","testSteps":[{"id":"15","pickleStepId":"6","stepDefinitionIds":["12"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"13"}} +{"testCase":{"id":"16","pickleId":"9","testSteps":[{"id":"17","pickleStepId":"8","stepDefinitionIds":["12"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"13"}} +{"testCase":{"id":"18","pickleId":"11","testSteps":[{"id":"19","pickleStepId":"10","stepDefinitionIds":["12"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"13"}} +{"testCaseStarted":{"id":"20","testCaseId":"14","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"20","testStepId":"15","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"20","testStepId":"15","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"20","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"21","testCaseId":"16","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"21","testStepId":"17","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"21","testStepId":"17","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"21","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"22","testCaseId":"18","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"19","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"19","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"22","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"13","timestamp":{"seconds":0,"nanos":13000000},"success":true}} diff --git a/compatibility/doc-strings/doc-strings.ts b/compatibility/doc-strings/doc-strings.ts new file mode 100644 index 00000000..ba72d417 --- /dev/null +++ b/compatibility/doc-strings/doc-strings.ts @@ -0,0 +1,5 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('a doc string:', (docString: string) => { + // no-op +}) diff --git a/compatibility/empty/empty.cpp b/compatibility/empty/empty.cpp new file mode 100644 index 00000000..ff514795 --- /dev/null +++ b/compatibility/empty/empty.cpp @@ -0,0 +1,6 @@ +#include +#include + +namespace cucumber_cpp::devkit +{ +} diff --git a/compatibility/empty/empty.feature b/compatibility/empty/empty.feature new file mode 100644 index 00000000..e9767eff --- /dev/null +++ b/compatibility/empty/empty.feature @@ -0,0 +1,7 @@ +Feature: Empty Scenarios + Sometimes we want to quickly jot down a new scenario without specifying any actual steps + for what should be executed. + + In this instance we want to stipulate what should / shouldn't run and what the output is. + + Scenario: Blank Scenario diff --git a/compatibility/empty/empty.ndjson b/compatibility/empty/empty.ndjson new file mode 100644 index 00000000..f90ca578 --- /dev/null +++ b/compatibility/empty/empty.ndjson @@ -0,0 +1,9 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Empty Scenarios\n Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.\n\n Scenario: Blank Scenario\n","uri":"samples/empty/empty.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Empty Scenarios","description":" Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.","children":[{"scenario":{"id":"0","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"Blank Scenario","description":"","steps":[],"examples":[]}}]},"comments":[],"uri":"samples/empty/empty.feature"}} +{"pickle":{"id":"1","uri":"samples/empty/empty.feature","location":{"line":7,"column":3},"astNodeIds":["0"],"tags":[],"name":"Blank Scenario","language":"en","steps":[]}} +{"testRunStarted":{"id":"2","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"3","pickleId":"1","testSteps":[],"testRunStartedId":"2"}} +{"testCaseStarted":{"id":"4","testCaseId":"3","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testCaseFinished":{"testCaseStartedId":"4","timestamp":{"seconds":0,"nanos":2000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"2","timestamp":{"seconds":0,"nanos":3000000},"success":true}} diff --git a/compatibility/empty/out.ndjson b/compatibility/empty/out.ndjson new file mode 100644 index 00000000..4da1f103 --- /dev/null +++ b/compatibility/empty/out.ndjson @@ -0,0 +1,8 @@ +{"source":{"data":"Feature: Empty Scenarios\n Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.\n\n Scenario: Blank Scenario\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/empty/empty.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"0","keyword":"Scenario","location":{"column":3,"line":7},"name":"Blank Scenario","steps":[],"tags":[]}}],"description":" Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Empty Scenarios","tags":[]},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/empty/empty.feature"}} +{"pickle":{"astNodeIds":["0"],"id":"1","language":"en","location":{"column":3,"line":7},"name":"Blank Scenario","steps":[],"tags":[],"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/empty/empty.feature"}} +{"testRunStarted":{"id":"2","timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"3","pickleId":"1","testRunStartedId":"2","testSteps":[]}} +{"testCaseStarted":{"attempt":0,"id":"4","testCaseId":"3","timestamp":{"nanos":1000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"4","timestamp":{"nanos":2000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"testRunStartedId":"2","timestamp":{"nanos":3000000,"seconds":0}}} diff --git a/compatibility/examples-tables-attachment/cucumber.jpeg b/compatibility/examples-tables-attachment/cucumber.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..e833d6c77d33e620ae49d01c6341ad7e5543bbd1 GIT binary patch literal 1444 zcmex=ma3|jiIItm zOAI4iKMQ#V{6EAX$ibk;pvlar#K0uT$SlbC{|JK&&=V}oKwrQC2Ma43I|Cy#)Bjrx z93W3JFj%5R(9qV{Ntcq}Ky~tP0uLAPSj53Js=3YaT3(saOkCY9;`5A|n$EGYdN> z$V3JPCPrqUBN+tQg%k~il>!AEoq|My6AOzQCy9zF8yTB~CKo{+&uGtZX0w)!P@n75 z+pl=GZ!o)2TKe4kXH-t@v#4EXEL!JE?zOu6r)a^it+7!xlS1t8{b#8CcjV%O8K0Lw zw!5=@{{4E%lYXmb|9XFR)y|pcq!QHyPd&4_T>Dx#CekoQenQBu?LV{I?)a=Z|5`qv zFCb50m1$zkt9Lo_J0{jC=okGqK3#m~$Ha_v^0sbzj}ElmKM-8K@73}%+E1_Sj1$h= za&Sv<=wdZVW0znRzq!_L_bo^lOYMz6H&?{%)XoFh(vNfajh)BRw*tXt#N5h?h}`dZpTv+M2|iZeX+YndxfsS7m0HMKkjMk z3R-;5(6ii>*OK{q>6sTyS60rQly}eE#LW&5BG%-(C1!D;fUT^{(mfa_yh< z{#u>M^5=OJ`#1Z*!uSb$%D$efQTh0vq2T`C;?gU-q$)Q@ZJHi;`qC^B2V>vgrM{Op zz2Y^CHRFrdI~bc)vyq`s=08J-S2f$|3$=&W)J@FMUY+(-GI%Z`A@L?@)(B$FN=Q(t=#l1^;xf6 ztKqtSx0-4T?$s+Kr!hS|dgkT)$(;KdOXF_sl{kL&qTE`s1rg3$Iy;OF^4Vh9&`X%c- z?@Ir1`muNQ?HGfD#&eeSnRs0PY8_Wf0x zYr%isu|McfyL=m%q*VpMX&9LEAPE|n37MD~SOi&xgcSot42=?%9Sb43h7p*8_w1>- z6Mg&7uRHgQ{q+{+?($T>Bl_*Z+yh@{H3b$5c{NME=w}I8|8lFOYSF`(6w%|A@+BXl zmusIkS2)Mu_D0Bf{>CWJM_W~opFJsmFzB$zE1{4Rf8~=jG8xtK!v7@pU6p$h?!dst zAi;cKJ=ASXzzoOCisUvHRt7dfAw$K$LSa!QBS)~?m_cs48@=pNOiq~YFVP2{3ZGA2 z+`D6=>s#ATRX)mE@f(tl=gZwXkQQv**LRUIg?&x;o5PWIH+oN(d8?&{>zqn{JdOYJ zpDR~h9MUx8cipWf_R8znWtJ&uK2N+Xmx{{Kw?qP#Uw literal 0 HcmV?d00001 diff --git a/compatibility/examples-tables-attachment/cucumber.png b/compatibility/examples-tables-attachment/cucumber.png new file mode 100644 index 0000000000000000000000000000000000000000..2760899aa0ce16aeaaea93d3118a32e7ac5bec8d GIT binary patch literal 1739 zcmV;+1~mDJP)G+r@kQ0VOJz1t=x5=pe3owlM@Ei<#*ZtpGI zgKd}Iv=WU6L@O5L5Qs#)gFvk*7OQ`VCsjOx5<>9^RV-lQA0`qL#Hi6fjMC@#y`7oe zS+={g-EDP}m+f}vz4x2%_`Ec`caU@Gwv+u9&;ebh;EPxToe6TQSS7$RIEHqf|0i z0O+ z#n5`Lu?_+l5a?76FBT_ov)oVPu)W&2iyqQk6q$E2`kQlqo37e8-)c+O?QiP<=H+=W zgC{tLKe`UI8wSZ$`}IIZS7^VP!&SQ9uqqj{=4V~1HP0i=kNxUV+pBnmxV8-q#Bxu7a+-a2h-P!vK`Neu>T>`ydm{x2F*T=U zFja;RD&fUT?=ygOHKmEIjm9$XXg4i$m^A1Td}8TX_oouO^1 zftu}jxiw*Sqct@^-zMG^t)7q>c1_O=)^$NOakew|C1vwbb(rq-=>$Sb3W6-RZHa~m zCJ-n2XtkjN1C$hf07a9cuvH0K135T-J;#nfDUID~Sv*Yz!r1hE3|a|Q4_9AFEWLHK zWz~oq!$8|OVjZ(mf6Y*Z+@G&h%4HXA<`4i@t!XO}{GYwU7nxru-K+|Xtu%RHv$V5t zB%F}kxeBctB<7atq+W_2XaHsIuS|aaB@`qli21Zmg1ge~X)cg!Z$@+@T2Kf6wH3yz z&3Yvsd2vMF!t&A=G!b@&(o#XX>H~4DCJ(w*r-TNAhSk2Rta1dYR_3O9WK; zw#R~odmNn4Rrysbu1(x67Wr<38~#FiG1`n}wO&7`@#)Xk3f`-e^xfhbRWBNCp0aZ6 zG7j-w;#H4aR;4%|7`O;-+)Hl;uEM%I>mVnijiapH>2)nQ~8L|KJMiw<@G*t1yFW9;lFYc#uS`DwjogG)sqA zR1ZXz{)UVWQ#sGl0$Aj@M%qBOs8U>G?P+&{nu~!`wccEicA`NtKbHn#g?=au3r^~) z3?!GiqlV-fJj2A0p}mr}21v220F29iQ1fLV>-W%pUD-_S!;=aJIQ+*`W9=9^HPvL{ z0;-_op8_N*ciN&xk6wP@c=5J5d#{rWB-U?ag_zePQ!sYV&o8}(nae94B=$6zwdRg$ z{h^gdI-K$AkW4|MJIzg%@h!p>_RSE#rObbMsUUTO7bj4~!21~&^MANd_d&CC2wth| hfX@)Vpvv|h{{nxzjlgvW5(5AL002ovPDHLkV1gBrLk9o= literal 0 HcmV?d00001 diff --git a/compatibility/examples-tables-attachment/examples-tables-attachment.feature b/compatibility/examples-tables-attachment/examples-tables-attachment.feature new file mode 100644 index 00000000..b8e6466a --- /dev/null +++ b/compatibility/examples-tables-attachment/examples-tables-attachment.feature @@ -0,0 +1,10 @@ +Feature: Examples Tables - With attachments + It is sometimes useful to take a screenshot while a scenario runs or capture some logs. + + Scenario Outline: Attaching images in an examples table + When a image is attached + + Examples: + | type | + | JPEG | + | PNG | diff --git a/compatibility/examples-tables-attachment/examples-tables-attachment.ndjson b/compatibility/examples-tables-attachment/examples-tables-attachment.ndjson new file mode 100644 index 00000000..1de68a68 --- /dev/null +++ b/compatibility/examples-tables-attachment/examples-tables-attachment.ndjson @@ -0,0 +1,21 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Examples Tables - With attachments\n It is sometimes useful to take a screenshot while a scenario runs or capture some logs.\n\n Scenario Outline: Attaching images in an examples table\n When a image is attached\n\n Examples:\n | type |\n | JPEG |\n | PNG |\n","uri":"samples/examples-tables-attachment/examples-tables-attachment.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Examples Tables - With attachments","description":" It is sometimes useful to take a screenshot while a scenario runs or capture some logs.","children":[{"scenario":{"id":"5","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario Outline","name":"Attaching images in an examples table","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a image is attached"}],"examples":[{"id":"4","tags":[],"location":{"line":7,"column":5},"keyword":"Examples","name":"","description":"","tableHeader":{"id":"1","location":{"line":8,"column":7},"cells":[{"location":{"line":8,"column":9},"value":"type"}]},"tableBody":[{"id":"2","location":{"line":9,"column":7},"cells":[{"location":{"line":9,"column":9},"value":"JPEG"}]},{"id":"3","location":{"line":10,"column":7},"cells":[{"location":{"line":10,"column":9},"value":"PNG"}]}]}]}}]},"comments":[],"uri":"samples/examples-tables-attachment/examples-tables-attachment.feature"}} +{"pickle":{"id":"7","uri":"samples/examples-tables-attachment/examples-tables-attachment.feature","location":{"line":9,"column":7},"astNodeIds":["5","2"],"name":"Attaching images in an examples table","language":"en","steps":[{"id":"6","text":"a JPEG image is attached","type":"Action","astNodeIds":["0","2"]}],"tags":[]}} +{"pickle":{"id":"9","uri":"samples/examples-tables-attachment/examples-tables-attachment.feature","location":{"line":10,"column":7},"astNodeIds":["5","3"],"name":"Attaching images in an examples table","language":"en","steps":[{"id":"8","text":"a PNG image is attached","type":"Action","astNodeIds":["0","3"]}],"tags":[]}} +{"stepDefinition":{"id":"10","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a JPEG image is attached"},"sourceReference":{"uri":"samples/examples-tables-attachment/examples-tables-attachment.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"11","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a PNG image is attached"},"sourceReference":{"uri":"samples/examples-tables-attachment/examples-tables-attachment.ts","location":{"line":8}}}} +{"testRunStarted":{"id":"12","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"13","pickleId":"7","testSteps":[{"id":"14","pickleStepId":"6","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"12"}} +{"testCase":{"id":"15","pickleId":"9","testSteps":[{"id":"16","pickleStepId":"8","stepDefinitionIds":["11"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"12"}} +{"testCaseStarted":{"id":"17","testCaseId":"13","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"17","testStepId":"14","timestamp":{"seconds":0,"nanos":2000000}}} +{"attachment":{"testCaseStartedId":"17","testStepId":"14","body":"/9j/4AAQSkZJRgABAQAAAQABAAD//gAfQ29tcHJlc3NlZCBieSBqcGVnLXJlY29tcHJlc3P/2wCEAAQEBAQEBAQEBAQGBgUGBggHBwcHCAwJCQkJCQwTDA4MDA4MExEUEA8QFBEeFxUVFx4iHRsdIiolJSo0MjRERFwBBAQEBAQEBAQEBAYGBQYGCAcHBwcIDAkJCQkJDBMMDgwMDgwTERQQDxAUER4XFRUXHiIdGx0iKiUlKjQyNEREXP/CABEIAC4AKQMBIgACEQEDEQH/xAAcAAABBAMBAAAAAAAAAAAAAAAIBAUGBwABAwL/2gAIAQEAAAAAOESYe+lPPw0bK2mvU5gRhNkM/tNMGeuJM5msiEjujvC+s0ApSWvn/8QAFgEBAQEAAAAAAAAAAAAAAAAABQME/9oACAECEAAAADs6pclK4E//xAAWAQEBAQAAAAAAAAAAAAAAAAAHBgT/2gAIAQMQAAAAMJZbKcF1XHit/8QANhAAAQQBAgQDBAcJAAAAAAAAAgEDBAUGABEHEiExEyJREEFCUhRTYXFzgZIVFiMyMzRVY3L/2gAIAQEAAT8AzLMqPBKOReXb6gy3sDbYdXXnS/labH3mWrrMOIWdGb063fxyoPq1XVp8klQ/3v8Aff7E0eCY86fjPtynn99/GclOq5v6782quZnOGmEnEcrmPNN96y1cWTFcH5BUurf5a4bcTKzP6x9QjlBuIKo1YVzq7mwfuJF+IC9y+zPLc8z4kWiuHz1GLuLAht/AU3u+6qfMK+XUuV4TbrTBtFNVoyYZM0RTJE6dO+2+oGcWZY1fzp0URsq5wGuXkUU3dLlHmH1FdYvMs59HCmW7SBKdQiVEHl3Hfyqqe7dNFbOYRlNDnkQlBth9uHaoPZ2C+SCSl9oL1HX0qN9c3+pNY6pkeSG9/XO/sie9fEV5d9Z5FxdbKNKsbeREsUbHZGAVxeQV6Lt8K6gtMPQYzhD43istETjzaC45sm6EaeulzOgC1Kmdkm1KF3wvO2Qjz+m+syECxe7Q+30ZV/NF3TX7dyv5nv06zGpPDOJd/WvAoV+QvHb1znwk8f8AcN/9c3XUuhp5s1qyl17L0poUQDNN+3VN07LqDTZdNg5fLsFdanyxAI4c/wBUSnsGy9B9w6x+kWwrq2blFW2VtHVUF11P4qiC+RT27r9+r6E9kUyiwmDusq8nNMny924zZc7rv3Cia/dSg/xTH6dcQMDpc/oSqbLmZeaNHoUxro9GfHs4C6uoGZYC4cXM6Z+TCb6BdV7avRjH1dEerRagWEO0iNToDyOx3N+Q0RU32XZehbLq4u4VMyByFI33VQI8ZpOZ5416IICnVdcHuHNjUOSs3y5lByGwaRpiL3Svid0b/EL4vavbXDDBM5ymjjRKi3qK2vZ5lOSYOvykRw1Lyhsgawbg9jGGSUtzJ63v1TzWU/zuB+CPZtPb/8QAJREAAgEDBAEEAwAAAAAAAAAAAQIDAAQRBRITIVEUMTJhI0Fx/9oACAECAQE/ALy8eNxb2/z63N4zTy6hbbpJJ9wV9uCdwPWaglFxEkqDGeiPBFSv6bUZJXLhXGQVx3kfdPBbpyvLNyDOAEbsEjOfsVpJ4rUlx83JH8FSwxTqElTI/R9iKGkBJm5X/GGO1R7kV0AABgAYA8Cv/8QAJREAAgIBBAEDBQAAAAAAAAAAAQIDBAUABhESMSFRcRMVIjJB/9oACAEDAQE/AN1bpuJcbFYt+hXgSSDzydG9uLFF7T3yekwjKl+wY8dvHtrAZlMzjo7RAWQHrIvsw1k+2I3LdksmZVcsymPjlg/z/NTU6MIsy2bf1x26hYnHKsy9ufXyB41sWnN9rmlPKrJNyvwBxrL4LH5mMLbj/Nf1dfRhqjsKaa27WZgtRZD1APLsuq1aGpBHXgQLGihVA1//2Q==","contentEncoding":"BASE64","mediaType":"image/jpeg","timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepFinished":{"testCaseStartedId":"17","testStepId":"14","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":4000000}}} +{"testCaseFinished":{"testCaseStartedId":"17","timestamp":{"seconds":0,"nanos":5000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"18","testCaseId":"15","timestamp":{"seconds":0,"nanos":6000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"18","testStepId":"16","timestamp":{"seconds":0,"nanos":7000000}}} +{"attachment":{"testCaseStartedId":"18","testStepId":"16","body":"iVBORw0KGgoAAAANSUhEUgAAACkAAAAuCAYAAAC1ZTBOAAAABmJLR0QA/wD/AP+gvaeTAAAGgElEQVRYw81ZeWwUVRgfNF4xalDo7Oy92yYmEkm0nZ22olYtM7Pbbu8t24Ntl960Eo0HRCsW5BCIRLyDQK0pFqt/iCdVPIISQvEIVSxg4h8mEhPEqNE/jNLn972dmd1Ztruz3W11kpftdue995vv+H2/7w3DzPBatChwKcvLd7GCvJn1SG+YPNIp+PwFxm8wzrO89CPrEY/A36/keKRuc4F8PTNX18IC700AaAg2/x0GSXN8B8AfNuf7F8wKuBxBXgybHIzdlKvxE2v/MmLf00Kc77QT16ddxH2sh346320nzn1hYtvcSMyhKsIukWPB/sny4iZ2sXhlVsBZiwJXmHh5Gyz8N25gKvES29ogcX3USXJP9RkfE73EMRgiXF1FLNjTbKEoZATwuqJyC+uRj1FwhTKxPrKM5H7Zkx64+HGyjzj2honJV64ChYcX7565e3npDAVY6Seu9zoyAxc33F+tJNZ766JW5eX+9JKjSMpjBfEnnGxpq6ELZhNg7LBta9SAmjzyA4YAssViDkz4ngLsqSW5J3pnDaAGdEeTCvSfHGGpmBokL+3HCebmSpL7zewDVId1Tb0K9NxC3meaHqBHbqNmLy2jVDJXAOkAj3HBCsXt0lBCgAtuqbiKFaSzeJMD+M1Q8E8CrewKEfvzy0nu1xda3THcQiz3B4hjqMXQeq6xDgIYEOhUDi8WJ3Cz3E/jsL3auIse0lwUmXcy+ptzf5uu2jjfakvX7W/rAObleS+DJziHP7oOtBsGyVX79UBGV2i/mcNVut+wKhmy5mddqjXPI8tEOdEjVtFkgfKVVrCvrtcBQdeq1YUtjKnZ8DdubnRdS1cNnQfCZEtMwkij9GlfWJ4eIUNymcSyaC2vr4hY41CnDjyW0XTWdQy3qnNPqBjnwZezaGL3eHfScmZ/uplYVtUS26YG4j4Sudf9cSfh/OU6kFg6FZcRy31g3cn0q5GpKCJIuGKfI1JdMO2r/MmfbqRVL7tA1WiWh8y2P9VM7M9GPWF7vIE4Xw3PmJLMzZGYhixvYkyCWEefuK826SQM/EQa0fFiaHbIXYl3KJUDAFLqxS/W9cGUZIuJobpRq7e3ezNXRomMsl0tlfIwZvajNGmeaDJMuLYNDcRyT4Bymn13iGZz1kEqnoPqcwAzeyMFCTE1p2UwVYYPKuHFS+8zgHQ1pYmtjcYy72g3LXOYNOgSfGL38eRSzvVhJ00q9Jb9mWbi/iS1qne8pOXAQQY7ORqT0KsknQg0YtvYQNhiWZ888D0ZdbkhXjFudXOA3DExkslApDvqbl56naFtqYGa7Xi5NWF2ozU1QN8m3hStnpAZdk3PDNZ1QTVxtjP2JWXzUXWY7vTpBEJKCoIst22JhggmECf5aLWhAgOUFH0ARZOisFUJWgM5OH09x45AKY3dalk8TQXC2PR9DFoJVQ9XX0ksvXW0ZdWIG8NA2zhiHbNSf81Qhdyfr1TKZRdt5hAAVq1pKxH8n73DF5lfKN2sCoytNHlgs7SzcCSckNy5Cq0bJOaW6qReih9oAGXur0x+/iUUJCeI+bROgrvS7WkukGtvRnQjWlAH/rUVxqvNeiUeeXFE38Ly0hc0EXaG0lJBuuoDca0mD7pVp4QGgobVvqqscgSpVq/MBaky0t/4DJc5umC0ySe2J6MFwX24i5hujVJPrPhIGj5DWoKe0Vwdc6FkG6ec+WDAsDUxGdBKtM+JSwRU+bbHgoZ7HJzPVflVK65N3C0W+W6EG/5CejHajGW1Xj+n8enP1wreq5P03eIaVS8abZ6ycuwyDvFd4lWPXFalOB4YuAhu3EtvBq7CujvrICej5A1ePMoEAhcbO8UVpA/Uoz7n6Oy6HoldcfMfJsF7g+FDK2dJyeUAdJ9WAqGZck9k/+AK67cqpGmrMINrHqiQdXiQRK0ql0V4NEuHWFQPRJX+howOUznP0gJY5LhG2kC2qFJcY+1pd4Kai4FTtd5ckHaiQTI/lwZihX4oDAtO6qoMJJe5o4bkGjzDxJChvZK2BkixrACMy35Q82Ug6/fQfl3ZTO3DkwoHOPzHU2PtGDo11WThAqqg5J8CJCp32qJGj15+4Hjxtjl7r5MMJNZvZIWY1yNTMHbPzy+9hpnLKx4k9jSYteaOav2hlUc6nPHrkExBojvNTZXxLcIU9s0Qv6XMf3mpIHWDFydQxcD7GRfzf7hQ90GzdAheqeyAzxC+oMr2Hv8Cf7uNwHUHEgMAAAAASUVORK5CYII=","contentEncoding":"BASE64","mediaType":"image/png","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"18","testStepId":"16","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testCaseFinished":{"testCaseStartedId":"18","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"12","timestamp":{"seconds":0,"nanos":11000000},"success":true}} diff --git a/compatibility/examples-tables-attachment/examples-tables-attachment.ts b/compatibility/examples-tables-attachment/examples-tables-attachment.ts new file mode 100644 index 00000000..0e29e9f2 --- /dev/null +++ b/compatibility/examples-tables-attachment/examples-tables-attachment.ts @@ -0,0 +1,10 @@ +import { When } from '@cucumber/fake-cucumber' +import fs from 'node:fs' + +When('a JPEG image is attached', async function () { + await this.attach(fs.createReadStream(import.meta.dirname + '/cucumber.jpeg'), 'image/jpeg') +}) + +When('a PNG image is attached', async function () { + await this.attach(fs.createReadStream(import.meta.dirname + '/cucumber.png'), 'image/png') +}) diff --git a/compatibility/examples-tables-undefined/examples-tables-undefined.ndjson b/compatibility/examples-tables-undefined/examples-tables-undefined.ndjson new file mode 100644 index 00000000..114aaf5a --- /dev/null +++ b/compatibility/examples-tables-undefined/examples-tables-undefined.ndjson @@ -0,0 +1,41 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Examples Tables - With Undefined Steps\n The replacement pattern used in scenario outlines does not influence how steps\n are matched. The replacement pattern is replaced, and step definitions are\n matched against that text. Because of that the following results in one\n undefined step for each example and a suggested snippet to implement it. \n\n Scenario Outline: Eating cucumbers\n Given there are cucumbers\n When I eat cucumbers\n Then I should have cucumbers\n\n @undefined\n Examples: These are undefined because the value is not an {int}\n | start | eat | left |\n | pear | 1 | 12 |\n | 12 | banana | 12 |\n | 0 | 1 | apple |\n","uri":"samples/examples-tables-undefined/examples-undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Examples Tables - With Undefined Steps","description":" The replacement pattern used in scenario outlines does not influence how steps\n are matched. The replacement pattern is replaced, and step definitions are\n matched against that text. Because of that the following results in one\n undefined step for each example and a suggested snippet to implement it. ","children":[{"scenario":{"id":"9","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario Outline","name":"Eating cucumbers","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"there are cucumbers"},{"id":"1","location":{"line":9,"column":5},"keyword":"When ","keywordType":"Action","text":"I eat cucumbers"},{"id":"2","location":{"line":10,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"I should have cucumbers"}],"examples":[{"id":"8","tags":[{"location":{"line":12,"column":5},"name":"@undefined","id":"7"}],"location":{"line":13,"column":5},"keyword":"Examples","name":"These are undefined because the value is not an {int}","description":"","tableHeader":{"id":"3","location":{"line":14,"column":7},"cells":[{"location":{"line":14,"column":9},"value":"start"},{"location":{"line":14,"column":17},"value":"eat"},{"location":{"line":14,"column":26},"value":"left"}]},"tableBody":[{"id":"4","location":{"line":15,"column":7},"cells":[{"location":{"line":15,"column":9},"value":"pear"},{"location":{"line":15,"column":17},"value":"1"},{"location":{"line":15,"column":26},"value":"12"}]},{"id":"5","location":{"line":16,"column":7},"cells":[{"location":{"line":16,"column":9},"value":"12"},{"location":{"line":16,"column":17},"value":"banana"},{"location":{"line":16,"column":26},"value":"12"}]},{"id":"6","location":{"line":17,"column":7},"cells":[{"location":{"line":17,"column":9},"value":"0"},{"location":{"line":17,"column":17},"value":"1"},{"location":{"line":17,"column":26},"value":"apple"}]}]}]}}]},"comments":[],"uri":"samples/examples-tables-undefined/examples-undefined.feature"}} +{"pickle":{"id":"13","uri":"samples/examples-tables-undefined/examples-undefined.feature","location":{"line":15,"column":7},"astNodeIds":["9","4"],"name":"Eating cucumbers","language":"en","steps":[{"id":"10","text":"there are pear cucumbers","type":"Context","astNodeIds":["0","4"]},{"id":"11","text":"I eat 1 cucumbers","type":"Action","astNodeIds":["1","4"]},{"id":"12","text":"I should have 12 cucumbers","type":"Outcome","astNodeIds":["2","4"]}],"tags":[{"name":"@undefined","astNodeId":"7"}]}} +{"pickle":{"id":"17","uri":"samples/examples-tables-undefined/examples-undefined.feature","location":{"line":16,"column":7},"astNodeIds":["9","5"],"name":"Eating cucumbers","language":"en","steps":[{"id":"14","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","5"]},{"id":"15","text":"I eat banana cucumbers","type":"Action","astNodeIds":["1","5"]},{"id":"16","text":"I should have 12 cucumbers","type":"Outcome","astNodeIds":["2","5"]}],"tags":[{"name":"@undefined","astNodeId":"7"}]}} +{"pickle":{"id":"21","uri":"samples/examples-tables-undefined/examples-undefined.feature","location":{"line":17,"column":7},"astNodeIds":["9","6"],"name":"Eating cucumbers","language":"en","steps":[{"id":"18","text":"there are 0 cucumbers","type":"Context","astNodeIds":["0","6"]},{"id":"19","text":"I eat 1 cucumbers","type":"Action","astNodeIds":["1","6"]},{"id":"20","text":"I should have apple cucumbers","type":"Outcome","astNodeIds":["2","6"]}],"tags":[{"name":"@undefined","astNodeId":"7"}]}} +{"stepDefinition":{"id":"22","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables-undefined/examples-tables-undefined.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"23","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I eat {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables-undefined/examples-tables-undefined.ts","location":{"line":8}}}} +{"stepDefinition":{"id":"24","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I should have {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables-undefined/examples-tables-undefined.ts","location":{"line":12}}}} +{"testRunStarted":{"id":"25","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"26","pickleId":"13","testSteps":[{"id":"27","pickleStepId":"10","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"28","pickleStepId":"11","stepDefinitionIds":["23"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"29","pickleStepId":"12","stepDefinitionIds":["24"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"12","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"25"}} +{"testCase":{"id":"30","pickleId":"17","testSteps":[{"id":"31","pickleStepId":"14","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"32","pickleStepId":"15","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"33","pickleStepId":"16","stepDefinitionIds":["24"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"12","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"25"}} +{"testCase":{"id":"34","pickleId":"21","testSteps":[{"id":"35","pickleStepId":"18","stepDefinitionIds":["22"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"36","pickleStepId":"19","stepDefinitionIds":["23"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"37","pickleStepId":"20","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"25"}} +{"testCaseStarted":{"id":"38","testCaseId":"26","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"27","timestamp":{"seconds":0,"nanos":2000000}}} +{"suggestion":{"id":"39","pickleStepId":"10","snippets":[{"language":"typescript","code":"Given(\"there are pear cucumbers\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"27","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"28","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"28","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"38","testStepId":"29","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"38","testStepId":"29","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"38","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"40","testCaseId":"30","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"40","testStepId":"31","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"40","testStepId":"31","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testStepStarted":{"testCaseStartedId":"40","testStepId":"32","timestamp":{"seconds":0,"nanos":12000000}}} +{"suggestion":{"id":"41","pickleStepId":"15","snippets":[{"language":"typescript","code":"When(\"I eat banana cucumbers\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"40","testStepId":"32","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"40","testStepId":"33","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"40","testStepId":"33","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"40","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"42","testCaseId":"34","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"42","testStepId":"35","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"42","testStepId":"35","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testStepStarted":{"testCaseStartedId":"42","testStepId":"36","timestamp":{"seconds":0,"nanos":20000000}}} +{"testStepFinished":{"testCaseStartedId":"42","testStepId":"36","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}} +{"testStepStarted":{"testCaseStartedId":"42","testStepId":"37","timestamp":{"seconds":0,"nanos":22000000}}} +{"suggestion":{"id":"43","pickleStepId":"20","snippets":[{"language":"typescript","code":"Then(\"I should have apple cucumbers\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"42","testStepId":"37","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testCaseFinished":{"testCaseStartedId":"42","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"25","timestamp":{"seconds":0,"nanos":25000000},"success":false}} diff --git a/compatibility/examples-tables-undefined/examples-tables-undefined.ts b/compatibility/examples-tables-undefined/examples-tables-undefined.ts new file mode 100644 index 00000000..80d45547 --- /dev/null +++ b/compatibility/examples-tables-undefined/examples-tables-undefined.ts @@ -0,0 +1,14 @@ +import assert from 'node:assert' +import { Given, When, Then } from '@cucumber/fake-cucumber' + +Given('there are {int} cucumbers', function (initialCount) { + this.count = initialCount +}) + +When('I eat {int} cucumbers', function (eatCount) { + this.count -= eatCount +}) + +Then('I should have {int} cucumbers', function (expectedCount) { + assert.strictEqual(this.count, expectedCount) +}) diff --git a/compatibility/examples-tables-undefined/examples-undefined.feature b/compatibility/examples-tables-undefined/examples-undefined.feature new file mode 100644 index 00000000..0e35aaf1 --- /dev/null +++ b/compatibility/examples-tables-undefined/examples-undefined.feature @@ -0,0 +1,17 @@ +Feature: Examples Tables - With Undefined Steps + The replacement pattern used in scenario outlines does not influence how steps + are matched. The replacement pattern is replaced, and step definitions are + matched against that text. Because of that the following results in one + undefined step for each example and a suggested snippet to implement it. + + Scenario Outline: Eating cucumbers + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + + @undefined + Examples: These are undefined because the value is not an {int} + | start | eat | left | + | pear | 1 | 12 | + | 12 | banana | 12 | + | 0 | 1 | apple | diff --git a/compatibility/examples-tables/examples-tables.feature b/compatibility/examples-tables/examples-tables.feature new file mode 100644 index 00000000..523309dc --- /dev/null +++ b/compatibility/examples-tables/examples-tables.feature @@ -0,0 +1,37 @@ +Feature: Examples Tables + Sometimes it can be desirable to run the same scenario multiple times with + different data each time - this can be done by placing an Examples table + underneath a Scenario, and use in the Scenario which match the + table headers. + + The Scenario Outline name can also be parameterized. The name of the resulting + pickle will have the replaced with the value from the examples + table. + + Scenario Outline: Eating cucumbers + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + + @passing + Examples: These are passing + | start | eat | left | + | 12 | 5 | 7 | + | 20 | 5 | 15 | + + @failing + Examples: These are failing + | start | eat | left | + | 12 | 20 | 0 | + | 0 | 1 | 0 | + + Scenario Outline: Eating cucumbers with friends + Given there are friends + And there are cucumbers + Then each person can eat cucumbers + + Examples: + | friends | start | share | + | 11 | 12 | 1 | + | 1 | 4 | 2 | + | 0 | 4 | 4 | diff --git a/compatibility/examples-tables/examples-tables.ndjson b/compatibility/examples-tables/examples-tables.ndjson new file mode 100644 index 00000000..f1db9992 --- /dev/null +++ b/compatibility/examples-tables/examples-tables.ndjson @@ -0,0 +1,80 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Examples Tables\n Sometimes it can be desirable to run the same scenario multiple times with\n different data each time - this can be done by placing an Examples table\n underneath a Scenario, and use in the Scenario which match the\n table headers.\n\n The Scenario Outline name can also be parameterized. The name of the resulting\n pickle will have the replaced with the value from the examples\n table.\n\n Scenario Outline: Eating cucumbers\n Given there are cucumbers\n When I eat cucumbers\n Then I should have cucumbers\n\n @passing\n Examples: These are passing\n | start | eat | left |\n | 12 | 5 | 7 |\n | 20 | 5 | 15 |\n\n @failing\n Examples: These are failing\n | start | eat | left |\n | 12 | 20 | 0 |\n | 0 | 1 | 0 |\n\n Scenario Outline: Eating cucumbers with friends\n Given there are friends\n And there are cucumbers\n Then each person can eat cucumbers\n\n Examples:\n | friends | start | share |\n | 11 | 12 | 1 |\n | 1 | 4 | 2 |\n | 0 | 4 | 4 |\n","uri":"samples/examples-tables/examples-tables.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Examples Tables","description":" Sometimes it can be desirable to run the same scenario multiple times with\n different data each time - this can be done by placing an Examples table\n underneath a Scenario, and use in the Scenario which match the\n table headers.\n\n The Scenario Outline name can also be parameterized. The name of the resulting\n pickle will have the replaced with the value from the examples\n table.","children":[{"scenario":{"id":"13","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario Outline","name":"Eating cucumbers","description":"","steps":[{"id":"0","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"there are cucumbers"},{"id":"1","location":{"line":13,"column":5},"keyword":"When ","keywordType":"Action","text":"I eat cucumbers"},{"id":"2","location":{"line":14,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"I should have cucumbers"}],"examples":[{"id":"7","tags":[{"location":{"line":16,"column":5},"name":"@passing","id":"6"}],"location":{"line":17,"column":5},"keyword":"Examples","name":"These are passing","description":"","tableHeader":{"id":"3","location":{"line":18,"column":7},"cells":[{"location":{"line":18,"column":9},"value":"start"},{"location":{"line":18,"column":17},"value":"eat"},{"location":{"line":18,"column":23},"value":"left"}]},"tableBody":[{"id":"4","location":{"line":19,"column":7},"cells":[{"location":{"line":19,"column":12},"value":"12"},{"location":{"line":19,"column":19},"value":"5"},{"location":{"line":19,"column":26},"value":"7"}]},{"id":"5","location":{"line":20,"column":7},"cells":[{"location":{"line":20,"column":12},"value":"20"},{"location":{"line":20,"column":19},"value":"5"},{"location":{"line":20,"column":25},"value":"15"}]}]},{"id":"12","tags":[{"location":{"line":22,"column":5},"name":"@failing","id":"11"}],"location":{"line":23,"column":5},"keyword":"Examples","name":"These are failing","description":"","tableHeader":{"id":"8","location":{"line":24,"column":7},"cells":[{"location":{"line":24,"column":9},"value":"start"},{"location":{"line":24,"column":17},"value":"eat"},{"location":{"line":24,"column":23},"value":"left"}]},"tableBody":[{"id":"9","location":{"line":25,"column":7},"cells":[{"location":{"line":25,"column":12},"value":"12"},{"location":{"line":25,"column":18},"value":"20"},{"location":{"line":25,"column":26},"value":"0"}]},{"id":"10","location":{"line":26,"column":7},"cells":[{"location":{"line":26,"column":13},"value":"0"},{"location":{"line":26,"column":19},"value":"1"},{"location":{"line":26,"column":26},"value":"0"}]}]}]}},{"scenario":{"id":"22","tags":[],"location":{"line":28,"column":3},"keyword":"Scenario Outline","name":"Eating cucumbers with friends","description":"","steps":[{"id":"14","location":{"line":29,"column":5},"keyword":"Given ","keywordType":"Context","text":"there are friends"},{"id":"15","location":{"line":30,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"there are cucumbers"},{"id":"16","location":{"line":31,"column":5},"keyword":"Then ","keywordType":"Outcome","text":"each person can eat cucumbers"}],"examples":[{"id":"21","tags":[],"location":{"line":33,"column":5},"keyword":"Examples","name":"","description":"","tableHeader":{"id":"17","location":{"line":34,"column":7},"cells":[{"location":{"line":34,"column":9},"value":"friends"},{"location":{"line":34,"column":19},"value":"start"},{"location":{"line":34,"column":27},"value":"share"}]},"tableBody":[{"id":"18","location":{"line":35,"column":7},"cells":[{"location":{"line":35,"column":14},"value":"11"},{"location":{"line":35,"column":22},"value":"12"},{"location":{"line":35,"column":31},"value":"1"}]},{"id":"19","location":{"line":36,"column":7},"cells":[{"location":{"line":36,"column":15},"value":"1"},{"location":{"line":36,"column":23},"value":"4"},{"location":{"line":36,"column":31},"value":"2"}]},{"id":"20","location":{"line":37,"column":7},"cells":[{"location":{"line":37,"column":15},"value":"0"},{"location":{"line":37,"column":23},"value":"4"},{"location":{"line":37,"column":31},"value":"4"}]}]}]}}]},"comments":[],"uri":"samples/examples-tables/examples-tables.feature"}} +{"pickle":{"id":"26","uri":"samples/examples-tables/examples-tables.feature","location":{"line":19,"column":7},"astNodeIds":["13","4"],"name":"Eating cucumbers","language":"en","steps":[{"id":"23","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","4"]},{"id":"24","text":"I eat 5 cucumbers","type":"Action","astNodeIds":["1","4"]},{"id":"25","text":"I should have 7 cucumbers","type":"Outcome","astNodeIds":["2","4"]}],"tags":[{"name":"@passing","astNodeId":"6"}]}} +{"pickle":{"id":"30","uri":"samples/examples-tables/examples-tables.feature","location":{"line":20,"column":7},"astNodeIds":["13","5"],"name":"Eating cucumbers","language":"en","steps":[{"id":"27","text":"there are 20 cucumbers","type":"Context","astNodeIds":["0","5"]},{"id":"28","text":"I eat 5 cucumbers","type":"Action","astNodeIds":["1","5"]},{"id":"29","text":"I should have 15 cucumbers","type":"Outcome","astNodeIds":["2","5"]}],"tags":[{"name":"@passing","astNodeId":"6"}]}} +{"pickle":{"id":"34","uri":"samples/examples-tables/examples-tables.feature","location":{"line":25,"column":7},"astNodeIds":["13","9"],"name":"Eating cucumbers","language":"en","steps":[{"id":"31","text":"there are 12 cucumbers","type":"Context","astNodeIds":["0","9"]},{"id":"32","text":"I eat 20 cucumbers","type":"Action","astNodeIds":["1","9"]},{"id":"33","text":"I should have 0 cucumbers","type":"Outcome","astNodeIds":["2","9"]}],"tags":[{"name":"@failing","astNodeId":"11"}]}} +{"pickle":{"id":"38","uri":"samples/examples-tables/examples-tables.feature","location":{"line":26,"column":7},"astNodeIds":["13","10"],"name":"Eating cucumbers","language":"en","steps":[{"id":"35","text":"there are 0 cucumbers","type":"Context","astNodeIds":["0","10"]},{"id":"36","text":"I eat 1 cucumbers","type":"Action","astNodeIds":["1","10"]},{"id":"37","text":"I should have 0 cucumbers","type":"Outcome","astNodeIds":["2","10"]}],"tags":[{"name":"@failing","astNodeId":"11"}]}} +{"pickle":{"id":"42","uri":"samples/examples-tables/examples-tables.feature","location":{"line":35,"column":7},"astNodeIds":["22","18"],"name":"Eating cucumbers with 11 friends","language":"en","steps":[{"id":"39","text":"there are 11 friends","type":"Context","astNodeIds":["14","18"]},{"id":"40","text":"there are 12 cucumbers","type":"Context","astNodeIds":["15","18"]},{"id":"41","text":"each person can eat 1 cucumbers","type":"Outcome","astNodeIds":["16","18"]}],"tags":[]}} +{"pickle":{"id":"46","uri":"samples/examples-tables/examples-tables.feature","location":{"line":36,"column":7},"astNodeIds":["22","19"],"name":"Eating cucumbers with 1 friends","language":"en","steps":[{"id":"43","text":"there are 1 friends","type":"Context","astNodeIds":["14","19"]},{"id":"44","text":"there are 4 cucumbers","type":"Context","astNodeIds":["15","19"]},{"id":"45","text":"each person can eat 2 cucumbers","type":"Outcome","astNodeIds":["16","19"]}],"tags":[]}} +{"pickle":{"id":"50","uri":"samples/examples-tables/examples-tables.feature","location":{"line":37,"column":7},"astNodeIds":["22","20"],"name":"Eating cucumbers with 0 friends","language":"en","steps":[{"id":"47","text":"there are 0 friends","type":"Context","astNodeIds":["14","20"]},{"id":"48","text":"there are 4 cucumbers","type":"Context","astNodeIds":["15","20"]},{"id":"49","text":"each person can eat 4 cucumbers","type":"Outcome","astNodeIds":["16","20"]}],"tags":[]}} +{"stepDefinition":{"id":"51","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"52","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are {int} friends"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":8}}}} +{"stepDefinition":{"id":"53","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I eat {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":12}}}} +{"stepDefinition":{"id":"54","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I should have {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":16}}}} +{"stepDefinition":{"id":"55","pattern":{"type":"CUCUMBER_EXPRESSION","source":"each person can eat {int} cucumbers"},"sourceReference":{"uri":"samples/examples-tables/examples-tables.ts","location":{"line":20}}}} +{"testRunStarted":{"id":"56","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"57","pickleId":"26","testSteps":[{"id":"58","pickleStepId":"23","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"59","pickleStepId":"24","stepDefinitionIds":["53"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"5","children":[]},"parameterTypeName":"int"}]}]},{"id":"60","pickleStepId":"25","stepDefinitionIds":["54"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"7","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}} +{"testCase":{"id":"61","pickleId":"30","testSteps":[{"id":"62","pickleStepId":"27","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"20","children":[]},"parameterTypeName":"int"}]}]},{"id":"63","pickleStepId":"28","stepDefinitionIds":["53"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"5","children":[]},"parameterTypeName":"int"}]}]},{"id":"64","pickleStepId":"29","stepDefinitionIds":["54"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"15","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}} +{"testCase":{"id":"65","pickleId":"34","testSteps":[{"id":"66","pickleStepId":"31","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"67","pickleStepId":"32","stepDefinitionIds":["53"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"20","children":[]},"parameterTypeName":"int"}]}]},{"id":"68","pickleStepId":"33","stepDefinitionIds":["54"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"0","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}} +{"testCase":{"id":"69","pickleId":"38","testSteps":[{"id":"70","pickleStepId":"35","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"71","pickleStepId":"36","stepDefinitionIds":["53"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":6,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"72","pickleStepId":"37","stepDefinitionIds":["54"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":14,"value":"0","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}} +{"testCase":{"id":"73","pickleId":"42","testSteps":[{"id":"74","pickleStepId":"39","stepDefinitionIds":["52"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"11","children":[]},"parameterTypeName":"int"}]}]},{"id":"75","pickleStepId":"40","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"12","children":[]},"parameterTypeName":"int"}]}]},{"id":"76","pickleStepId":"41","stepDefinitionIds":["55"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"1","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}} +{"testCase":{"id":"77","pickleId":"46","testSteps":[{"id":"78","pickleStepId":"43","stepDefinitionIds":["52"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"79","pickleStepId":"44","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"4","children":[]},"parameterTypeName":"int"}]}]},{"id":"80","pickleStepId":"45","stepDefinitionIds":["55"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"2","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}} +{"testCase":{"id":"81","pickleId":"50","testSteps":[{"id":"82","pickleStepId":"47","stepDefinitionIds":["52"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"0","children":[]},"parameterTypeName":"int"}]}]},{"id":"83","pickleStepId":"48","stepDefinitionIds":["51"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":10,"value":"4","children":[]},"parameterTypeName":"int"}]}]},{"id":"84","pickleStepId":"49","stepDefinitionIds":["55"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":20,"value":"4","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"56"}} +{"testCaseStarted":{"id":"85","testCaseId":"57","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"85","testStepId":"58","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"85","testStepId":"58","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"85","testStepId":"59","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"85","testStepId":"59","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"85","testStepId":"60","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"85","testStepId":"60","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"85","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"86","testCaseId":"61","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"86","testStepId":"62","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"86","testStepId":"62","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testStepStarted":{"testCaseStartedId":"86","testStepId":"63","timestamp":{"seconds":0,"nanos":12000000}}} +{"testStepFinished":{"testCaseStartedId":"86","testStepId":"63","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"86","testStepId":"64","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"86","testStepId":"64","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"86","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"87","testCaseId":"65","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"87","testStepId":"66","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"87","testStepId":"66","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testStepStarted":{"testCaseStartedId":"87","testStepId":"67","timestamp":{"seconds":0,"nanos":20000000}}} +{"testStepFinished":{"testCaseStartedId":"87","testStepId":"67","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}} +{"testStepStarted":{"testCaseStartedId":"87","testStepId":"68","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"87","testStepId":"68","testStepResult":{"message":"Expected values to be strictly equal:\n\n-8 !== 0\n","exception":{"type":"AssertionError","message":"Expected values to be strictly equal:\n\n-8 !== 0\n","stackTrace":"AssertionError: Expected values to be strictly equal:\n\n-8 !== 0\n\nsamples/examples-tables/examples-tables.feature:14"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testCaseFinished":{"testCaseStartedId":"87","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"88","testCaseId":"69","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"88","testStepId":"70","timestamp":{"seconds":0,"nanos":26000000}}} +{"testStepFinished":{"testCaseStartedId":"88","testStepId":"70","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} +{"testStepStarted":{"testCaseStartedId":"88","testStepId":"71","timestamp":{"seconds":0,"nanos":28000000}}} +{"testStepFinished":{"testCaseStartedId":"88","testStepId":"71","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}} +{"testStepStarted":{"testCaseStartedId":"88","testStepId":"72","timestamp":{"seconds":0,"nanos":30000000}}} +{"testStepFinished":{"testCaseStartedId":"88","testStepId":"72","testStepResult":{"message":"Expected values to be strictly equal:\n\n-1 !== 0\n","exception":{"type":"AssertionError","message":"Expected values to be strictly equal:\n\n-1 !== 0\n","stackTrace":"AssertionError: Expected values to be strictly equal:\n\n-1 !== 0\n\nsamples/examples-tables/examples-tables.feature:14"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}} +{"testCaseFinished":{"testCaseStartedId":"88","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"89","testCaseId":"73","timestamp":{"seconds":0,"nanos":33000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"89","testStepId":"74","timestamp":{"seconds":0,"nanos":34000000}}} +{"testStepFinished":{"testCaseStartedId":"89","testStepId":"74","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}} +{"testStepStarted":{"testCaseStartedId":"89","testStepId":"75","timestamp":{"seconds":0,"nanos":36000000}}} +{"testStepFinished":{"testCaseStartedId":"89","testStepId":"75","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":37000000}}} +{"testStepStarted":{"testCaseStartedId":"89","testStepId":"76","timestamp":{"seconds":0,"nanos":38000000}}} +{"testStepFinished":{"testCaseStartedId":"89","testStepId":"76","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":39000000}}} +{"testCaseFinished":{"testCaseStartedId":"89","timestamp":{"seconds":0,"nanos":40000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"90","testCaseId":"77","timestamp":{"seconds":0,"nanos":41000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"90","testStepId":"78","timestamp":{"seconds":0,"nanos":42000000}}} +{"testStepFinished":{"testCaseStartedId":"90","testStepId":"78","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":43000000}}} +{"testStepStarted":{"testCaseStartedId":"90","testStepId":"79","timestamp":{"seconds":0,"nanos":44000000}}} +{"testStepFinished":{"testCaseStartedId":"90","testStepId":"79","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":45000000}}} +{"testStepStarted":{"testCaseStartedId":"90","testStepId":"80","timestamp":{"seconds":0,"nanos":46000000}}} +{"testStepFinished":{"testCaseStartedId":"90","testStepId":"80","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":47000000}}} +{"testCaseFinished":{"testCaseStartedId":"90","timestamp":{"seconds":0,"nanos":48000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"91","testCaseId":"81","timestamp":{"seconds":0,"nanos":49000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"91","testStepId":"82","timestamp":{"seconds":0,"nanos":50000000}}} +{"testStepFinished":{"testCaseStartedId":"91","testStepId":"82","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":51000000}}} +{"testStepStarted":{"testCaseStartedId":"91","testStepId":"83","timestamp":{"seconds":0,"nanos":52000000}}} +{"testStepFinished":{"testCaseStartedId":"91","testStepId":"83","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":53000000}}} +{"testStepStarted":{"testCaseStartedId":"91","testStepId":"84","timestamp":{"seconds":0,"nanos":54000000}}} +{"testStepFinished":{"testCaseStartedId":"91","testStepId":"84","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":55000000}}} +{"testCaseFinished":{"testCaseStartedId":"91","timestamp":{"seconds":0,"nanos":56000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"56","timestamp":{"seconds":0,"nanos":57000000},"success":false}} diff --git a/compatibility/examples-tables/examples-tables.ts b/compatibility/examples-tables/examples-tables.ts new file mode 100644 index 00000000..38a157fd --- /dev/null +++ b/compatibility/examples-tables/examples-tables.ts @@ -0,0 +1,23 @@ +import assert from 'node:assert' +import { Given, When, Then } from '@cucumber/fake-cucumber' + +Given('there are {int} cucumbers', function (initialCount) { + this.count = initialCount +}) + +Given('there are {int} friends', function (initialFriends) { + this.friends = initialFriends +}) + +When('I eat {int} cucumbers', function (eatCount) { + this.count -= eatCount +}) + +Then('I should have {int} cucumbers', function (expectedCount) { + assert.strictEqual(this.count, expectedCount) +}) + +Then('each person can eat {int} cucumbers', function (expectedShare) { + let share = Math.floor(this.count / (1 + this.friends)); + assert.strictEqual(share, expectedShare) +}) diff --git a/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.feature b/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.feature new file mode 100644 index 00000000..0ef1bfed --- /dev/null +++ b/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.feature @@ -0,0 +1,6 @@ +Feature: Global hooks - AfterAll error + Errors in AfterAll hooks cause the whole test run to fail. The remaining AfterAll hooks will still run, in an + effort to clean up resources as well as possible. + + Scenario: A passing scenario + When a step passes diff --git a/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.ndjson b/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.ndjson new file mode 100644 index 00000000..25b9a8b5 --- /dev/null +++ b/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.ndjson @@ -0,0 +1,27 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Global hooks - AfterAll error\n Errors in AfterAll hooks cause the whole test run to fail. The remaining AfterAll hooks will still run, in an\n effort to clean up resources as well as possible.\n\n Scenario: A passing scenario\n When a step passes\n","uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Global hooks - AfterAll error","description":" Errors in AfterAll hooks cause the whole test run to fail. The remaining AfterAll hooks will still run, in an\n effort to clean up resources as well as possible.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"A passing scenario","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.feature"}} +{"pickle":{"id":"3","uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.feature","location":{"line":5,"column":3},"astNodeIds":["1"],"tags":[],"name":"A passing scenario","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"6","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.ts","location":{"line":11}}}} +{"hook":{"id":"4","type":"BEFORE_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.ts","location":{"line":3}}}} +{"hook":{"id":"5","type":"BEFORE_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.ts","location":{"line":7}}}} +{"hook":{"id":"7","type":"AFTER_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.ts","location":{"line":15}}}} +{"hook":{"id":"8","type":"AFTER_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.ts","location":{"line":19}}}} +{"hook":{"id":"9","type":"AFTER_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-afterall-error/global-hooks-afterall-error.ts","location":{"line":23}}}} +{"testRunStarted":{"id":"10","timestamp":{"seconds":0,"nanos":0}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"11","hookId":"4","timestamp":{"seconds":0,"nanos":1000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"11","timestamp":{"seconds":0,"nanos":2000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"12","hookId":"5","timestamp":{"seconds":0,"nanos":3000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"12","timestamp":{"seconds":0,"nanos":4000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testCase":{"id":"13","pickleId":"3","testSteps":[{"id":"14","pickleStepId":"2","stepDefinitionIds":["6"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"10"}} +{"testCaseStarted":{"id":"15","testCaseId":"13","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"15","testStepId":"14","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"15","testStepId":"14","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"15","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"16","hookId":"9","timestamp":{"seconds":0,"nanos":9000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"16","timestamp":{"seconds":0,"nanos":10000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"17","hookId":"8","timestamp":{"seconds":0,"nanos":11000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"17","timestamp":{"seconds":0,"nanos":12000000},"result":{"message":"AfterAll hook went wrong","exception":{"type":"Error","message":"AfterAll hook went wrong","stackTrace":"Error: AfterAll hook went wrong\nsamples/global-hooks-afterall-error/global-hooks-afterall-error.ts:19"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"18","hookId":"7","timestamp":{"seconds":0,"nanos":13000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"18","timestamp":{"seconds":0,"nanos":14000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunFinished":{"testRunStartedId":"10","timestamp":{"seconds":0,"nanos":15000000},"success":false}} diff --git a/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.ts b/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.ts new file mode 100644 index 00000000..3fe24b79 --- /dev/null +++ b/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.ts @@ -0,0 +1,25 @@ +import { When, BeforeAll, AfterAll } from '@cucumber/fake-cucumber' + +BeforeAll({}, function () { + // no-op +}) + +BeforeAll({}, function () { + // no-op +}) + +When('a step passes', function () { + // no-op +}) + +AfterAll({}, function () { + // no-op +}) + +AfterAll({}, function () { + throw new Error('AfterAll hook went wrong') +}) + +AfterAll({}, function () { + // no-op +}) diff --git a/compatibility/global-hooks-attachments/global-hooks-attachments.feature b/compatibility/global-hooks-attachments/global-hooks-attachments.feature new file mode 100644 index 00000000..d96ccf2b --- /dev/null +++ b/compatibility/global-hooks-attachments/global-hooks-attachments.feature @@ -0,0 +1,5 @@ +Feature: Global hooks with attachments + Attachments can be captured in BeforeAll and AfterAll hooks. + + Scenario: A scenario + When a step passes diff --git a/compatibility/global-hooks-attachments/global-hooks-attachments.ndjson b/compatibility/global-hooks-attachments/global-hooks-attachments.ndjson new file mode 100644 index 00000000..0c174212 --- /dev/null +++ b/compatibility/global-hooks-attachments/global-hooks-attachments.ndjson @@ -0,0 +1,20 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Global hooks with attachments\n Attachments can be captured in BeforeAll and AfterAll hooks.\n\n Scenario: A scenario\n When a step passes\n","uri":"samples/global-hooks-attachments/global-hooks-attachments.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Global hooks with attachments","description":" Attachments can be captured in BeforeAll and AfterAll hooks.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"A scenario","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/global-hooks-attachments/global-hooks-attachments.feature"}} +{"pickle":{"id":"3","uri":"samples/global-hooks-attachments/global-hooks-attachments.feature","location":{"line":4,"column":3},"astNodeIds":["1"],"tags":[],"name":"A scenario","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"5","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/global-hooks-attachments/global-hooks-attachments.ts","location":{"line":7}}}} +{"hook":{"id":"4","type":"BEFORE_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-attachments/global-hooks-attachments.ts","location":{"line":3}}}} +{"hook":{"id":"6","type":"AFTER_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-attachments/global-hooks-attachments.ts","location":{"line":11}}}} +{"testRunStarted":{"id":"7","timestamp":{"seconds":0,"nanos":0}}} +{"testRunHookStarted":{"testRunStartedId":"7","id":"8","hookId":"4","timestamp":{"seconds":0,"nanos":1000000}}} +{"attachment":{"testRunHookStartedId":"8","body":"Attachment from BeforeAll hook","contentEncoding":"IDENTITY","mediaType":"text/plain","timestamp":{"seconds":0,"nanos":2000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"8","timestamp":{"seconds":0,"nanos":3000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testCase":{"id":"9","pickleId":"3","testSteps":[{"id":"10","pickleStepId":"2","stepDefinitionIds":["5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"7"}} +{"testCaseStarted":{"id":"11","testCaseId":"9","timestamp":{"seconds":0,"nanos":4000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"11","testStepId":"10","timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepFinished":{"testCaseStartedId":"11","testStepId":"10","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":6000000}}} +{"testCaseFinished":{"testCaseStartedId":"11","timestamp":{"seconds":0,"nanos":7000000},"willBeRetried":false}} +{"testRunHookStarted":{"testRunStartedId":"7","id":"12","hookId":"6","timestamp":{"seconds":0,"nanos":8000000}}} +{"attachment":{"testRunHookStartedId":"12","body":"Attachment from AfterAll hook","contentEncoding":"IDENTITY","mediaType":"text/plain","timestamp":{"seconds":0,"nanos":9000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"12","timestamp":{"seconds":0,"nanos":10000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunFinished":{"testRunStartedId":"7","timestamp":{"seconds":0,"nanos":11000000},"success":true}} diff --git a/compatibility/global-hooks-attachments/global-hooks-attachments.ts b/compatibility/global-hooks-attachments/global-hooks-attachments.ts new file mode 100644 index 00000000..77bbdbee --- /dev/null +++ b/compatibility/global-hooks-attachments/global-hooks-attachments.ts @@ -0,0 +1,13 @@ +import { When, BeforeAll, AfterAll } from '@cucumber/fake-cucumber' + +BeforeAll({}, async function () { + await this.attach('Attachment from BeforeAll hook', 'text/plain') +}) + +When('a step passes', function () { + // no-op +}) + +AfterAll({}, async function () { + await this.attach('Attachment from AfterAll hook', 'text/plain') +}) diff --git a/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.feature b/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.feature new file mode 100644 index 00000000..2b42678e --- /dev/null +++ b/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.feature @@ -0,0 +1,6 @@ +Feature: Global hooks - BeforeAll error + Errors in BeforeAll hooks cause the whole test run to fail. Test cases will not be executed. The remaining BeforeAll + hooks will still run, along with all AfterAll hooks, in an effort to clean up resources as well as possible. + + Scenario: A passing scenario + When a step passes diff --git a/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.ndjson b/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.ndjson new file mode 100644 index 00000000..a8a2635e --- /dev/null +++ b/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.ndjson @@ -0,0 +1,22 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Global hooks - BeforeAll error\n Errors in BeforeAll hooks cause the whole test run to fail. Test cases will not be executed. The remaining BeforeAll\n hooks will still run, along with all AfterAll hooks, in an effort to clean up resources as well as possible.\n\n Scenario: A passing scenario\n When a step passes\n","uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Global hooks - BeforeAll error","description":" Errors in BeforeAll hooks cause the whole test run to fail. Test cases will not be executed. The remaining BeforeAll\n hooks will still run, along with all AfterAll hooks, in an effort to clean up resources as well as possible.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"A passing scenario","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.feature"}} +{"pickle":{"id":"3","uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.feature","location":{"line":5,"column":3},"astNodeIds":["1"],"tags":[],"name":"A passing scenario","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"7","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.ts","location":{"line":15}}}} +{"hook":{"id":"4","type":"BEFORE_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.ts","location":{"line":3}}}} +{"hook":{"id":"5","type":"BEFORE_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.ts","location":{"line":7}}}} +{"hook":{"id":"6","type":"BEFORE_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.ts","location":{"line":11}}}} +{"hook":{"id":"8","type":"AFTER_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.ts","location":{"line":19}}}} +{"hook":{"id":"9","type":"AFTER_TEST_RUN","sourceReference":{"uri":"samples/global-hooks-beforeall-error/global-hooks-beforeall-error.ts","location":{"line":23}}}} +{"testRunStarted":{"id":"10","timestamp":{"seconds":0,"nanos":0}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"11","hookId":"4","timestamp":{"seconds":0,"nanos":1000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"11","timestamp":{"seconds":0,"nanos":2000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"12","hookId":"5","timestamp":{"seconds":0,"nanos":3000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"12","timestamp":{"seconds":0,"nanos":4000000},"result":{"message":"BeforeAll hook went wrong","exception":{"type":"Error","message":"BeforeAll hook went wrong","stackTrace":"Error: BeforeAll hook went wrong\nsamples/global-hooks-beforeall-error/global-hooks-beforeall-error.ts:7"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"13","hookId":"6","timestamp":{"seconds":0,"nanos":5000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"13","timestamp":{"seconds":0,"nanos":6000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"14","hookId":"9","timestamp":{"seconds":0,"nanos":7000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"14","timestamp":{"seconds":0,"nanos":8000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"10","id":"15","hookId":"8","timestamp":{"seconds":0,"nanos":9000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"15","timestamp":{"seconds":0,"nanos":10000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunFinished":{"testRunStartedId":"10","timestamp":{"seconds":0,"nanos":11000000},"success":false}} diff --git a/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.ts b/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.ts new file mode 100644 index 00000000..3a526374 --- /dev/null +++ b/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.ts @@ -0,0 +1,25 @@ +import { When, BeforeAll, AfterAll } from '@cucumber/fake-cucumber' + +BeforeAll({}, function () { + // no-op +}) + +BeforeAll({}, function () { + throw new Error('BeforeAll hook went wrong') +}) + +BeforeAll({}, function () { + // no-op +}) + +When('a step passes', function () { + // no-op +}) + +AfterAll({}, function () { + // no-op +}) + +AfterAll({}, function () { + // no-op +}) diff --git a/compatibility/global-hooks/global-hooks.feature b/compatibility/global-hooks/global-hooks.feature new file mode 100644 index 00000000..715a76f3 --- /dev/null +++ b/compatibility/global-hooks/global-hooks.feature @@ -0,0 +1,10 @@ +Feature: Global hooks + Hooks can be at the test run level, so they run once before or after all test cases. + + AfterAll hooks are executed in reverse order of definition. + + Scenario: A passing scenario + When a step passes + + Scenario: A failing scenario + When a step fails diff --git a/compatibility/global-hooks/global-hooks.ndjson b/compatibility/global-hooks/global-hooks.ndjson new file mode 100644 index 00000000..baa7783a --- /dev/null +++ b/compatibility/global-hooks/global-hooks.ndjson @@ -0,0 +1,31 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Global hooks\n Hooks can be at the test run level, so they run once before or after all test cases.\n\n AfterAll hooks are executed in reverse order of definition.\n\n Scenario: A passing scenario\n When a step passes\n\n Scenario: A failing scenario\n When a step fails\n","uri":"samples/global-hooks/global-hooks.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Global hooks","description":" Hooks can be at the test run level, so they run once before or after all test cases.\n\n AfterAll hooks are executed in reverse order of definition.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"A passing scenario","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"A failing scenario","description":"","steps":[{"id":"2","location":{"line":10,"column":5},"keyword":"When ","keywordType":"Action","text":"a step fails"}],"examples":[]}}]},"comments":[],"uri":"samples/global-hooks/global-hooks.feature"}} +{"pickle":{"id":"5","uri":"samples/global-hooks/global-hooks.feature","location":{"line":6,"column":3},"astNodeIds":["1"],"tags":[],"name":"A passing scenario","language":"en","steps":[{"id":"4","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"pickle":{"id":"7","uri":"samples/global-hooks/global-hooks.feature","location":{"line":9,"column":3},"astNodeIds":["3"],"tags":[],"name":"A failing scenario","language":"en","steps":[{"id":"6","text":"a step fails","type":"Action","astNodeIds":["2"]}]}} +{"stepDefinition":{"id":"10","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/global-hooks/global-hooks.ts","location":{"line":11}}}} +{"stepDefinition":{"id":"11","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step fails"},"sourceReference":{"uri":"samples/global-hooks/global-hooks.ts","location":{"line":15}}}} +{"hook":{"id":"8","type":"BEFORE_TEST_RUN","sourceReference":{"uri":"samples/global-hooks/global-hooks.ts","location":{"line":3}}}} +{"hook":{"id":"9","type":"BEFORE_TEST_RUN","sourceReference":{"uri":"samples/global-hooks/global-hooks.ts","location":{"line":7}}}} +{"hook":{"id":"12","type":"AFTER_TEST_RUN","sourceReference":{"uri":"samples/global-hooks/global-hooks.ts","location":{"line":19}}}} +{"hook":{"id":"13","type":"AFTER_TEST_RUN","sourceReference":{"uri":"samples/global-hooks/global-hooks.ts","location":{"line":23}}}} +{"testRunStarted":{"id":"14","timestamp":{"seconds":0,"nanos":0}}} +{"testRunHookStarted":{"testRunStartedId":"14","id":"15","hookId":"8","timestamp":{"seconds":0,"nanos":1000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"15","timestamp":{"seconds":0,"nanos":2000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"14","id":"16","hookId":"9","timestamp":{"seconds":0,"nanos":3000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"16","timestamp":{"seconds":0,"nanos":4000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testCase":{"id":"17","pickleId":"5","testSteps":[{"id":"18","pickleStepId":"4","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"14"}} +{"testCase":{"id":"19","pickleId":"7","testSteps":[{"id":"20","pickleStepId":"6","stepDefinitionIds":["11"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"14"}} +{"testCaseStarted":{"id":"21","testCaseId":"17","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"21","testStepId":"18","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"21","testStepId":"18","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"21","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"22","testCaseId":"19","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"20","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"20","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/global-hooks/global-hooks.feature:10"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"22","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testRunHookStarted":{"testRunStartedId":"14","id":"23","hookId":"13","timestamp":{"seconds":0,"nanos":13000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"23","timestamp":{"seconds":0,"nanos":14000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunHookStarted":{"testRunStartedId":"14","id":"24","hookId":"12","timestamp":{"seconds":0,"nanos":15000000}}} +{"testRunHookFinished":{"testRunHookStartedId":"24","timestamp":{"seconds":0,"nanos":16000000},"result":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}}}} +{"testRunFinished":{"testRunStartedId":"14","timestamp":{"seconds":0,"nanos":17000000},"success":false}} diff --git a/compatibility/global-hooks/global-hooks.ts b/compatibility/global-hooks/global-hooks.ts new file mode 100644 index 00000000..c86714a5 --- /dev/null +++ b/compatibility/global-hooks/global-hooks.ts @@ -0,0 +1,25 @@ +import { When, BeforeAll, AfterAll } from '@cucumber/fake-cucumber' + +BeforeAll({}, function () { + // no-op +}) + +BeforeAll({}, function () { + // no-op +}) + +When('a step passes', function () { + // no-op +}) + +When('a step fails', function () { + throw new Error('Exception in step') +}) + +AfterAll({}, function () { + // no-op +}) + +AfterAll({}, function () { + // no-op +}) diff --git a/compatibility/hooks-attachment/.gitattributes b/compatibility/hooks-attachment/.gitattributes new file mode 100644 index 00000000..6ea7b312 --- /dev/null +++ b/compatibility/hooks-attachment/.gitattributes @@ -0,0 +1,4 @@ +# SVG files are plain text. So git will change line endings on windows. +# Because we expect the image to have been encoded in base64 with lf rather than +# crlf this is not desirable. +cucumber.svg eol=lf diff --git a/compatibility/hooks-attachment/cucumber.svg b/compatibility/hooks-attachment/cucumber.svg new file mode 100644 index 00000000..e76ff7fa --- /dev/null +++ b/compatibility/hooks-attachment/cucumber.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/compatibility/hooks-attachment/hooks-attachment.feature b/compatibility/hooks-attachment/hooks-attachment.feature new file mode 100644 index 00000000..721e3470 --- /dev/null +++ b/compatibility/hooks-attachment/hooks-attachment.feature @@ -0,0 +1,7 @@ +Feature: Hooks - Attachments + Hooks are special steps that run before or after each scenario's steps. + + Like regular steps, it is possible to attach a file to the output. + + Scenario: With an valid attachment in the hook and a passed step + When a step passes diff --git a/compatibility/hooks-attachment/hooks-attachment.ndjson b/compatibility/hooks-attachment/hooks-attachment.ndjson new file mode 100644 index 00000000..b8383ab0 --- /dev/null +++ b/compatibility/hooks-attachment/hooks-attachment.ndjson @@ -0,0 +1,20 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Hooks - Attachments\n Hooks are special steps that run before or after each scenario's steps.\n\n Like regular steps, it is possible to attach a file to the output.\n\n Scenario: With an valid attachment in the hook and a passed step\n When a step passes\n","uri":"samples/hooks-attachment/hooks-attachment.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks - Attachments","description":" Hooks are special steps that run before or after each scenario's steps.\n\n Like regular steps, it is possible to attach a file to the output.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"With an valid attachment in the hook and a passed step","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-attachment/hooks-attachment.feature"}} +{"pickle":{"id":"3","uri":"samples/hooks-attachment/hooks-attachment.feature","location":{"line":6,"column":3},"astNodeIds":["1"],"tags":[],"name":"With an valid attachment in the hook and a passed step","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"5","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/hooks-attachment/hooks-attachment.ts","location":{"line":11}}}} +{"hook":{"id":"4","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks-attachment/hooks-attachment.ts","location":{"line":4}}}} +{"hook":{"id":"6","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks-attachment/hooks-attachment.ts","location":{"line":15}}}} +{"testRunStarted":{"id":"7","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"8","pickleId":"3","testSteps":[{"id":"9","hookId":"4"},{"id":"10","pickleStepId":"2","stepDefinitionIds":["5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"11","hookId":"6"}],"testRunStartedId":"7"}} +{"testCaseStarted":{"id":"12","testCaseId":"8","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"12","testStepId":"9","timestamp":{"seconds":0,"nanos":2000000}}} +{"attachment":{"testCaseStartedId":"12","testStepId":"9","body":"PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJtbC0zIG1sLW1kLTAiIHZpZXdCb3g9IjAgMCA0MC41OSA0Ni4zMSIgd2lkdGg9IjQwLjU5IiBoZWlnaHQ9IjQ2LjMxIj4KICAgIDxnPgogICAgICAgIDxwYXRoIGZpbGw9IiMyM2Q5NmMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTMwLjI4MyAzLjY0NXEtLjUyOC0uMzE3LTEuMDgtLjU5M2ExNi4xNjQgMTYuMTY0IDAgMDAtMS4xNTQtLjUxOGMtLjEyNC0uMDUyLS4yNDctLjEtLjM3Mi0uMTQ5LS4zNDMtLjEyNy0uNjg5LS4yNjgtMS4wNDItLjM3MWExOS40MjcgMTkuNDI3IDAgMTAtOS43OTIgMzcuNTF2NS41NmMxMS42NzYtMS43NTMgMjIuMDE2LTEwLjk3OSAyMi43ODctMjMuMDkzLjQ1OS03LjI4OS0zLjE5My0xNC43My05LjM0Ny0xOC4zNDZ6Ii8+CiAgICAgICAgPHBhdGggZmlsbD0iIzE3MzY0NyIgZD0iTTE1Ljc4NyA0Ni4zMDd2LTUuOTM1QTIwLjQ3MiAyMC40NzIgMCAxMTI2Ljk1OSAxLjAxNWMuMjc0LjA4LjU1Ny4xODcuODMyLjI5MWwuMjQ4LjA5M2MuMTY1LjA2NC4yOTEuMTEzLjQxNy4xNjcuMzQ4LjEzNy43MzkuMzEzIDEuMjA4LjU0M3EuNTg5LjI5NSAxLjE1My42MzNjNi4zOTMgMy43NTYgMTAuMzU0IDExLjUxOCA5Ljg1NyAxOS4zMTYtLjc2MyAxMi0xMC43MjIgMjIuMTIyLTIzLjY3OSAyNC4wNjd6bTQuOC00NC4yMTRoLS4wMjZhMTguMzY2IDE4LjM2NiAwIDAwLTMuNTI0IDM2LjQwOGwuODUuMTY1djUuMThjMTEuMzkyLTIuMjI0IDIwLjAwOS0xMS4yNzIgMjAuNjg2LTIxLjkyMi40NDgtNy4wMzMtMy4xLTE0LjAxOC04LjgzLTE3LjM4M2wtLjAwOC0uMDA1QTE0LjY5MSAxNC42OTEgMCAwMDI3LjY1NCAzLjVhNS43NCA1Ljc0IDAgMDAtLjM0NC0uMTM4bC0uMjctLjFhOS40OSA5LjQ5IDAgMDAtLjcwOC0uMjQ5IDE4LjQyNSAxOC40MjUgMCAwMC01Ljc0My0uOTJ6Ii8+CiAgICAgICAgPHBhdGggZmlsbD0iIzE3MzY0NyIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTYuNjY2IDEwLjU4YTEuOCAxLjggMCAwMTEuNTgzLjYwOCA0LjE4NCA0LjE4NCAwIDAxLjcyOCAxLjEwN2MuNjQ1IDEuNDIyIDEuMDI3IDMuNDYxLjIzIDQuNjA1YTYuMzM0IDYuMzM0IDAgMDEtMy45ODEtMy4wODcgMy4yMzYgMy4yMzYgMCAwMS0uMzQ3LTEuMzM5IDEuOTU3IDEuOTU3IDAgMDExLjc4Ny0xLjg5NHptLTUuNjgzIDguMDI1YTcuNzQyIDcuNzQyIDAgMDAxLjIxOC43MzcgNS43ODkgNS43ODkgMCAwMDQuODgzLS4xMzggNi4xMTYgNi4xMTYgMCAwMC0zLjM0NS0zLjQ1IDMuNjY0IDMuNjY0IDAgMDAtMS40NDItLjMyMSAxLjg4NCAxLjg4NCAwIDAwLS4zMTkgMCAxLjc2NiAxLjc2NiAwIDAwLS45OTUgMy4xNzJ6bTYuMSAzLjQzM2MtLjc3Ny0uNTE4LTIuMzc5LS4zMDktMy4zMTItLjI5MmE0LjQxNiA0LjQxNiAwIDAwLTEuNjY2LjM1MiAzLjUgMy41IDAgMDAtMS4yMTguNzM4IDEuODE3IDEuODE3IDAgMDAxLjQwOSAzLjE3MSAzLjMgMy4zIDAgMDAxLjQ0Mi0uMzIxYzEuNDM2LS42MiAzLjE0MS0yLjMyIDMuMzQ2LTMuNjQ4em0yLjYxIDJhNi41NTYgNi41NTYgMCAwMC0zLjcyNCAzLjUwNiAzLjA5MSAzLjA5MSAwIDAwLS4zMjEgMS4zMTQgMS45MDcgMS45MDcgMCAwMDMuMyAxLjM0NiA3LjQyMiA3LjQyMiAwIDAwLjctMS4yMThjLjYyMS0xLjMzMy44NjYtMy43Mi4wNDYtNC45NDh6bTIuNTU3LTcuMTY3YTUuOTQxIDUuOTQxIDAgMDAzLjctMy4xNjcgMy4yNDMgMy4yNDMgMCAwMC4zMTktMS4zNDYgMS45MTUgMS45MTUgMCAwMC0xLjc5NC0xLjk1NCAxLjgzMiAxLjgzMiAwIDAwLTEuNi42NDEgNy4zODIgNy4zODIgMCAwMC0uNzA1IDEuMjE4Yy0uNjIgMS40MzQtLjg0MiAzLjQ4LjA4MSA0LjYwM3ptNC4yMDggMTIuMTE1YTMuMjQ0IDMuMjQ0IDAgMDAtLjMyMS0xLjM0NSA1Ljg2OSA1Ljg2OSAwIDAwLTMuNTU0LTMuMjY5IDUuMzg2IDUuMzg2IDAgMDAtLjIyNiA0LjcxMSA0LjE0NyA0LjE0NyAwIDAwLjcgMS4xMjFjMS4xMzMgMS4yMyAzLjUwNS4zMiAzLjQwMi0xLjIxOHptNC4yLTYuMjhhNy40NjYgNy40NjYgMCAwMC0xLjIxNy0uNyA0LjQyNSA0LjQyNSAwIDAwLTEuNjY2LS4zNTIgNi40IDYuNCAwIDAwLTMuMTg4LjU1NSA1Ljk1OSA1Ljk1OSAwIDAwMy4zMTYgMy4zODYgMy42NzIgMy42NzIgMCAwMDEuNDQyLjMyIDEuOCAxLjggMCAwMDEuMzEtMy4yMDl6Ii8+CiAgICA8L2c+Cjwvc3ZnPg==","contentEncoding":"BASE64","mediaType":"image/svg+xml","timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepFinished":{"testCaseStartedId":"12","testStepId":"9","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepStarted":{"testCaseStartedId":"12","testStepId":"10","timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepFinished":{"testCaseStartedId":"12","testStepId":"10","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepStarted":{"testCaseStartedId":"12","testStepId":"11","timestamp":{"seconds":0,"nanos":7000000}}} +{"attachment":{"testCaseStartedId":"12","testStepId":"11","body":"PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIGNsYXNzPSJtbC0zIG1sLW1kLTAiIHZpZXdCb3g9IjAgMCA0MC41OSA0Ni4zMSIgd2lkdGg9IjQwLjU5IiBoZWlnaHQ9IjQ2LjMxIj4KICAgIDxnPgogICAgICAgIDxwYXRoIGZpbGw9IiMyM2Q5NmMiIGZpbGwtcnVsZT0iZXZlbm9kZCIgZD0iTTMwLjI4MyAzLjY0NXEtLjUyOC0uMzE3LTEuMDgtLjU5M2ExNi4xNjQgMTYuMTY0IDAgMDAtMS4xNTQtLjUxOGMtLjEyNC0uMDUyLS4yNDctLjEtLjM3Mi0uMTQ5LS4zNDMtLjEyNy0uNjg5LS4yNjgtMS4wNDItLjM3MWExOS40MjcgMTkuNDI3IDAgMTAtOS43OTIgMzcuNTF2NS41NmMxMS42NzYtMS43NTMgMjIuMDE2LTEwLjk3OSAyMi43ODctMjMuMDkzLjQ1OS03LjI4OS0zLjE5My0xNC43My05LjM0Ny0xOC4zNDZ6Ii8+CiAgICAgICAgPHBhdGggZmlsbD0iIzE3MzY0NyIgZD0iTTE1Ljc4NyA0Ni4zMDd2LTUuOTM1QTIwLjQ3MiAyMC40NzIgMCAxMTI2Ljk1OSAxLjAxNWMuMjc0LjA4LjU1Ny4xODcuODMyLjI5MWwuMjQ4LjA5M2MuMTY1LjA2NC4yOTEuMTEzLjQxNy4xNjcuMzQ4LjEzNy43MzkuMzEzIDEuMjA4LjU0M3EuNTg5LjI5NSAxLjE1My42MzNjNi4zOTMgMy43NTYgMTAuMzU0IDExLjUxOCA5Ljg1NyAxOS4zMTYtLjc2MyAxMi0xMC43MjIgMjIuMTIyLTIzLjY3OSAyNC4wNjd6bTQuOC00NC4yMTRoLS4wMjZhMTguMzY2IDE4LjM2NiAwIDAwLTMuNTI0IDM2LjQwOGwuODUuMTY1djUuMThjMTEuMzkyLTIuMjI0IDIwLjAwOS0xMS4yNzIgMjAuNjg2LTIxLjkyMi40NDgtNy4wMzMtMy4xLTE0LjAxOC04LjgzLTE3LjM4M2wtLjAwOC0uMDA1QTE0LjY5MSAxNC42OTEgMCAwMDI3LjY1NCAzLjVhNS43NCA1Ljc0IDAgMDAtLjM0NC0uMTM4bC0uMjctLjFhOS40OSA5LjQ5IDAgMDAtLjcwOC0uMjQ5IDE4LjQyNSAxOC40MjUgMCAwMC01Ljc0My0uOTJ6Ii8+CiAgICAgICAgPHBhdGggZmlsbD0iIzE3MzY0NyIgZmlsbC1ydWxlPSJldmVub2RkIiBkPSJNMTYuNjY2IDEwLjU4YTEuOCAxLjggMCAwMTEuNTgzLjYwOCA0LjE4NCA0LjE4NCAwIDAxLjcyOCAxLjEwN2MuNjQ1IDEuNDIyIDEuMDI3IDMuNDYxLjIzIDQuNjA1YTYuMzM0IDYuMzM0IDAgMDEtMy45ODEtMy4wODcgMy4yMzYgMy4yMzYgMCAwMS0uMzQ3LTEuMzM5IDEuOTU3IDEuOTU3IDAgMDExLjc4Ny0xLjg5NHptLTUuNjgzIDguMDI1YTcuNzQyIDcuNzQyIDAgMDAxLjIxOC43MzcgNS43ODkgNS43ODkgMCAwMDQuODgzLS4xMzggNi4xMTYgNi4xMTYgMCAwMC0zLjM0NS0zLjQ1IDMuNjY0IDMuNjY0IDAgMDAtMS40NDItLjMyMSAxLjg4NCAxLjg4NCAwIDAwLS4zMTkgMCAxLjc2NiAxLjc2NiAwIDAwLS45OTUgMy4xNzJ6bTYuMSAzLjQzM2MtLjc3Ny0uNTE4LTIuMzc5LS4zMDktMy4zMTItLjI5MmE0LjQxNiA0LjQxNiAwIDAwLTEuNjY2LjM1MiAzLjUgMy41IDAgMDAtMS4yMTguNzM4IDEuODE3IDEuODE3IDAgMDAxLjQwOSAzLjE3MSAzLjMgMy4zIDAgMDAxLjQ0Mi0uMzIxYzEuNDM2LS42MiAzLjE0MS0yLjMyIDMuMzQ2LTMuNjQ4em0yLjYxIDJhNi41NTYgNi41NTYgMCAwMC0zLjcyNCAzLjUwNiAzLjA5MSAzLjA5MSAwIDAwLS4zMjEgMS4zMTQgMS45MDcgMS45MDcgMCAwMDMuMyAxLjM0NiA3LjQyMiA3LjQyMiAwIDAwLjctMS4yMThjLjYyMS0xLjMzMy44NjYtMy43Mi4wNDYtNC45NDh6bTIuNTU3LTcuMTY3YTUuOTQxIDUuOTQxIDAgMDAzLjctMy4xNjcgMy4yNDMgMy4yNDMgMCAwMC4zMTktMS4zNDYgMS45MTUgMS45MTUgMCAwMC0xLjc5NC0xLjk1NCAxLjgzMiAxLjgzMiAwIDAwLTEuNi42NDEgNy4zODIgNy4zODIgMCAwMC0uNzA1IDEuMjE4Yy0uNjIgMS40MzQtLjg0MiAzLjQ4LjA4MSA0LjYwM3ptNC4yMDggMTIuMTE1YTMuMjQ0IDMuMjQ0IDAgMDAtLjMyMS0xLjM0NSA1Ljg2OSA1Ljg2OSAwIDAwLTMuNTU0LTMuMjY5IDUuMzg2IDUuMzg2IDAgMDAtLjIyNiA0LjcxMSA0LjE0NyA0LjE0NyAwIDAwLjcgMS4xMjFjMS4xMzMgMS4yMyAzLjUwNS4zMiAzLjQwMi0xLjIxOHptNC4yLTYuMjhhNy40NjYgNy40NjYgMCAwMC0xLjIxNy0uNyA0LjQyNSA0LjQyNSAwIDAwLTEuNjY2LS4zNTIgNi40IDYuNCAwIDAwLTMuMTg4LjU1NSA1Ljk1OSA1Ljk1OSAwIDAwMy4zMTYgMy4zODYgMy42NzIgMy42NzIgMCAwMDEuNDQyLjMyIDEuOCAxLjggMCAwMDEuMzEtMy4yMDl6Ii8+CiAgICA8L2c+Cjwvc3ZnPg==","contentEncoding":"BASE64","mediaType":"image/svg+xml","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"12","testStepId":"11","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testCaseFinished":{"testCaseStartedId":"12","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"7","timestamp":{"seconds":0,"nanos":11000000},"success":true}} diff --git a/compatibility/hooks-attachment/hooks-attachment.ts b/compatibility/hooks-attachment/hooks-attachment.ts new file mode 100644 index 00000000..2cc47c73 --- /dev/null +++ b/compatibility/hooks-attachment/hooks-attachment.ts @@ -0,0 +1,20 @@ +import { When, Before, After } from '@cucumber/fake-cucumber' +import fs from 'node:fs' + +Before({}, async function () { + await this.attach( + fs.createReadStream(import.meta.dirname + '/cucumber.svg'), + 'image/svg+xml' + ) +}) + +When('a step passes', function () { + // no-op +}) + +After({}, async function () { + await this.attach( + fs.createReadStream(import.meta.dirname + '/cucumber.svg'), + 'image/svg+xml' + ) +}) diff --git a/compatibility/hooks-conditional/hooks-conditional.feature b/compatibility/hooks-conditional/hooks-conditional.feature new file mode 100644 index 00000000..86aa9fb3 --- /dev/null +++ b/compatibility/hooks-conditional/hooks-conditional.feature @@ -0,0 +1,16 @@ +Feature: Hooks - Conditional execution + Hooks are special steps that run before or after each scenario's steps. + + They can also conditionally target specific scenarios, using tag expressions. + + @fail-before + Scenario: A failure in the before hook and a skipped step + When a step passes + + @fail-after + Scenario: A failure in the after hook and a passed step + When a step passes + + @passing-hook + Scenario: With an tag, a passed step and hook + When a step passes diff --git a/compatibility/hooks-conditional/hooks-conditional.ndjson b/compatibility/hooks-conditional/hooks-conditional.ndjson new file mode 100644 index 00000000..773dd04b --- /dev/null +++ b/compatibility/hooks-conditional/hooks-conditional.ndjson @@ -0,0 +1,36 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Hooks - Conditional execution\n Hooks are special steps that run before or after each scenario's steps.\n\n They can also conditionally target specific scenarios, using tag expressions.\n\n @fail-before\n Scenario: A failure in the before hook and a skipped step\n When a step passes\n\n @fail-after\n Scenario: A failure in the after hook and a passed step\n When a step passes\n\n @passing-hook\n Scenario: With an tag, a passed step and hook\n When a step passes\n","uri":"samples/hooks-conditional/hooks-conditional.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks - Conditional execution","description":" Hooks are special steps that run before or after each scenario's steps.\n\n They can also conditionally target specific scenarios, using tag expressions.","children":[{"scenario":{"id":"2","tags":[{"location":{"line":6,"column":3},"name":"@fail-before","id":"1"}],"location":{"line":7,"column":3},"keyword":"Scenario","name":"A failure in the before hook and a skipped step","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"5","tags":[{"location":{"line":10,"column":3},"name":"@fail-after","id":"4"}],"location":{"line":11,"column":3},"keyword":"Scenario","name":"A failure in the after hook and a passed step","description":"","steps":[{"id":"3","location":{"line":12,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"8","tags":[{"location":{"line":14,"column":3},"name":"@passing-hook","id":"7"}],"location":{"line":15,"column":3},"keyword":"Scenario","name":"With an tag, a passed step and hook","description":"","steps":[{"id":"6","location":{"line":16,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-conditional/hooks-conditional.feature"}} +{"pickle":{"id":"10","uri":"samples/hooks-conditional/hooks-conditional.feature","location":{"line":7,"column":3},"astNodeIds":["2"],"tags":[{"name":"@fail-before","astNodeId":"1"}],"name":"A failure in the before hook and a skipped step","language":"en","steps":[{"id":"9","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"pickle":{"id":"12","uri":"samples/hooks-conditional/hooks-conditional.feature","location":{"line":11,"column":3},"astNodeIds":["5"],"tags":[{"name":"@fail-after","astNodeId":"4"}],"name":"A failure in the after hook and a passed step","language":"en","steps":[{"id":"11","text":"a step passes","type":"Action","astNodeIds":["3"]}]}} +{"pickle":{"id":"14","uri":"samples/hooks-conditional/hooks-conditional.feature","location":{"line":15,"column":3},"astNodeIds":["8"],"tags":[{"name":"@passing-hook","astNodeId":"7"}],"name":"With an tag, a passed step and hook","language":"en","steps":[{"id":"13","text":"a step passes","type":"Action","astNodeIds":["6"]}]}} +{"stepDefinition":{"id":"17","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/hooks-conditional/hooks-conditional.ts","location":{"line":11}}}} +{"hook":{"id":"15","type":"BEFORE_TEST_CASE","tagExpression":"@passing-hook","sourceReference":{"uri":"samples/hooks-conditional/hooks-conditional.ts","location":{"line":3}}}} +{"hook":{"id":"16","type":"BEFORE_TEST_CASE","tagExpression":"@fail-before","sourceReference":{"uri":"samples/hooks-conditional/hooks-conditional.ts","location":{"line":7}}}} +{"hook":{"id":"18","type":"AFTER_TEST_CASE","tagExpression":"@fail-after","sourceReference":{"uri":"samples/hooks-conditional/hooks-conditional.ts","location":{"line":15}}}} +{"hook":{"id":"19","type":"AFTER_TEST_CASE","tagExpression":"@passing-hook","sourceReference":{"uri":"samples/hooks-conditional/hooks-conditional.ts","location":{"line":19}}}} +{"testRunStarted":{"id":"20","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"21","pickleId":"10","testSteps":[{"id":"22","hookId":"16"},{"id":"23","pickleStepId":"9","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}} +{"testCase":{"id":"24","pickleId":"12","testSteps":[{"id":"25","pickleStepId":"11","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"26","hookId":"18"}],"testRunStartedId":"20"}} +{"testCase":{"id":"27","pickleId":"14","testSteps":[{"id":"28","hookId":"15"},{"id":"29","pickleStepId":"13","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"30","hookId":"19"}],"testRunStartedId":"20"}} +{"testCaseStarted":{"id":"31","testCaseId":"21","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"31","testStepId":"22","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"31","testStepId":"22","testStepResult":{"message":"Exception in conditional hook","exception":{"type":"Error","message":"Exception in conditional hook","stackTrace":"Error: Exception in conditional hook\nsamples/hooks-conditional/hooks-conditional.feature:7"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"31","testStepId":"23","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"31","testStepId":"23","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testCaseFinished":{"testCaseStartedId":"31","timestamp":{"seconds":0,"nanos":6000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"32","testCaseId":"24","timestamp":{"seconds":0,"nanos":7000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"32","testStepId":"25","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"32","testStepId":"25","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testStepStarted":{"testCaseStartedId":"32","testStepId":"26","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"32","testStepId":"26","testStepResult":{"message":"Exception in conditional hook","exception":{"type":"Error","message":"Exception in conditional hook","stackTrace":"Error: Exception in conditional hook\nsamples/hooks-conditional/hooks-conditional.feature:11"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"32","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"33","testCaseId":"27","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"33","testStepId":"28","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"33","testStepId":"28","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testStepStarted":{"testCaseStartedId":"33","testStepId":"29","timestamp":{"seconds":0,"nanos":16000000}}} +{"testStepFinished":{"testCaseStartedId":"33","testStepId":"29","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":17000000}}} +{"testStepStarted":{"testCaseStartedId":"33","testStepId":"30","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"33","testStepId":"30","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"33","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"20","timestamp":{"seconds":0,"nanos":21000000},"success":false}} diff --git a/compatibility/hooks-conditional/hooks-conditional.ts b/compatibility/hooks-conditional/hooks-conditional.ts new file mode 100644 index 00000000..b47008e3 --- /dev/null +++ b/compatibility/hooks-conditional/hooks-conditional.ts @@ -0,0 +1,21 @@ +import { When, Before, After } from '@cucumber/fake-cucumber' + +Before({tags: '@passing-hook'}, async function () { + // no-op +}) + +Before({tags: '@fail-before'}, function () { + throw new Error('Exception in conditional hook') +}) + +When('a step passes', function () { + // no-op +}) + +After({tags: '@fail-after'}, function () { + throw new Error('Exception in conditional hook') +}) + +After({tags: '@passing-hook'}, async function () { + // no-op +}) diff --git a/compatibility/hooks-named/hooks-named.feature b/compatibility/hooks-named/hooks-named.feature new file mode 100644 index 00000000..41007112 --- /dev/null +++ b/compatibility/hooks-named/hooks-named.feature @@ -0,0 +1,8 @@ +Feature: Hooks - Named + Hooks are special steps that run before or after each scenario's steps. + + Hooks can be given a name. Which is nice for reporting. Otherwise they work + exactly the same as regular hooks. + + Scenario: With a named before and after hook + When a step passes diff --git a/compatibility/hooks-named/hooks-named.ndjson b/compatibility/hooks-named/hooks-named.ndjson new file mode 100644 index 00000000..16e20a1d --- /dev/null +++ b/compatibility/hooks-named/hooks-named.ndjson @@ -0,0 +1,18 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Hooks - Named\n Hooks are special steps that run before or after each scenario's steps.\n\n Hooks can be given a name. Which is nice for reporting. Otherwise they work\n exactly the same as regular hooks.\n\n Scenario: With a named before and after hook\n When a step passes\n","uri":"samples/hooks-named/hooks-named.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks - Named","description":" Hooks are special steps that run before or after each scenario's steps.\n\n Hooks can be given a name. Which is nice for reporting. Otherwise they work\n exactly the same as regular hooks.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"With a named before and after hook","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-named/hooks-named.feature"}} +{"pickle":{"id":"3","uri":"samples/hooks-named/hooks-named.feature","location":{"line":7,"column":3},"astNodeIds":["1"],"tags":[],"name":"With a named before and after hook","language":"en","steps":[{"id":"2","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"5","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/hooks-named/hooks-named.ts","location":{"line":7}}}} +{"hook":{"id":"4","type":"BEFORE_TEST_CASE","name":"A named before hook","sourceReference":{"uri":"samples/hooks-named/hooks-named.ts","location":{"line":3}}}} +{"hook":{"id":"6","type":"AFTER_TEST_CASE","name":"A named after hook","sourceReference":{"uri":"samples/hooks-named/hooks-named.ts","location":{"line":11}}}} +{"testRunStarted":{"id":"7","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"8","pickleId":"3","testSteps":[{"id":"9","hookId":"4"},{"id":"10","pickleStepId":"2","stepDefinitionIds":["5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"11","hookId":"6"}],"testRunStartedId":"7"}} +{"testCaseStarted":{"id":"12","testCaseId":"8","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"12","testStepId":"9","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"12","testStepId":"9","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"12","testStepId":"10","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"12","testStepId":"10","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"12","testStepId":"11","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"12","testStepId":"11","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"12","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"7","timestamp":{"seconds":0,"nanos":9000000},"success":true}} diff --git a/compatibility/hooks-named/hooks-named.ts b/compatibility/hooks-named/hooks-named.ts new file mode 100644 index 00000000..a21cf9a2 --- /dev/null +++ b/compatibility/hooks-named/hooks-named.ts @@ -0,0 +1,13 @@ +import { When, Before, After } from '@cucumber/fake-cucumber' + +Before({name: 'A named before hook'}, function () { + // no-op +}) + +When('a step passes', function () { + // no-op +}) + +After({name: 'A named after hook'}, function () { + // no-op +}) diff --git a/compatibility/hooks-skipped/hooks-skipped.feature b/compatibility/hooks-skipped/hooks-skipped.feature new file mode 100644 index 00000000..a721b409 --- /dev/null +++ b/compatibility/hooks-skipped/hooks-skipped.feature @@ -0,0 +1,26 @@ +Feature: Hooks and skipped + + Before and After hooks behave a little differently when it comes to skipping. + + Scenario: Skip from a step + + Skipping from a step causes all subsequent steps to be skipped, but any After hooks + will still be run normally. + + Given a step that skips + + @skip-before + Scenario: Skip from a Before hook + + Skipping from a Before hook will cause all subsequent Before hooks and steps to be skipped, + but any After hooks will still be run normally. + + Given a normal step + + @skip-after + Scenario: Skip from an After hook + + Skipping from a After hook will only mark that hook as skipped. Any subsequent After hooks + will still be run normally. + + Given a normal step diff --git a/compatibility/hooks-skipped/hooks-skipped.ndjson b/compatibility/hooks-skipped/hooks-skipped.ndjson new file mode 100644 index 00000000..d05380be --- /dev/null +++ b/compatibility/hooks-skipped/hooks-skipped.ndjson @@ -0,0 +1,59 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Hooks and skipped\n\n Before and After hooks behave a little differently when it comes to skipping.\n\n Scenario: Skip from a step\n\n Skipping from a step causes all subsequent steps to be skipped, but any After hooks\n will still be run normally.\n\n Given a step that skips\n\n @skip-before\n Scenario: Skip from a Before hook\n\n Skipping from a Before hook will cause all subsequent Before hooks and steps to be skipped,\n but any After hooks will still be run normally.\n\n Given a normal step\n\n @skip-after\n Scenario: Skip from an After hook\n\n Skipping from a After hook will only mark that hook as skipped. Any subsequent After hooks\n will still be run normally.\n\n Given a normal step\n","uri":"samples/hooks-skipped/hooks-skipped.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks and skipped","description":" Before and After hooks behave a little differently when it comes to skipping.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"Skip from a step","description":" Skipping from a step causes all subsequent steps to be skipped, but any After hooks\n will still be run normally.","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that skips"}],"examples":[]}},{"scenario":{"id":"4","tags":[{"location":{"line":12,"column":3},"name":"@skip-before","id":"3"}],"location":{"line":13,"column":3},"keyword":"Scenario","name":"Skip from a Before hook","description":" Skipping from a Before hook will cause all subsequent Before hooks and steps to be skipped,\n but any After hooks will still be run normally.","steps":[{"id":"2","location":{"line":18,"column":5},"keyword":"Given ","keywordType":"Context","text":"a normal step"}],"examples":[]}},{"scenario":{"id":"7","tags":[{"location":{"line":20,"column":3},"name":"@skip-after","id":"6"}],"location":{"line":21,"column":3},"keyword":"Scenario","name":"Skip from an After hook","description":" Skipping from a After hook will only mark that hook as skipped. Any subsequent After hooks\n will still be run normally.","steps":[{"id":"5","location":{"line":26,"column":5},"keyword":"Given ","keywordType":"Context","text":"a normal step"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-skipped/hooks-skipped.feature"}} +{"pickle":{"id":"9","uri":"samples/hooks-skipped/hooks-skipped.feature","location":{"line":5,"column":3},"astNodeIds":["1"],"tags":[],"name":"Skip from a step","language":"en","steps":[{"id":"8","text":"a step that skips","type":"Context","astNodeIds":["0"]}]}} +{"pickle":{"id":"11","uri":"samples/hooks-skipped/hooks-skipped.feature","location":{"line":13,"column":3},"astNodeIds":["4"],"tags":[{"name":"@skip-before","astNodeId":"3"}],"name":"Skip from a Before hook","language":"en","steps":[{"id":"10","text":"a normal step","type":"Context","astNodeIds":["2"]}]}} +{"pickle":{"id":"13","uri":"samples/hooks-skipped/hooks-skipped.feature","location":{"line":21,"column":3},"astNodeIds":["7"],"tags":[{"name":"@skip-after","astNodeId":"6"}],"name":"Skip from an After hook","language":"en","steps":[{"id":"12","text":"a normal step","type":"Context","astNodeIds":["5"]}]}} +{"stepDefinition":{"id":"17","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a normal step"},"sourceReference":{"uri":"samples/hooks-skipped/hooks-skipped.ts","location":{"line":15}}}} +{"stepDefinition":{"id":"18","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that skips"},"sourceReference":{"uri":"samples/hooks-skipped/hooks-skipped.ts","location":{"line":19}}}} +{"hook":{"id":"14","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks-skipped/hooks-skipped.ts","location":{"line":3}}}} +{"hook":{"id":"15","type":"BEFORE_TEST_CASE","tagExpression":"@skip-before","sourceReference":{"uri":"samples/hooks-skipped/hooks-skipped.ts","location":{"line":7}}}} +{"hook":{"id":"16","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks-skipped/hooks-skipped.ts","location":{"line":11}}}} +{"hook":{"id":"19","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks-skipped/hooks-skipped.ts","location":{"line":23}}}} +{"hook":{"id":"20","type":"AFTER_TEST_CASE","tagExpression":"@skip-after","sourceReference":{"uri":"samples/hooks-skipped/hooks-skipped.ts","location":{"line":27}}}} +{"hook":{"id":"21","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks-skipped/hooks-skipped.ts","location":{"line":31}}}} +{"testRunStarted":{"id":"22","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"23","pickleId":"9","testSteps":[{"id":"24","hookId":"14"},{"id":"25","hookId":"16"},{"id":"26","pickleStepId":"8","stepDefinitionIds":["18"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"27","hookId":"21"},{"id":"28","hookId":"19"}],"testRunStartedId":"22"}} +{"testCase":{"id":"29","pickleId":"11","testSteps":[{"id":"30","hookId":"14"},{"id":"31","hookId":"15"},{"id":"32","hookId":"16"},{"id":"33","pickleStepId":"10","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"34","hookId":"21"},{"id":"35","hookId":"19"}],"testRunStartedId":"22"}} +{"testCase":{"id":"36","pickleId":"13","testSteps":[{"id":"37","hookId":"14"},{"id":"38","hookId":"16"},{"id":"39","pickleStepId":"12","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"40","hookId":"21"},{"id":"41","hookId":"20"},{"id":"42","hookId":"19"}],"testRunStartedId":"22"}} +{"testCaseStarted":{"id":"43","testCaseId":"23","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"43","testStepId":"24","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"43","testStepId":"24","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"43","testStepId":"25","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"43","testStepId":"25","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"43","testStepId":"26","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"43","testStepId":"26","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testStepStarted":{"testCaseStartedId":"43","testStepId":"27","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"43","testStepId":"27","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testStepStarted":{"testCaseStartedId":"43","testStepId":"28","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"43","testStepId":"28","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"43","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"44","testCaseId":"29","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"44","testStepId":"30","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"44","testStepId":"30","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testStepStarted":{"testCaseStartedId":"44","testStepId":"31","timestamp":{"seconds":0,"nanos":16000000}}} +{"testStepFinished":{"testCaseStartedId":"44","testStepId":"31","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":17000000}}} +{"testStepStarted":{"testCaseStartedId":"44","testStepId":"32","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"44","testStepId":"32","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testStepStarted":{"testCaseStartedId":"44","testStepId":"33","timestamp":{"seconds":0,"nanos":20000000}}} +{"testStepFinished":{"testCaseStartedId":"44","testStepId":"33","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":21000000}}} +{"testStepStarted":{"testCaseStartedId":"44","testStepId":"34","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"44","testStepId":"34","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testStepStarted":{"testCaseStartedId":"44","testStepId":"35","timestamp":{"seconds":0,"nanos":24000000}}} +{"testStepFinished":{"testCaseStartedId":"44","testStepId":"35","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":25000000}}} +{"testCaseFinished":{"testCaseStartedId":"44","timestamp":{"seconds":0,"nanos":26000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"45","testCaseId":"36","timestamp":{"seconds":0,"nanos":27000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"45","testStepId":"37","timestamp":{"seconds":0,"nanos":28000000}}} +{"testStepFinished":{"testCaseStartedId":"45","testStepId":"37","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}} +{"testStepStarted":{"testCaseStartedId":"45","testStepId":"38","timestamp":{"seconds":0,"nanos":30000000}}} +{"testStepFinished":{"testCaseStartedId":"45","testStepId":"38","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}} +{"testStepStarted":{"testCaseStartedId":"45","testStepId":"39","timestamp":{"seconds":0,"nanos":32000000}}} +{"testStepFinished":{"testCaseStartedId":"45","testStepId":"39","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":33000000}}} +{"testStepStarted":{"testCaseStartedId":"45","testStepId":"40","timestamp":{"seconds":0,"nanos":34000000}}} +{"testStepFinished":{"testCaseStartedId":"45","testStepId":"40","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}} +{"testStepStarted":{"testCaseStartedId":"45","testStepId":"41","timestamp":{"seconds":0,"nanos":36000000}}} +{"testStepFinished":{"testCaseStartedId":"45","testStepId":"41","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":37000000}}} +{"testStepStarted":{"testCaseStartedId":"45","testStepId":"42","timestamp":{"seconds":0,"nanos":38000000}}} +{"testStepFinished":{"testCaseStartedId":"45","testStepId":"42","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":39000000}}} +{"testCaseFinished":{"testCaseStartedId":"45","timestamp":{"seconds":0,"nanos":40000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"22","timestamp":{"seconds":0,"nanos":41000000},"success":true}} diff --git a/compatibility/hooks-skipped/hooks-skipped.ts b/compatibility/hooks-skipped/hooks-skipped.ts new file mode 100644 index 00000000..36ec26c5 --- /dev/null +++ b/compatibility/hooks-skipped/hooks-skipped.ts @@ -0,0 +1,33 @@ +import { After, Before, When } from '@cucumber/fake-cucumber' + +Before({}, function () { + // no-op +}) + +Before({ tags: '@skip-before' }, function () { + return 'skipped' +}) + +Before({}, function () { + // no-op +}) + +When('a normal step', function () { + // no-op +}) + +When('a step that skips', function () { + return 'skipped' +}) + +After({}, function () { + // no-op +}) + +After({ tags: '@skip-after' }, function () { + return 'skipped' +}) + +After({}, function () { + // no-op +}) diff --git a/compatibility/hooks-undefined/hooks-undefined.feature b/compatibility/hooks-undefined/hooks-undefined.feature new file mode 100644 index 00000000..a13e450b --- /dev/null +++ b/compatibility/hooks-undefined/hooks-undefined.feature @@ -0,0 +1,5 @@ +Feature: Hooks - With Undefined Steps + Hooks also run before and after undefined steps + + Scenario: No tags and a undefined step + When a step does not exist diff --git a/compatibility/hooks-undefined/hooks-undefined.ndjson b/compatibility/hooks-undefined/hooks-undefined.ndjson new file mode 100644 index 00000000..ebd0b7fe --- /dev/null +++ b/compatibility/hooks-undefined/hooks-undefined.ndjson @@ -0,0 +1,18 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Hooks - With Undefined Steps\n Hooks also run before and after undefined steps\n\n Scenario: No tags and a undefined step\n When a step does not exist\n","uri":"samples/hooks-undefined/hooks-undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks - With Undefined Steps","description":" Hooks also run before and after undefined steps","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"No tags and a undefined step","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step does not exist"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks-undefined/hooks-undefined.feature"}} +{"pickle":{"id":"3","uri":"samples/hooks-undefined/hooks-undefined.feature","location":{"line":4,"column":3},"astNodeIds":["1"],"tags":[],"name":"No tags and a undefined step","language":"en","steps":[{"id":"2","text":"a step does not exist","type":"Action","astNodeIds":["0"]}]}} +{"hook":{"id":"4","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks-undefined/hooks-undefined.ts","location":{"line":3}}}} +{"hook":{"id":"5","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks-undefined/hooks-undefined.ts","location":{"line":7}}}} +{"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","hookId":"4"},{"id":"9","pickleStepId":"2","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"10","hookId":"5"}],"testRunStartedId":"6"}} +{"testCaseStarted":{"id":"11","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"11","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"11","testStepId":"8","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"11","testStepId":"9","timestamp":{"seconds":0,"nanos":4000000}}} +{"suggestion":{"id":"12","pickleStepId":"2","snippets":[{"language":"typescript","code":"When(\"a step does not exist\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"11","testStepId":"9","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"11","testStepId":"10","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"11","testStepId":"10","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"11","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"6","timestamp":{"seconds":0,"nanos":9000000},"success":false}} diff --git a/compatibility/hooks-undefined/hooks-undefined.ts b/compatibility/hooks-undefined/hooks-undefined.ts new file mode 100644 index 00000000..9a669601 --- /dev/null +++ b/compatibility/hooks-undefined/hooks-undefined.ts @@ -0,0 +1,9 @@ +import { When, Before, After } from '@cucumber/fake-cucumber' + +Before({}, function () { + // no-op +}) + +After({}, function () { + // no-op +}) diff --git a/compatibility/hooks/hooks.feature b/compatibility/hooks/hooks.feature new file mode 100644 index 00000000..5a29a802 --- /dev/null +++ b/compatibility/hooks/hooks.feature @@ -0,0 +1,8 @@ +Feature: Hooks + Hooks are special steps that run before or after each scenario's steps. + + Scenario: No tags and a passed step + When a step passes + + Scenario: No tags and a failed step + When a step fails diff --git a/compatibility/hooks/hooks.ndjson b/compatibility/hooks/hooks.ndjson new file mode 100644 index 00000000..ec2f6186 --- /dev/null +++ b/compatibility/hooks/hooks.ndjson @@ -0,0 +1,29 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Hooks\n Hooks are special steps that run before or after each scenario's steps.\n\n Scenario: No tags and a passed step\n When a step passes\n\n Scenario: No tags and a failed step\n When a step fails\n","uri":"samples/hooks/hooks.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Hooks","description":" Hooks are special steps that run before or after each scenario's steps.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":4,"column":3},"keyword":"Scenario","name":"No tags and a passed step","description":"","steps":[{"id":"0","location":{"line":5,"column":5},"keyword":"When ","keywordType":"Action","text":"a step passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"No tags and a failed step","description":"","steps":[{"id":"2","location":{"line":8,"column":5},"keyword":"When ","keywordType":"Action","text":"a step fails"}],"examples":[]}}]},"comments":[],"uri":"samples/hooks/hooks.feature"}} +{"pickle":{"id":"5","uri":"samples/hooks/hooks.feature","location":{"line":4,"column":3},"astNodeIds":["1"],"tags":[],"name":"No tags and a passed step","language":"en","steps":[{"id":"4","text":"a step passes","type":"Action","astNodeIds":["0"]}]}} +{"pickle":{"id":"7","uri":"samples/hooks/hooks.feature","location":{"line":7,"column":3},"astNodeIds":["3"],"tags":[],"name":"No tags and a failed step","language":"en","steps":[{"id":"6","text":"a step fails","type":"Action","astNodeIds":["2"]}]}} +{"stepDefinition":{"id":"9","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step passes"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":7}}}} +{"stepDefinition":{"id":"10","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step fails"},"sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":11}}}} +{"hook":{"id":"8","type":"BEFORE_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":3}}}} +{"hook":{"id":"11","type":"AFTER_TEST_CASE","sourceReference":{"uri":"samples/hooks/hooks.ts","location":{"line":15}}}} +{"testRunStarted":{"id":"12","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"13","pickleId":"5","testSteps":[{"id":"14","hookId":"8"},{"id":"15","pickleStepId":"4","stepDefinitionIds":["9"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"16","hookId":"11"}],"testRunStartedId":"12"}} +{"testCase":{"id":"17","pickleId":"7","testSteps":[{"id":"18","hookId":"8"},{"id":"19","pickleStepId":"6","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"20","hookId":"11"}],"testRunStartedId":"12"}} +{"testCaseStarted":{"id":"21","testCaseId":"13","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"21","testStepId":"14","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"21","testStepId":"14","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"21","testStepId":"15","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"21","testStepId":"15","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"21","testStepId":"16","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"21","testStepId":"16","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"21","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"22","testCaseId":"17","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"18","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"18","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"19","timestamp":{"seconds":0,"nanos":12000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"19","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/hooks/hooks.feature:8"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"20","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"20","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"22","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"12","timestamp":{"seconds":0,"nanos":17000000},"success":false}} diff --git a/compatibility/hooks/hooks.ts b/compatibility/hooks/hooks.ts new file mode 100644 index 00000000..30b6e27f --- /dev/null +++ b/compatibility/hooks/hooks.ts @@ -0,0 +1,17 @@ +import { When, Before, After } from '@cucumber/fake-cucumber' + +Before({}, function () { + // no-op +}) + +When('a step passes', function () { + // no-op +}) + +When('a step fails', function () { + throw new Error('Exception in step') +}) + +After({}, function () { + // no-op +}) diff --git a/compatibility/markdown/markdown.feature.md b/compatibility/markdown/markdown.feature.md new file mode 100644 index 00000000..e0cdec5b --- /dev/null +++ b/compatibility/markdown/markdown.feature.md @@ -0,0 +1,46 @@ +# Feature: Cheese + +This table is not picked up by Gherkin (not indented 2+ spaces) + +| foo | bar | +| --- | --- | +| boz | boo | + + +## Rule: Nom nom nom + +I love cheese, especially fromage macaroni cheese. Rubber cheese ricotta caerphilly blue castello who moved my cheese queso bavarian bergkase melted cheese. + +### Scenario Outline: Ylajali! + +* Given some TypeScript code: + ```typescript + type Cheese = 'reblochon' | 'roquefort' | 'rocamadour' + ``` +* And some classic Gherkin: + ```gherkin + Given there are 24 apples in Mary's basket + ``` +* When we use a data table and attach something and then + | name | age | + | ---- | --: | + | Bill | 3 | + | Jane | 6 | + | Isla | 5 | +* Then this might or might not run + +#### Examples: because we need more tables + +This table is indented 2 spaces, so Gherkin will pick it up + + | what | + | ---- | + | fail | + | pass | + +And oh by the way, this table is also ignored by Gherkin because it doesn't have 2+ space indent: + +| cheese | +| -------- | +| gouda | +| gamalost | diff --git a/compatibility/markdown/markdown.ndjson b/compatibility/markdown/markdown.ndjson new file mode 100644 index 00000000..2d485ed1 --- /dev/null +++ b/compatibility/markdown/markdown.ndjson @@ -0,0 +1,35 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"# Feature: Cheese\n\nThis table is not picked up by Gherkin (not indented 2+ spaces)\n\n| foo | bar |\n| --- | --- |\n| boz | boo |\n\n\n## Rule: Nom nom nom\n\nI love cheese, especially fromage macaroni cheese. Rubber cheese ricotta caerphilly blue castello who moved my cheese queso bavarian bergkase melted cheese.\n\n### Scenario Outline: Ylajali!\n\n* Given some TypeScript code:\n ```typescript\n type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'\n ```\n* And some classic Gherkin:\n ```gherkin\n Given there are 24 apples in Mary's basket\n ```\n* When we use a data table and attach something and then \n | name | age |\n | ---- | --: |\n | Bill | 3 |\n | Jane | 6 |\n | Isla | 5 |\n* Then this might or might not run\n\n#### Examples: because we need more tables\n\nThis table is indented 2 spaces, so Gherkin will pick it up\n\n | what |\n | ---- |\n | fail |\n | pass |\n\nAnd oh by the way, this table is also ignored by Gherkin because it doesn't have 2+ space indent:\n\n| cheese |\n| -------- |\n| gouda |\n| gamalost |\n","uri":"samples/markdown/markdown.feature.md","mediaType":"text/x.cucumber.gherkin+markdown"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":3},"language":"en","keyword":"Feature","name":"Cheese","description":"| boz | boo |","children":[{"rule":{"id":"13","location":{"line":10,"column":4},"keyword":"Rule","name":"Nom nom nom","description":"","children":[{"scenario":{"id":"12","tags":[],"location":{"line":14,"column":5},"keyword":"Scenario Outline","name":"Ylajali!","description":"","steps":[{"id":"0","location":{"line":16,"column":3},"keyword":"Given ","keywordType":"Context","text":"some TypeScript code:","docString":{"location":{"line":17,"column":3},"content":"type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'","delimiter":"```","mediaType":"typescript"}},{"id":"1","location":{"line":20,"column":3},"keyword":"And ","keywordType":"Conjunction","text":"some classic Gherkin:","docString":{"location":{"line":21,"column":3},"content":"Given there are 24 apples in Mary's basket","delimiter":"```","mediaType":"gherkin"}},{"id":"6","location":{"line":24,"column":3},"keyword":"When ","keywordType":"Action","text":"we use a data table and attach something and then ","dataTable":{"location":{"line":25,"column":3},"rows":[{"id":"2","location":{"line":25,"column":3},"cells":[{"location":{"line":25,"column":5},"value":"name"},{"location":{"line":25,"column":12},"value":"age"}]},{"id":"3","location":{"line":27,"column":3},"cells":[{"location":{"line":27,"column":5},"value":"Bill"},{"location":{"line":27,"column":14},"value":"3"}]},{"id":"4","location":{"line":28,"column":3},"cells":[{"location":{"line":28,"column":5},"value":"Jane"},{"location":{"line":28,"column":14},"value":"6"}]},{"id":"5","location":{"line":29,"column":3},"cells":[{"location":{"line":29,"column":5},"value":"Isla"},{"location":{"line":29,"column":14},"value":"5"}]}]}},{"id":"7","location":{"line":30,"column":3},"keyword":"Then ","keywordType":"Outcome","text":"this might or might not run"}],"examples":[{"id":"11","tags":[],"location":{"line":32,"column":6},"keyword":"Examples","name":"because we need more tables","description":"","tableHeader":{"id":"8","location":{"line":36,"column":3},"cells":[{"location":{"line":36,"column":5},"value":"what"}]},"tableBody":[{"id":"9","location":{"line":38,"column":3},"cells":[{"location":{"line":38,"column":5},"value":"fail"}]},{"id":"10","location":{"line":39,"column":3},"cells":[{"location":{"line":39,"column":5},"value":"pass"}]}]}]}}],"tags":[]}}]},"comments":[],"uri":"samples/markdown/markdown.feature.md"}} +{"pickle":{"id":"18","uri":"samples/markdown/markdown.feature.md","location":{"line":38,"column":3},"astNodeIds":["12","9"],"name":"Ylajali!","language":"en","steps":[{"id":"14","text":"some TypeScript code:","type":"Context","argument":{"docString":{"content":"type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'","mediaType":"typescript"}},"astNodeIds":["0","9"]},{"id":"15","text":"some classic Gherkin:","type":"Context","argument":{"docString":{"content":"Given there are 24 apples in Mary's basket","mediaType":"gherkin"}},"astNodeIds":["1","9"]},{"id":"16","text":"we use a data table and attach something and then fail","type":"Action","argument":{"dataTable":{"rows":[{"cells":[{"value":"name"},{"value":"age"}]},{"cells":[{"value":"Bill"},{"value":"3"}]},{"cells":[{"value":"Jane"},{"value":"6"}]},{"cells":[{"value":"Isla"},{"value":"5"}]}]}},"astNodeIds":["6","9"]},{"id":"17","text":"this might or might not run","type":"Outcome","astNodeIds":["7","9"]}],"tags":[]}} +{"pickle":{"id":"23","uri":"samples/markdown/markdown.feature.md","location":{"line":39,"column":3},"astNodeIds":["12","10"],"name":"Ylajali!","language":"en","steps":[{"id":"19","text":"some TypeScript code:","type":"Context","argument":{"docString":{"content":"type Cheese = 'reblochon' | 'roquefort' | 'rocamadour'","mediaType":"typescript"}},"astNodeIds":["0","10"]},{"id":"20","text":"some classic Gherkin:","type":"Context","argument":{"docString":{"content":"Given there are 24 apples in Mary's basket","mediaType":"gherkin"}},"astNodeIds":["1","10"]},{"id":"21","text":"we use a data table and attach something and then pass","type":"Action","argument":{"dataTable":{"rows":[{"cells":[{"value":"name"},{"value":"age"}]},{"cells":[{"value":"Bill"},{"value":"3"}]},{"cells":[{"value":"Jane"},{"value":"6"}]},{"cells":[{"value":"Isla"},{"value":"5"}]}]}},"astNodeIds":["6","10"]},{"id":"22","text":"this might or might not run","type":"Outcome","astNodeIds":["7","10"]}],"tags":[]}} +{"stepDefinition":{"id":"24","pattern":{"type":"CUCUMBER_EXPRESSION","source":"some TypeScript code:"},"sourceReference":{"uri":"samples/markdown/markdown.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"25","pattern":{"type":"CUCUMBER_EXPRESSION","source":"some classic Gherkin:"},"sourceReference":{"uri":"samples/markdown/markdown.ts","location":{"line":8}}}} +{"stepDefinition":{"id":"26","pattern":{"type":"CUCUMBER_EXPRESSION","source":"we use a data table and attach something and then {word}"},"sourceReference":{"uri":"samples/markdown/markdown.ts","location":{"line":12}}}} +{"stepDefinition":{"id":"27","pattern":{"type":"CUCUMBER_EXPRESSION","source":"this might or might not run"},"sourceReference":{"uri":"samples/markdown/markdown.ts","location":{"line":23}}}} +{"testRunStarted":{"id":"28","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"29","pickleId":"18","testSteps":[{"id":"30","pickleStepId":"14","stepDefinitionIds":["24"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"31","pickleStepId":"15","stepDefinitionIds":["25"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"32","pickleStepId":"16","stepDefinitionIds":["26"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":50,"value":"fail","children":[]},"parameterTypeName":"word"}]}]},{"id":"33","pickleStepId":"17","stepDefinitionIds":["27"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"28"}} +{"testCase":{"id":"34","pickleId":"23","testSteps":[{"id":"35","pickleStepId":"19","stepDefinitionIds":["24"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"36","pickleStepId":"20","stepDefinitionIds":["25"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"37","pickleStepId":"21","stepDefinitionIds":["26"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":50,"value":"pass","children":[]},"parameterTypeName":"word"}]}]},{"id":"38","pickleStepId":"22","stepDefinitionIds":["27"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"28"}} +{"testCaseStarted":{"id":"39","testCaseId":"29","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"30","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"30","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"31","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"31","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"32","timestamp":{"seconds":0,"nanos":6000000}}} +{"attachment":{"testCaseStartedId":"39","testStepId":"32","body":"We are logging some plain text (fail)","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain","timestamp":{"seconds":0,"nanos":7000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"32","testStepResult":{"message":"You asked me to fail","exception":{"type":"Error","message":"You asked me to fail","stackTrace":"Error: You asked me to fail\nsamples/markdown/markdown.feature.md:24"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"33","timestamp":{"seconds":0,"nanos":9000000}}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"33","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":10000000}}} +{"testCaseFinished":{"testCaseStartedId":"39","timestamp":{"seconds":0,"nanos":11000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"40","testCaseId":"34","timestamp":{"seconds":0,"nanos":12000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"40","testStepId":"35","timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepFinished":{"testCaseStartedId":"40","testStepId":"35","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepStarted":{"testCaseStartedId":"40","testStepId":"36","timestamp":{"seconds":0,"nanos":15000000}}} +{"testStepFinished":{"testCaseStartedId":"40","testStepId":"36","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":16000000}}} +{"testStepStarted":{"testCaseStartedId":"40","testStepId":"37","timestamp":{"seconds":0,"nanos":17000000}}} +{"attachment":{"testCaseStartedId":"40","testStepId":"37","body":"We are logging some plain text (pass)","contentEncoding":"IDENTITY","mediaType":"text/x.cucumber.log+plain","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"40","testStepId":"37","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testStepStarted":{"testCaseStartedId":"40","testStepId":"38","timestamp":{"seconds":0,"nanos":20000000}}} +{"testStepFinished":{"testCaseStartedId":"40","testStepId":"38","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}} +{"testCaseFinished":{"testCaseStartedId":"40","timestamp":{"seconds":0,"nanos":22000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"28","timestamp":{"seconds":0,"nanos":23000000},"success":false}} diff --git a/compatibility/markdown/markdown.ts b/compatibility/markdown/markdown.ts new file mode 100644 index 00000000..7af55d90 --- /dev/null +++ b/compatibility/markdown/markdown.ts @@ -0,0 +1,25 @@ +import assert from 'node:assert' +import { Given, When, Then } from '@cucumber/fake-cucumber' + +Given('some TypeScript code:', function (dataTable: string[][]) { + assert(dataTable) +}) + +Given('some classic Gherkin:', function (gherkin: string) { + assert(gherkin) +}) + +When( + 'we use a data table and attach something and then {word}', + async function (word: string, dataTable: string[][]) { + assert(dataTable) + await this.log(`We are logging some plain text (${word})`) + if (word === 'fail') { + throw new Error('You asked me to fail') + } + } +) + +Then('this might or might not run', function () { + // no-op +}) diff --git a/compatibility/minimal/minimal.cpp b/compatibility/minimal/minimal.cpp new file mode 100644 index 00000000..aa40c108 --- /dev/null +++ b/compatibility/minimal/minimal.cpp @@ -0,0 +1,6 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include +STEP(R"(I have {int} cukes in my belly)", (std::int32_t number)) +{ + // no-op +} diff --git a/compatibility/minimal/minimal.feature b/compatibility/minimal/minimal.feature new file mode 100644 index 00000000..158fde29 --- /dev/null +++ b/compatibility/minimal/minimal.feature @@ -0,0 +1,10 @@ +Feature: minimal + + Cucumber doesn't execute this markdown, but @cucumber/react renders it. + + * This is + * a bullet + * list + + Scenario: cukes + Given I have 42 cukes in my belly diff --git a/compatibility/minimal/minimal.ndjson b/compatibility/minimal/minimal.ndjson new file mode 100644 index 00000000..f58addeb --- /dev/null +++ b/compatibility/minimal/minimal.ndjson @@ -0,0 +1,12 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: minimal\n \n Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list\n \n Scenario: cukes\n Given I have 42 cukes in my belly\n","uri":"samples/minimal/minimal.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"minimal","description":" Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list","children":[{"scenario":{"id":"1","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"cukes","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"I have 42 cukes in my belly"}],"examples":[]}}]},"comments":[],"uri":"samples/minimal/minimal.feature"}} +{"pickle":{"id":"3","uri":"samples/minimal/minimal.feature","location":{"line":9,"column":3},"astNodeIds":["1"],"tags":[],"name":"cukes","language":"en","steps":[{"id":"2","text":"I have 42 cukes in my belly","type":"Context","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I have {int} cukes in my belly"},"sourceReference":{"uri":"samples/minimal/minimal.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":7,"value":"42","children":[]},"parameterTypeName":"int"}]}]}],"testRunStartedId":"5"}} +{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":true}} diff --git a/compatibility/minimal/minimal.ts b/compatibility/minimal/minimal.ts new file mode 100644 index 00000000..969e74a0 --- /dev/null +++ b/compatibility/minimal/minimal.ts @@ -0,0 +1,5 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('I have {int} cukes in my belly', function (cukeCount: number) { + // no-op +}) diff --git a/compatibility/minimal/out.ndjson b/compatibility/minimal/out.ndjson new file mode 100644 index 00000000..0cc1575f --- /dev/null +++ b/compatibility/minimal/out.ndjson @@ -0,0 +1,11 @@ +{"source":{"data":"Feature: minimal\n \n Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list\n \n Scenario: cukes\n Given I have 42 cukes in my belly\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/minimal/minimal.feature"}} +{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"1","keyword":"Scenario","location":{"column":3,"line":9},"name":"cukes","steps":[{"id":"0","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":10},"text":"I have 42 cukes in my belly"}],"tags":[]}}],"description":" Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"minimal","tags":[]},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/minimal/minimal.feature"}} +{"pickle":{"astNodeIds":["1"],"id":"3","language":"en","location":{"column":3,"line":9},"name":"cukes","steps":[{"astNodeIds":["0"],"id":"2","text":"I have 42 cukes in my belly","type":"Context"}],"tags":[],"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/minimal/minimal.feature"}} +{"stepDefinition":{"id":"4","pattern":{"source":"I have {int} cukes in my belly","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":3},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/minimal/minimal.cpp"}}} +{"testRunStarted":{"id":"5","timestamp":{"nanos":0,"seconds":0}}} +{"testCase":{"id":"6","pickleId":"3","testRunStartedId":"5","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"value":"42"},"parameterTypeName":"int"}]}]}]}} +{"testCaseStarted":{"attempt":0,"id":"8","testCaseId":"6","timestamp":{"nanos":1000000,"seconds":0}}} +{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"nanos":2000000,"seconds":0}}} +{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} +{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} +{"testRunFinished":{"success":true,"testRunStartedId":"5","timestamp":{"nanos":5000000,"seconds":0}}} diff --git a/compatibility/multiple-features-reversed/multiple-features-reversed-1.feature b/compatibility/multiple-features-reversed/multiple-features-reversed-1.feature new file mode 100644 index 00000000..faf03eee --- /dev/null +++ b/compatibility/multiple-features-reversed/multiple-features-reversed-1.feature @@ -0,0 +1,10 @@ +Feature: First feature + + Scenario: First scenario + Given an order for "eggs" + + Scenario: Second scenario + Given an order for "milk" + + Scenario: Third scenario + Given an order for "bread" \ No newline at end of file diff --git a/compatibility/multiple-features-reversed/multiple-features-reversed-2.feature b/compatibility/multiple-features-reversed/multiple-features-reversed-2.feature new file mode 100644 index 00000000..0b0f7569 --- /dev/null +++ b/compatibility/multiple-features-reversed/multiple-features-reversed-2.feature @@ -0,0 +1,10 @@ +Feature: Second feature + + Scenario: First scenario + Given an order for "batteries" + + Scenario: Second scenario + Given an order for "light bulbs" + + Scenario: Third scenario + Given an order for "fuses" \ No newline at end of file diff --git a/compatibility/multiple-features-reversed/multiple-features-reversed-3.feature b/compatibility/multiple-features-reversed/multiple-features-reversed-3.feature new file mode 100644 index 00000000..4c306c6c --- /dev/null +++ b/compatibility/multiple-features-reversed/multiple-features-reversed-3.feature @@ -0,0 +1,10 @@ +Feature: Third feature + + Scenario: First scenario + Given an order for "pencils" + + Scenario: Second scenario + Given an order for "rulers" + + Scenario: Third scenario + Given an order for "paperclips" \ No newline at end of file diff --git a/compatibility/multiple-features-reversed/multiple-features-reversed.arguments.txt b/compatibility/multiple-features-reversed/multiple-features-reversed.arguments.txt new file mode 100644 index 00000000..97072c0c --- /dev/null +++ b/compatibility/multiple-features-reversed/multiple-features-reversed.arguments.txt @@ -0,0 +1 @@ +--order reverse diff --git a/compatibility/multiple-features-reversed/multiple-features-reversed.ndjson b/compatibility/multiple-features-reversed/multiple-features-reversed.ndjson new file mode 100644 index 00000000..7516fb80 --- /dev/null +++ b/compatibility/multiple-features-reversed/multiple-features-reversed.ndjson @@ -0,0 +1,64 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: First feature\n\n Scenario: First scenario\n Given an order for \"eggs\"\n\n Scenario: Second scenario\n Given an order for \"milk\"\n\n Scenario: Third scenario\n Given an order for \"bread\"","uri":"samples/multiple-features-reversed/multiple-features-reversed-1.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"First feature","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"First scenario","description":"","steps":[{"id":"0","location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"eggs\""}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Second scenario","description":"","steps":[{"id":"2","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"milk\""}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Third scenario","description":"","steps":[{"id":"4","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"bread\""}],"examples":[]}}]},"comments":[],"uri":"samples/multiple-features-reversed/multiple-features-reversed-1.feature"}} +{"pickle":{"id":"7","uri":"samples/multiple-features-reversed/multiple-features-reversed-1.feature","location":{"line":3,"column":3},"astNodeIds":["1"],"tags":[],"name":"First scenario","language":"en","steps":[{"id":"6","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]}]}} +{"pickle":{"id":"9","uri":"samples/multiple-features-reversed/multiple-features-reversed-1.feature","location":{"line":6,"column":3},"astNodeIds":["3"],"tags":[],"name":"Second scenario","language":"en","steps":[{"id":"8","text":"an order for \"milk\"","type":"Context","astNodeIds":["2"]}]}} +{"pickle":{"id":"11","uri":"samples/multiple-features-reversed/multiple-features-reversed-1.feature","location":{"line":9,"column":3},"astNodeIds":["5"],"tags":[],"name":"Third scenario","language":"en","steps":[{"id":"10","text":"an order for \"bread\"","type":"Context","astNodeIds":["4"]}]}} +{"source":{"data":"Feature: Second feature\n\n Scenario: First scenario\n Given an order for \"batteries\"\n\n Scenario: Second scenario\n Given an order for \"light bulbs\"\n\n Scenario: Third scenario\n Given an order for \"fuses\"","uri":"samples/multiple-features-reversed/multiple-features-reversed-2.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Second feature","description":"","children":[{"scenario":{"id":"13","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"First scenario","description":"","steps":[{"id":"12","location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"batteries\""}],"examples":[]}},{"scenario":{"id":"15","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Second scenario","description":"","steps":[{"id":"14","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"light bulbs\""}],"examples":[]}},{"scenario":{"id":"17","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Third scenario","description":"","steps":[{"id":"16","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"fuses\""}],"examples":[]}}]},"comments":[],"uri":"samples/multiple-features-reversed/multiple-features-reversed-2.feature"}} +{"pickle":{"id":"19","uri":"samples/multiple-features-reversed/multiple-features-reversed-2.feature","location":{"line":3,"column":3},"astNodeIds":["13"],"tags":[],"name":"First scenario","language":"en","steps":[{"id":"18","text":"an order for \"batteries\"","type":"Context","astNodeIds":["12"]}]}} +{"pickle":{"id":"21","uri":"samples/multiple-features-reversed/multiple-features-reversed-2.feature","location":{"line":6,"column":3},"astNodeIds":["15"],"tags":[],"name":"Second scenario","language":"en","steps":[{"id":"20","text":"an order for \"light bulbs\"","type":"Context","astNodeIds":["14"]}]}} +{"pickle":{"id":"23","uri":"samples/multiple-features-reversed/multiple-features-reversed-2.feature","location":{"line":9,"column":3},"astNodeIds":["17"],"tags":[],"name":"Third scenario","language":"en","steps":[{"id":"22","text":"an order for \"fuses\"","type":"Context","astNodeIds":["16"]}]}} +{"source":{"data":"Feature: Third feature\n\n Scenario: First scenario\n Given an order for \"pencils\"\n\n Scenario: Second scenario\n Given an order for \"rulers\"\n\n Scenario: Third scenario\n Given an order for \"paperclips\"","uri":"samples/multiple-features-reversed/multiple-features-reversed-3.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Third feature","description":"","children":[{"scenario":{"id":"25","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"First scenario","description":"","steps":[{"id":"24","location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"pencils\""}],"examples":[]}},{"scenario":{"id":"27","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Second scenario","description":"","steps":[{"id":"26","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"rulers\""}],"examples":[]}},{"scenario":{"id":"29","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Third scenario","description":"","steps":[{"id":"28","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"paperclips\""}],"examples":[]}}]},"comments":[],"uri":"samples/multiple-features-reversed/multiple-features-reversed-3.feature"}} +{"pickle":{"id":"31","uri":"samples/multiple-features-reversed/multiple-features-reversed-3.feature","location":{"line":3,"column":3},"astNodeIds":["25"],"tags":[],"name":"First scenario","language":"en","steps":[{"id":"30","text":"an order for \"pencils\"","type":"Context","astNodeIds":["24"]}]}} +{"pickle":{"id":"33","uri":"samples/multiple-features-reversed/multiple-features-reversed-3.feature","location":{"line":6,"column":3},"astNodeIds":["27"],"tags":[],"name":"Second scenario","language":"en","steps":[{"id":"32","text":"an order for \"rulers\"","type":"Context","astNodeIds":["26"]}]}} +{"pickle":{"id":"35","uri":"samples/multiple-features-reversed/multiple-features-reversed-3.feature","location":{"line":9,"column":3},"astNodeIds":["29"],"tags":[],"name":"Third scenario","language":"en","steps":[{"id":"34","text":"an order for \"paperclips\"","type":"Context","astNodeIds":["28"]}]}} +{"stepDefinition":{"id":"36","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an order for {string}"},"sourceReference":{"uri":"samples/multiple-features-reversed/multiple-features-reversed.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"37","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"38","pickleId":"35","testSteps":[{"id":"39","pickleStepId":"34","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"paperclips\"","children":[{"start":14,"value":"paperclips","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"40","pickleId":"33","testSteps":[{"id":"41","pickleStepId":"32","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"rulers\"","children":[{"start":14,"value":"rulers","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"42","pickleId":"31","testSteps":[{"id":"43","pickleStepId":"30","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"pencils\"","children":[{"start":14,"value":"pencils","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"44","pickleId":"23","testSteps":[{"id":"45","pickleStepId":"22","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"fuses\"","children":[{"start":14,"value":"fuses","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"46","pickleId":"21","testSteps":[{"id":"47","pickleStepId":"20","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"light bulbs\"","children":[{"start":14,"value":"light bulbs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"48","pickleId":"19","testSteps":[{"id":"49","pickleStepId":"18","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"batteries\"","children":[{"start":14,"value":"batteries","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"50","pickleId":"11","testSteps":[{"id":"51","pickleStepId":"10","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"bread\"","children":[{"start":14,"value":"bread","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"52","pickleId":"9","testSteps":[{"id":"53","pickleStepId":"8","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"milk\"","children":[{"start":14,"value":"milk","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"54","pickleId":"7","testSteps":[{"id":"55","pickleStepId":"6","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"eggs\"","children":[{"start":14,"value":"eggs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCaseStarted":{"id":"56","testCaseId":"38","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"39","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"39","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"57","testCaseId":"40","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"41","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"57","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"58","testCaseId":"42","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"58","testStepId":"43","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"58","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"58","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"59","testCaseId":"44","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"59","testStepId":"45","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"59","testStepId":"45","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"59","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"60","testCaseId":"46","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"60","testStepId":"47","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"60","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"60","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"61","testCaseId":"48","timestamp":{"seconds":0,"nanos":21000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"61","testStepId":"49","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"61","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testCaseFinished":{"testCaseStartedId":"61","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"62","testCaseId":"50","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"62","testStepId":"51","timestamp":{"seconds":0,"nanos":26000000}}} +{"testStepFinished":{"testCaseStartedId":"62","testStepId":"51","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} +{"testCaseFinished":{"testCaseStartedId":"62","timestamp":{"seconds":0,"nanos":28000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"63","testCaseId":"52","timestamp":{"seconds":0,"nanos":29000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"63","testStepId":"53","timestamp":{"seconds":0,"nanos":30000000}}} +{"testStepFinished":{"testCaseStartedId":"63","testStepId":"53","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}} +{"testCaseFinished":{"testCaseStartedId":"63","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"64","testCaseId":"54","timestamp":{"seconds":0,"nanos":33000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"64","testStepId":"55","timestamp":{"seconds":0,"nanos":34000000}}} +{"testStepFinished":{"testCaseStartedId":"64","testStepId":"55","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}} +{"testCaseFinished":{"testCaseStartedId":"64","timestamp":{"seconds":0,"nanos":36000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"37","timestamp":{"seconds":0,"nanos":37000000},"success":true}} diff --git a/compatibility/multiple-features-reversed/multiple-features-reversed.ts b/compatibility/multiple-features-reversed/multiple-features-reversed.ts new file mode 100644 index 00000000..e41dae39 --- /dev/null +++ b/compatibility/multiple-features-reversed/multiple-features-reversed.ts @@ -0,0 +1,5 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('an order for {string}', () => { + // no-op +}) diff --git a/compatibility/multiple-features/multiple-features-1.feature b/compatibility/multiple-features/multiple-features-1.feature new file mode 100644 index 00000000..faf03eee --- /dev/null +++ b/compatibility/multiple-features/multiple-features-1.feature @@ -0,0 +1,10 @@ +Feature: First feature + + Scenario: First scenario + Given an order for "eggs" + + Scenario: Second scenario + Given an order for "milk" + + Scenario: Third scenario + Given an order for "bread" \ No newline at end of file diff --git a/compatibility/multiple-features/multiple-features-2.feature b/compatibility/multiple-features/multiple-features-2.feature new file mode 100644 index 00000000..0b0f7569 --- /dev/null +++ b/compatibility/multiple-features/multiple-features-2.feature @@ -0,0 +1,10 @@ +Feature: Second feature + + Scenario: First scenario + Given an order for "batteries" + + Scenario: Second scenario + Given an order for "light bulbs" + + Scenario: Third scenario + Given an order for "fuses" \ No newline at end of file diff --git a/compatibility/multiple-features/multiple-features-3.feature b/compatibility/multiple-features/multiple-features-3.feature new file mode 100644 index 00000000..4c306c6c --- /dev/null +++ b/compatibility/multiple-features/multiple-features-3.feature @@ -0,0 +1,10 @@ +Feature: Third feature + + Scenario: First scenario + Given an order for "pencils" + + Scenario: Second scenario + Given an order for "rulers" + + Scenario: Third scenario + Given an order for "paperclips" \ No newline at end of file diff --git a/compatibility/multiple-features/multiple-features.ndjson b/compatibility/multiple-features/multiple-features.ndjson new file mode 100644 index 00000000..e3fbf473 --- /dev/null +++ b/compatibility/multiple-features/multiple-features.ndjson @@ -0,0 +1,64 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: First feature\n\n Scenario: First scenario\n Given an order for \"eggs\"\n\n Scenario: Second scenario\n Given an order for \"milk\"\n\n Scenario: Third scenario\n Given an order for \"bread\"","uri":"samples/multiple-features/multiple-features-1.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"First feature","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"First scenario","description":"","steps":[{"id":"0","location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"eggs\""}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Second scenario","description":"","steps":[{"id":"2","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"milk\""}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Third scenario","description":"","steps":[{"id":"4","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"bread\""}],"examples":[]}}]},"comments":[],"uri":"samples/multiple-features/multiple-features-1.feature"}} +{"pickle":{"id":"7","uri":"samples/multiple-features/multiple-features-1.feature","location":{"line":3,"column":3},"astNodeIds":["1"],"tags":[],"name":"First scenario","language":"en","steps":[{"id":"6","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]}]}} +{"pickle":{"id":"9","uri":"samples/multiple-features/multiple-features-1.feature","location":{"line":6,"column":3},"astNodeIds":["3"],"tags":[],"name":"Second scenario","language":"en","steps":[{"id":"8","text":"an order for \"milk\"","type":"Context","astNodeIds":["2"]}]}} +{"pickle":{"id":"11","uri":"samples/multiple-features/multiple-features-1.feature","location":{"line":9,"column":3},"astNodeIds":["5"],"tags":[],"name":"Third scenario","language":"en","steps":[{"id":"10","text":"an order for \"bread\"","type":"Context","astNodeIds":["4"]}]}} +{"source":{"data":"Feature: Second feature\n\n Scenario: First scenario\n Given an order for \"batteries\"\n\n Scenario: Second scenario\n Given an order for \"light bulbs\"\n\n Scenario: Third scenario\n Given an order for \"fuses\"","uri":"samples/multiple-features/multiple-features-2.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Second feature","description":"","children":[{"scenario":{"id":"13","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"First scenario","description":"","steps":[{"id":"12","location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"batteries\""}],"examples":[]}},{"scenario":{"id":"15","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Second scenario","description":"","steps":[{"id":"14","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"light bulbs\""}],"examples":[]}},{"scenario":{"id":"17","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Third scenario","description":"","steps":[{"id":"16","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"fuses\""}],"examples":[]}}]},"comments":[],"uri":"samples/multiple-features/multiple-features-2.feature"}} +{"pickle":{"id":"19","uri":"samples/multiple-features/multiple-features-2.feature","location":{"line":3,"column":3},"astNodeIds":["13"],"tags":[],"name":"First scenario","language":"en","steps":[{"id":"18","text":"an order for \"batteries\"","type":"Context","astNodeIds":["12"]}]}} +{"pickle":{"id":"21","uri":"samples/multiple-features/multiple-features-2.feature","location":{"line":6,"column":3},"astNodeIds":["15"],"tags":[],"name":"Second scenario","language":"en","steps":[{"id":"20","text":"an order for \"light bulbs\"","type":"Context","astNodeIds":["14"]}]}} +{"pickle":{"id":"23","uri":"samples/multiple-features/multiple-features-2.feature","location":{"line":9,"column":3},"astNodeIds":["17"],"tags":[],"name":"Third scenario","language":"en","steps":[{"id":"22","text":"an order for \"fuses\"","type":"Context","astNodeIds":["16"]}]}} +{"source":{"data":"Feature: Third feature\n\n Scenario: First scenario\n Given an order for \"pencils\"\n\n Scenario: Second scenario\n Given an order for \"rulers\"\n\n Scenario: Third scenario\n Given an order for \"paperclips\"","uri":"samples/multiple-features/multiple-features-3.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Third feature","description":"","children":[{"scenario":{"id":"25","tags":[],"location":{"line":3,"column":3},"keyword":"Scenario","name":"First scenario","description":"","steps":[{"id":"24","location":{"line":4,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"pencils\""}],"examples":[]}},{"scenario":{"id":"27","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"Second scenario","description":"","steps":[{"id":"26","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"rulers\""}],"examples":[]}},{"scenario":{"id":"29","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Third scenario","description":"","steps":[{"id":"28","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"paperclips\""}],"examples":[]}}]},"comments":[],"uri":"samples/multiple-features/multiple-features-3.feature"}} +{"pickle":{"id":"31","uri":"samples/multiple-features/multiple-features-3.feature","location":{"line":3,"column":3},"astNodeIds":["25"],"tags":[],"name":"First scenario","language":"en","steps":[{"id":"30","text":"an order for \"pencils\"","type":"Context","astNodeIds":["24"]}]}} +{"pickle":{"id":"33","uri":"samples/multiple-features/multiple-features-3.feature","location":{"line":6,"column":3},"astNodeIds":["27"],"tags":[],"name":"Second scenario","language":"en","steps":[{"id":"32","text":"an order for \"rulers\"","type":"Context","astNodeIds":["26"]}]}} +{"pickle":{"id":"35","uri":"samples/multiple-features/multiple-features-3.feature","location":{"line":9,"column":3},"astNodeIds":["29"],"tags":[],"name":"Third scenario","language":"en","steps":[{"id":"34","text":"an order for \"paperclips\"","type":"Context","astNodeIds":["28"]}]}} +{"stepDefinition":{"id":"36","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an order for {string}"},"sourceReference":{"uri":"samples/multiple-features/multiple-features.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"37","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"38","pickleId":"7","testSteps":[{"id":"39","pickleStepId":"6","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"eggs\"","children":[{"start":14,"value":"eggs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"40","pickleId":"9","testSteps":[{"id":"41","pickleStepId":"8","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"milk\"","children":[{"start":14,"value":"milk","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"42","pickleId":"11","testSteps":[{"id":"43","pickleStepId":"10","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"bread\"","children":[{"start":14,"value":"bread","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"44","pickleId":"19","testSteps":[{"id":"45","pickleStepId":"18","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"batteries\"","children":[{"start":14,"value":"batteries","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"46","pickleId":"21","testSteps":[{"id":"47","pickleStepId":"20","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"light bulbs\"","children":[{"start":14,"value":"light bulbs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"48","pickleId":"23","testSteps":[{"id":"49","pickleStepId":"22","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"fuses\"","children":[{"start":14,"value":"fuses","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"50","pickleId":"31","testSteps":[{"id":"51","pickleStepId":"30","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"pencils\"","children":[{"start":14,"value":"pencils","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"52","pickleId":"33","testSteps":[{"id":"53","pickleStepId":"32","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"rulers\"","children":[{"start":14,"value":"rulers","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCase":{"id":"54","pickleId":"35","testSteps":[{"id":"55","pickleStepId":"34","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"paperclips\"","children":[{"start":14,"value":"paperclips","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]}],"testRunStartedId":"37"}} +{"testCaseStarted":{"id":"56","testCaseId":"38","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"39","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"39","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"57","testCaseId":"40","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"41","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"57","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"58","testCaseId":"42","timestamp":{"seconds":0,"nanos":9000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"58","testStepId":"43","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"58","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"58","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"59","testCaseId":"44","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"59","testStepId":"45","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"59","testStepId":"45","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"59","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"60","testCaseId":"46","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"60","testStepId":"47","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"60","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"60","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"61","testCaseId":"48","timestamp":{"seconds":0,"nanos":21000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"61","testStepId":"49","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"61","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testCaseFinished":{"testCaseStartedId":"61","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"62","testCaseId":"50","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"62","testStepId":"51","timestamp":{"seconds":0,"nanos":26000000}}} +{"testStepFinished":{"testCaseStartedId":"62","testStepId":"51","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} +{"testCaseFinished":{"testCaseStartedId":"62","timestamp":{"seconds":0,"nanos":28000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"63","testCaseId":"52","timestamp":{"seconds":0,"nanos":29000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"63","testStepId":"53","timestamp":{"seconds":0,"nanos":30000000}}} +{"testStepFinished":{"testCaseStartedId":"63","testStepId":"53","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}} +{"testCaseFinished":{"testCaseStartedId":"63","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"64","testCaseId":"54","timestamp":{"seconds":0,"nanos":33000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"64","testStepId":"55","timestamp":{"seconds":0,"nanos":34000000}}} +{"testStepFinished":{"testCaseStartedId":"64","testStepId":"55","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}} +{"testCaseFinished":{"testCaseStartedId":"64","timestamp":{"seconds":0,"nanos":36000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"37","timestamp":{"seconds":0,"nanos":37000000},"success":true}} diff --git a/compatibility/multiple-features/multiple-features.ts b/compatibility/multiple-features/multiple-features.ts new file mode 100644 index 00000000..e41dae39 --- /dev/null +++ b/compatibility/multiple-features/multiple-features.ts @@ -0,0 +1,5 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('an order for {string}', () => { + // no-op +}) diff --git a/compatibility/parameter-types/parameter-types.feature b/compatibility/parameter-types/parameter-types.feature new file mode 100644 index 00000000..67e09946 --- /dev/null +++ b/compatibility/parameter-types/parameter-types.feature @@ -0,0 +1,11 @@ +Feature: Parameter Types + Cucumber lets you define your own parameter types, which can be used + in Cucumber Expressions. + + This lets you define a precise domain-specific vocabulary which can be used to + generate a glossary with examples taken from your scenarios. + + Parameter types also enable you to transform strings and tables into different types. + + Scenario: Flight transformer + Given LHR-CDG has been delayed diff --git a/compatibility/parameter-types/parameter-types.ndjson b/compatibility/parameter-types/parameter-types.ndjson new file mode 100644 index 00000000..0923b944 --- /dev/null +++ b/compatibility/parameter-types/parameter-types.ndjson @@ -0,0 +1,13 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Parameter Types\n Cucumber lets you define your own parameter types, which can be used\n in Cucumber Expressions.\n\n This lets you define a precise domain-specific vocabulary which can be used to\n generate a glossary with examples taken from your scenarios.\n\n Parameter types also enable you to transform strings and tables into different types.\n\n Scenario: Flight transformer\n Given LHR-CDG has been delayed\n","uri":"samples/parameter-types/parameter-types.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Parameter Types","description":" Cucumber lets you define your own parameter types, which can be used\n in Cucumber Expressions.\n\n This lets you define a precise domain-specific vocabulary which can be used to\n generate a glossary with examples taken from your scenarios.\n\n Parameter types also enable you to transform strings and tables into different types.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":10,"column":3},"keyword":"Scenario","name":"Flight transformer","description":"","steps":[{"id":"0","location":{"line":11,"column":5},"keyword":"Given ","keywordType":"Context","text":"LHR-CDG has been delayed"}],"examples":[]}}]},"comments":[],"uri":"samples/parameter-types/parameter-types.feature"}} +{"pickle":{"id":"3","uri":"samples/parameter-types/parameter-types.feature","location":{"line":10,"column":3},"astNodeIds":["1"],"tags":[],"name":"Flight transformer","language":"en","steps":[{"id":"2","text":"LHR-CDG has been delayed","type":"Context","astNodeIds":["0"]}]}} +{"parameterType":{"id":"4","name":"flight","regularExpressions":["([A-Z]{3})-([A-Z]{3})"],"preferForRegularExpressionMatch":false,"useForSnippets":true,"sourceReference":{"uri":"samples/parameter-types/parameter-types.ts","location":{"line":8}}}} +{"stepDefinition":{"id":"5","pattern":{"type":"CUCUMBER_EXPRESSION","source":"{flight} has been delayed"},"sourceReference":{"uri":"samples/parameter-types/parameter-types.ts","location":{"line":16}}}} +{"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":0,"value":"LHR-CDG","children":[{"start":0,"value":"LHR","children":[]},{"start":4,"value":"CDG","children":[]}]},"parameterTypeName":"flight"}]}]}],"testRunStartedId":"6"}} +{"testCaseStarted":{"id":"9","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"9","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"9","testStepId":"8","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"9","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"6","timestamp":{"seconds":0,"nanos":5000000},"success":true}} diff --git a/compatibility/parameter-types/parameter-types.ts b/compatibility/parameter-types/parameter-types.ts new file mode 100644 index 00000000..ca8833a5 --- /dev/null +++ b/compatibility/parameter-types/parameter-types.ts @@ -0,0 +1,19 @@ +import assert from 'node:assert' +import { Given, ParameterType } from '@cucumber/fake-cucumber' + +class Flight { + constructor(public readonly from: string, public readonly to: string) {} +} + +ParameterType({ + name: 'flight', + regexp: /([A-Z]{3})-([A-Z]{3})/, + transformer(from: string, to: string) { + return new Flight(from, to) + }, +}) + +Given('{flight} has been delayed', function (flight: Flight) { + assert.strictEqual(flight.from, 'LHR') + assert.strictEqual(flight.to, 'CDG') +}) diff --git a/compatibility/pending/pending.feature b/compatibility/pending/pending.feature new file mode 100644 index 00000000..767ece53 --- /dev/null +++ b/compatibility/pending/pending.feature @@ -0,0 +1,18 @@ +Feature: Pending steps + During development, step definitions can signal at runtime that they are + not yet implemented (or "pending") by returning or throwing a particular + value. + + This causes subsequent steps in the scenario to be skipped, and the overall + result to be treated as a failure. + + Scenario: Unimplemented step signals pending status + Given an unimplemented pending step + + Scenario: Steps before unimplemented steps are executed + Given an implemented non-pending step + And an unimplemented pending step + + Scenario: Steps after unimplemented steps are skipped + Given an unimplemented pending step + And an implemented step that is skipped diff --git a/compatibility/pending/pending.ndjson b/compatibility/pending/pending.ndjson new file mode 100644 index 00000000..a7891bcf --- /dev/null +++ b/compatibility/pending/pending.ndjson @@ -0,0 +1,30 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Pending steps\n During development, step definitions can signal at runtime that they are\n not yet implemented (or \"pending\") by returning or throwing a particular\n value.\n\n This causes subsequent steps in the scenario to be skipped, and the overall\n result to be treated as a failure.\n\n Scenario: Unimplemented step signals pending status\n Given an unimplemented pending step\n\n Scenario: Steps before unimplemented steps are executed\n Given an implemented non-pending step\n And an unimplemented pending step\n\n Scenario: Steps after unimplemented steps are skipped\n Given an unimplemented pending step\n And an implemented step that is skipped\n","uri":"samples/pending/pending.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Pending steps","description":" During development, step definitions can signal at runtime that they are\n not yet implemented (or \"pending\") by returning or throwing a particular\n value.\n\n This causes subsequent steps in the scenario to be skipped, and the overall\n result to be treated as a failure.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Unimplemented step signals pending status","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"an unimplemented pending step"}],"examples":[]}},{"scenario":{"id":"4","tags":[],"location":{"line":12,"column":3},"keyword":"Scenario","name":"Steps before unimplemented steps are executed","description":"","steps":[{"id":"2","location":{"line":13,"column":5},"keyword":"Given ","keywordType":"Context","text":"an implemented non-pending step"},{"id":"3","location":{"line":14,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an unimplemented pending step"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":16,"column":3},"keyword":"Scenario","name":"Steps after unimplemented steps are skipped","description":"","steps":[{"id":"5","location":{"line":17,"column":5},"keyword":"Given ","keywordType":"Context","text":"an unimplemented pending step"},{"id":"6","location":{"line":18,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an implemented step that is skipped"}],"examples":[]}}]},"comments":[],"uri":"samples/pending/pending.feature"}} +{"pickle":{"id":"9","uri":"samples/pending/pending.feature","location":{"line":9,"column":3},"astNodeIds":["1"],"tags":[],"name":"Unimplemented step signals pending status","language":"en","steps":[{"id":"8","text":"an unimplemented pending step","type":"Context","astNodeIds":["0"]}]}} +{"pickle":{"id":"12","uri":"samples/pending/pending.feature","location":{"line":12,"column":3},"astNodeIds":["4"],"tags":[],"name":"Steps before unimplemented steps are executed","language":"en","steps":[{"id":"10","text":"an implemented non-pending step","type":"Context","astNodeIds":["2"]},{"id":"11","text":"an unimplemented pending step","type":"Context","astNodeIds":["3"]}]}} +{"pickle":{"id":"15","uri":"samples/pending/pending.feature","location":{"line":16,"column":3},"astNodeIds":["7"],"tags":[],"name":"Steps after unimplemented steps are skipped","language":"en","steps":[{"id":"13","text":"an unimplemented pending step","type":"Context","astNodeIds":["5"]},{"id":"14","text":"an implemented step that is skipped","type":"Context","astNodeIds":["6"]}]}} +{"stepDefinition":{"id":"16","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an implemented non-pending step"},"sourceReference":{"uri":"samples/pending/pending.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"17","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an implemented step that is skipped"},"sourceReference":{"uri":"samples/pending/pending.ts","location":{"line":7}}}} +{"stepDefinition":{"id":"18","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an unimplemented pending step"},"sourceReference":{"uri":"samples/pending/pending.ts","location":{"line":11}}}} +{"testRunStarted":{"id":"19","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"20","pickleId":"9","testSteps":[{"id":"21","pickleStepId":"8","stepDefinitionIds":["18"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"19"}} +{"testCase":{"id":"22","pickleId":"12","testSteps":[{"id":"23","pickleStepId":"10","stepDefinitionIds":["16"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"24","pickleStepId":"11","stepDefinitionIds":["18"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"19"}} +{"testCase":{"id":"25","pickleId":"15","testSteps":[{"id":"26","pickleStepId":"13","stepDefinitionIds":["18"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"27","pickleStepId":"14","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"19"}} +{"testCaseStarted":{"id":"28","testCaseId":"20","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"28","testStepId":"21","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"28","testStepId":"21","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"28","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"29","testCaseId":"22","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"29","testStepId":"23","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"29","testStepId":"23","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testStepStarted":{"testCaseStartedId":"29","testStepId":"24","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"29","testStepId":"24","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testCaseFinished":{"testCaseStartedId":"29","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"30","testCaseId":"25","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"30","testStepId":"26","timestamp":{"seconds":0,"nanos":12000000}}} +{"testStepFinished":{"testCaseStartedId":"30","testStepId":"26","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"30","testStepId":"27","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"30","testStepId":"27","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"30","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"19","timestamp":{"seconds":0,"nanos":17000000},"success":false}} diff --git a/compatibility/pending/pending.ts b/compatibility/pending/pending.ts new file mode 100644 index 00000000..431d643a --- /dev/null +++ b/compatibility/pending/pending.ts @@ -0,0 +1,13 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('an implemented non-pending step', function () { + // no-op +}) + +Given('an implemented step that is skipped', function () { + // no-op +}) + +Given('an unimplemented pending step', function () { + return 'pending' +}) diff --git a/compatibility/regular-expression/regular-expression.feature b/compatibility/regular-expression/regular-expression.feature new file mode 100644 index 00000000..fcf7ae59 --- /dev/null +++ b/compatibility/regular-expression/regular-expression.feature @@ -0,0 +1,9 @@ +Feature: regular expression + + Cucumber supports both Cucumber and regular expressions in step + definitions. This shows a sample that uses a regular expression. + + Scenario: regular expression + Given a cucumber + Given a cucumber and a zucchini + Given a cucumber and a zucchini and a gourd diff --git a/compatibility/regular-expression/regular-expression.ndjson b/compatibility/regular-expression/regular-expression.ndjson new file mode 100644 index 00000000..e5ceaf8f --- /dev/null +++ b/compatibility/regular-expression/regular-expression.ndjson @@ -0,0 +1,16 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: regular expression\n \n Cucumber supports both Cucumber and regular expressions in step\n definitions. This shows a sample that uses a regular expression.\n \n Scenario: regular expression\n Given a cucumber\n Given a cucumber and a zucchini\n Given a cucumber and a zucchini and a gourd\n","uri":"samples/regular-expression/regular-expression.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"regular expression","description":" Cucumber supports both Cucumber and regular expressions in step\n definitions. This shows a sample that uses a regular expression.","children":[{"scenario":{"id":"3","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"regular expression","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"a cucumber"},{"id":"1","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"a cucumber and a zucchini"},{"id":"2","location":{"line":9,"column":5},"keyword":"Given ","keywordType":"Context","text":"a cucumber and a zucchini and a gourd"}],"examples":[]}}]},"comments":[],"uri":"samples/regular-expression/regular-expression.feature"}} +{"pickle":{"id":"7","uri":"samples/regular-expression/regular-expression.feature","location":{"line":6,"column":3},"astNodeIds":["3"],"tags":[],"name":"regular expression","language":"en","steps":[{"id":"4","text":"a cucumber","type":"Context","astNodeIds":["0"]},{"id":"5","text":"a cucumber and a zucchini","type":"Context","astNodeIds":["1"]},{"id":"6","text":"a cucumber and a zucchini and a gourd","type":"Context","astNodeIds":["2"]}]}} +{"stepDefinition":{"id":"8","pattern":{"type":"REGULAR_EXPRESSION","source":"^a (.*?)(?: and a (.*?))?(?: and a (.*?))?$"},"sourceReference":{"uri":"samples/regular-expression/regular-expression.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"9","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"10","pickleId":"7","testSteps":[{"id":"11","pickleStepId":"4","stepDefinitionIds":["8"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":2,"value":"cucumber","children":[]}},{"group":{"children":[]}},{"group":{"children":[]}}]}]},{"id":"12","pickleStepId":"5","stepDefinitionIds":["8"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":2,"value":"cucumber","children":[]}},{"group":{"start":17,"value":"zucchini","children":[]}},{"group":{"children":[]}}]}]},{"id":"13","pickleStepId":"6","stepDefinitionIds":["8"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":2,"value":"cucumber","children":[]}},{"group":{"start":17,"value":"zucchini","children":[]}},{"group":{"start":32,"value":"gourd","children":[]}}]}]}],"testRunStartedId":"9"}} +{"testCaseStarted":{"id":"14","testCaseId":"10","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"14","testStepId":"11","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"14","testStepId":"11","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"14","testStepId":"12","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"14","testStepId":"12","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"14","testStepId":"13","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"14","testStepId":"13","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"14","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"9","timestamp":{"seconds":0,"nanos":9000000},"success":true}} diff --git a/compatibility/regular-expression/regular-expression.ts b/compatibility/regular-expression/regular-expression.ts new file mode 100644 index 00000000..49f66a2b --- /dev/null +++ b/compatibility/regular-expression/regular-expression.ts @@ -0,0 +1,6 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given(/^a (.*?)(?: and a (.*?))?(?: and a (.*?))?$/, + function (vegtable1: string, vegtable2: string, vegtable3: string) { + // no-op +}) diff --git a/compatibility/retry-ambiguous/retry-ambiguous.arguments.txt b/compatibility/retry-ambiguous/retry-ambiguous.arguments.txt new file mode 100644 index 00000000..cf83a555 --- /dev/null +++ b/compatibility/retry-ambiguous/retry-ambiguous.arguments.txt @@ -0,0 +1 @@ +--retry 2 diff --git a/compatibility/retry-ambiguous/retry-ambiguous.feature b/compatibility/retry-ambiguous/retry-ambiguous.feature new file mode 100644 index 00000000..2769d2f4 --- /dev/null +++ b/compatibility/retry-ambiguous/retry-ambiguous.feature @@ -0,0 +1,3 @@ +Feature: Retry - With Ambiguous Steps + Scenario: Test cases won't retry when the status is AMBIGUOUS + Given an ambiguous step diff --git a/compatibility/retry-ambiguous/retry-ambiguous.ndjson b/compatibility/retry-ambiguous/retry-ambiguous.ndjson new file mode 100644 index 00000000..70771be9 --- /dev/null +++ b/compatibility/retry-ambiguous/retry-ambiguous.ndjson @@ -0,0 +1,13 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Retry - With Ambiguous Steps\n Scenario: Test cases won't retry when the status is AMBIGUOUS\n Given an ambiguous step\n","uri":"samples/retry-ambiguous/retry-ambiguous.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry - With Ambiguous Steps","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is AMBIGUOUS","description":"","steps":[{"id":"0","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"an ambiguous step"}],"examples":[]}}]},"comments":[],"uri":"samples/retry-ambiguous/retry-ambiguous.feature"}} +{"pickle":{"id":"3","uri":"samples/retry-ambiguous/retry-ambiguous.feature","location":{"line":2,"column":3},"astNodeIds":["1"],"tags":[],"name":"Test cases won't retry when the status is AMBIGUOUS","language":"en","steps":[{"id":"2","text":"an ambiguous step","type":"Context","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an ambiguous step"},"sourceReference":{"uri":"samples/retry-ambiguous/retry-ambiguous.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"5","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an ambiguous step"},"sourceReference":{"uri":"samples/retry-ambiguous/retry-ambiguous.ts","location":{"line":7}}}} +{"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["4","5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]},{"stepMatchArguments":[]}]}],"testRunStartedId":"6"}} +{"testCaseStarted":{"id":"9","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"9","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"9","testStepId":"8","testStepResult":{"status":"AMBIGUOUS","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"9","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"6","timestamp":{"seconds":0,"nanos":5000000},"success":false}} diff --git a/compatibility/retry-ambiguous/retry-ambiguous.ts b/compatibility/retry-ambiguous/retry-ambiguous.ts new file mode 100644 index 00000000..e08a7b17 --- /dev/null +++ b/compatibility/retry-ambiguous/retry-ambiguous.ts @@ -0,0 +1,9 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('an ambiguous step', function () { + // first one +}) + +Given('an ambiguous step', function () { + // second one +}) diff --git a/compatibility/retry-pending/retry-pending.arguments.txt b/compatibility/retry-pending/retry-pending.arguments.txt new file mode 100644 index 00000000..cf83a555 --- /dev/null +++ b/compatibility/retry-pending/retry-pending.arguments.txt @@ -0,0 +1 @@ +--retry 2 diff --git a/compatibility/retry-pending/retry-pending.feature b/compatibility/retry-pending/retry-pending.feature new file mode 100644 index 00000000..bacc607e --- /dev/null +++ b/compatibility/retry-pending/retry-pending.feature @@ -0,0 +1,3 @@ +Feature: Retry - With Pending Steps + Scenario: Test cases won't retry when the status is PENDING + Given a pending step diff --git a/compatibility/retry-pending/retry-pending.ndjson b/compatibility/retry-pending/retry-pending.ndjson new file mode 100644 index 00000000..11265a05 --- /dev/null +++ b/compatibility/retry-pending/retry-pending.ndjson @@ -0,0 +1,12 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Retry - With Pending Steps\n Scenario: Test cases won't retry when the status is PENDING\n Given a pending step\n","uri":"samples/retry-pending/retry-pending.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry - With Pending Steps","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is PENDING","description":"","steps":[{"id":"0","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a pending step"}],"examples":[]}}]},"comments":[],"uri":"samples/retry-pending/retry-pending.feature"}} +{"pickle":{"id":"3","uri":"samples/retry-pending/retry-pending.feature","location":{"line":2,"column":3},"astNodeIds":["1"],"tags":[],"name":"Test cases won't retry when the status is PENDING","language":"en","steps":[{"id":"2","text":"a pending step","type":"Context","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a pending step"},"sourceReference":{"uri":"samples/retry-pending/retry-pending.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"5"}} +{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"PENDING","message":"TODO","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":false}} diff --git a/compatibility/retry-pending/retry-pending.ts b/compatibility/retry-pending/retry-pending.ts new file mode 100644 index 00000000..cab68569 --- /dev/null +++ b/compatibility/retry-pending/retry-pending.ts @@ -0,0 +1,5 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('a pending step', function () { + return 'pending' +}) diff --git a/compatibility/retry-undefined/retry-undefined.arguments.txt b/compatibility/retry-undefined/retry-undefined.arguments.txt new file mode 100644 index 00000000..cf83a555 --- /dev/null +++ b/compatibility/retry-undefined/retry-undefined.arguments.txt @@ -0,0 +1 @@ +--retry 2 diff --git a/compatibility/retry-undefined/retry-undefined.feature b/compatibility/retry-undefined/retry-undefined.feature new file mode 100644 index 00000000..10f7eab6 --- /dev/null +++ b/compatibility/retry-undefined/retry-undefined.feature @@ -0,0 +1,3 @@ +Feature: Retry - With Undefined Steps + Scenario: Test cases won't retry when the status is UNDEFINED + Given a non-existent step diff --git a/compatibility/retry-undefined/retry-undefined.ndjson b/compatibility/retry-undefined/retry-undefined.ndjson new file mode 100644 index 00000000..c2a0619d --- /dev/null +++ b/compatibility/retry-undefined/retry-undefined.ndjson @@ -0,0 +1,12 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Retry - With Undefined Steps\n Scenario: Test cases won't retry when the status is UNDEFINED\n Given a non-existent step\n","uri":"samples/retry-undefined/retry-undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry - With Undefined Steps","description":"","children":[{"scenario":{"id":"1","tags":[],"location":{"line":2,"column":3},"keyword":"Scenario","name":"Test cases won't retry when the status is UNDEFINED","description":"","steps":[{"id":"0","location":{"line":3,"column":5},"keyword":"Given ","keywordType":"Context","text":"a non-existent step"}],"examples":[]}}]},"comments":[],"uri":"samples/retry-undefined/retry-undefined.feature"}} +{"pickle":{"id":"3","uri":"samples/retry-undefined/retry-undefined.feature","location":{"line":2,"column":3},"astNodeIds":["1"],"tags":[],"name":"Test cases won't retry when the status is UNDEFINED","language":"en","steps":[{"id":"2","text":"a non-existent step","type":"Context","astNodeIds":["0"]}]}} +{"testRunStarted":{"id":"4","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"5","pickleId":"3","testSteps":[{"id":"6","pickleStepId":"2","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"4"}} +{"testCaseStarted":{"id":"7","testCaseId":"5","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"7","testStepId":"6","timestamp":{"seconds":0,"nanos":2000000}}} +{"suggestion":{"id":"8","pickleStepId":"2","snippets":[{"language":"typescript","code":"Given(\"a non-existent step\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"7","testStepId":"6","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"7","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"4","timestamp":{"seconds":0,"nanos":5000000},"success":false}} diff --git a/compatibility/retry-undefined/retry-undefined.ts b/compatibility/retry-undefined/retry-undefined.ts new file mode 100644 index 00000000..7b8d0a62 --- /dev/null +++ b/compatibility/retry-undefined/retry-undefined.ts @@ -0,0 +1 @@ +// There are intentionally no steps defined for this sample \ No newline at end of file diff --git a/compatibility/retry/retry.arguments.txt b/compatibility/retry/retry.arguments.txt new file mode 100644 index 00000000..cf83a555 --- /dev/null +++ b/compatibility/retry/retry.arguments.txt @@ -0,0 +1 @@ +--retry 2 diff --git a/compatibility/retry/retry.feature b/compatibility/retry/retry.feature new file mode 100644 index 00000000..e48671dd --- /dev/null +++ b/compatibility/retry/retry.feature @@ -0,0 +1,18 @@ +Feature: Retry + Some Cucumber implementations support a Retry mechanism, where test cases that fail + can be retried up to a limited number of attempts in the same test run. + + Non-passing statuses other than FAILED won't trigger a retry, as they are not + going to pass however many times we attempt them. + + Scenario: Test cases that pass aren't retried + Given a step that always passes + + Scenario: Test cases that fail are retried if within the --retry limit + Given a step that passes the second time + + Scenario: Test cases that fail will continue to retry up to the --retry limit + Given a step that passes the third time + + Scenario: Test cases won't retry after failing more than the --retry limit + Given a step that always fails diff --git a/compatibility/retry/retry.ndjson b/compatibility/retry/retry.ndjson new file mode 100644 index 00000000..d3c2d9cb --- /dev/null +++ b/compatibility/retry/retry.ndjson @@ -0,0 +1,53 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Retry\n Some Cucumber implementations support a Retry mechanism, where test cases that fail\n can be retried up to a limited number of attempts in the same test run.\n\n Non-passing statuses other than FAILED won't trigger a retry, as they are not\n going to pass however many times we attempt them.\n\n Scenario: Test cases that pass aren't retried\n Given a step that always passes\n\n Scenario: Test cases that fail are retried if within the --retry limit\n Given a step that passes the second time\n\n Scenario: Test cases that fail will continue to retry up to the --retry limit\n Given a step that passes the third time\n\n Scenario: Test cases won't retry after failing more than the --retry limit\n Given a step that always fails\n","uri":"samples/retry/retry.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Retry","description":" Some Cucumber implementations support a Retry mechanism, where test cases that fail\n can be retried up to a limited number of attempts in the same test run.\n\n Non-passing statuses other than FAILED won't trigger a retry, as they are not\n going to pass however many times we attempt them.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":8,"column":3},"keyword":"Scenario","name":"Test cases that pass aren't retried","description":"","steps":[{"id":"0","location":{"line":9,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that always passes"}],"examples":[]}},{"scenario":{"id":"3","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Test cases that fail are retried if within the --retry limit","description":"","steps":[{"id":"2","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that passes the second time"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":14,"column":3},"keyword":"Scenario","name":"Test cases that fail will continue to retry up to the --retry limit","description":"","steps":[{"id":"4","location":{"line":15,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that passes the third time"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":17,"column":3},"keyword":"Scenario","name":"Test cases won't retry after failing more than the --retry limit","description":"","steps":[{"id":"6","location":{"line":18,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that always fails"}],"examples":[]}}]},"comments":[],"uri":"samples/retry/retry.feature"}} +{"pickle":{"id":"9","uri":"samples/retry/retry.feature","location":{"line":8,"column":3},"astNodeIds":["1"],"tags":[],"name":"Test cases that pass aren't retried","language":"en","steps":[{"id":"8","text":"a step that always passes","type":"Context","astNodeIds":["0"]}]}} +{"pickle":{"id":"11","uri":"samples/retry/retry.feature","location":{"line":11,"column":3},"astNodeIds":["3"],"tags":[],"name":"Test cases that fail are retried if within the --retry limit","language":"en","steps":[{"id":"10","text":"a step that passes the second time","type":"Context","astNodeIds":["2"]}]}} +{"pickle":{"id":"13","uri":"samples/retry/retry.feature","location":{"line":14,"column":3},"astNodeIds":["5"],"tags":[],"name":"Test cases that fail will continue to retry up to the --retry limit","language":"en","steps":[{"id":"12","text":"a step that passes the third time","type":"Context","astNodeIds":["4"]}]}} +{"pickle":{"id":"15","uri":"samples/retry/retry.feature","location":{"line":17,"column":3},"astNodeIds":["7"],"tags":[],"name":"Test cases won't retry after failing more than the --retry limit","language":"en","steps":[{"id":"14","text":"a step that always fails","type":"Context","astNodeIds":["6"]}]}} +{"stepDefinition":{"id":"16","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that always passes"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"17","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that passes the second time"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":8}}}} +{"stepDefinition":{"id":"18","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that passes the third time"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":16}}}} +{"stepDefinition":{"id":"19","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that always fails"},"sourceReference":{"uri":"samples/retry/retry.ts","location":{"line":23}}}} +{"testRunStarted":{"id":"20","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"21","pickleId":"9","testSteps":[{"id":"22","pickleStepId":"8","stepDefinitionIds":["16"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}} +{"testCase":{"id":"23","pickleId":"11","testSteps":[{"id":"24","pickleStepId":"10","stepDefinitionIds":["17"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}} +{"testCase":{"id":"25","pickleId":"13","testSteps":[{"id":"26","pickleStepId":"12","stepDefinitionIds":["18"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}} +{"testCase":{"id":"27","pickleId":"15","testSteps":[{"id":"28","pickleStepId":"14","stepDefinitionIds":["19"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"20"}} +{"testCaseStarted":{"id":"29","testCaseId":"21","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"29","testStepId":"22","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"29","testStepId":"22","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"29","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"30","testCaseId":"23","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"30","testStepId":"24","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"30","testStepId":"24","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:12"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testCaseFinished":{"testCaseStartedId":"30","timestamp":{"seconds":0,"nanos":8000000},"willBeRetried":true}} +{"testCaseStarted":{"id":"31","testCaseId":"23","timestamp":{"seconds":0,"nanos":9000000},"attempt":1}} +{"testStepStarted":{"testCaseStartedId":"31","testStepId":"24","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"31","testStepId":"24","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"31","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"32","testCaseId":"25","timestamp":{"seconds":0,"nanos":13000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"32","testStepId":"26","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"32","testStepId":"26","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:15"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"32","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":true}} +{"testCaseStarted":{"id":"33","testCaseId":"25","timestamp":{"seconds":0,"nanos":17000000},"attempt":1}} +{"testStepStarted":{"testCaseStartedId":"33","testStepId":"26","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"33","testStepId":"26","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:15"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"33","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":true}} +{"testCaseStarted":{"id":"34","testCaseId":"25","timestamp":{"seconds":0,"nanos":21000000},"attempt":2}} +{"testStepStarted":{"testCaseStartedId":"34","testStepId":"26","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"34","testStepId":"26","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testCaseFinished":{"testCaseStartedId":"34","timestamp":{"seconds":0,"nanos":24000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"35","testCaseId":"27","timestamp":{"seconds":0,"nanos":25000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"35","testStepId":"28","timestamp":{"seconds":0,"nanos":26000000}}} +{"testStepFinished":{"testCaseStartedId":"35","testStepId":"28","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} +{"testCaseFinished":{"testCaseStartedId":"35","timestamp":{"seconds":0,"nanos":28000000},"willBeRetried":true}} +{"testCaseStarted":{"id":"36","testCaseId":"27","timestamp":{"seconds":0,"nanos":29000000},"attempt":1}} +{"testStepStarted":{"testCaseStartedId":"36","testStepId":"28","timestamp":{"seconds":0,"nanos":30000000}}} +{"testStepFinished":{"testCaseStartedId":"36","testStepId":"28","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}} +{"testCaseFinished":{"testCaseStartedId":"36","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":true}} +{"testCaseStarted":{"id":"37","testCaseId":"27","timestamp":{"seconds":0,"nanos":33000000},"attempt":2}} +{"testStepStarted":{"testCaseStartedId":"37","testStepId":"28","timestamp":{"seconds":0,"nanos":34000000}}} +{"testStepFinished":{"testCaseStartedId":"37","testStepId":"28","testStepResult":{"message":"Exception in step","exception":{"type":"Error","message":"Exception in step","stackTrace":"Error: Exception in step\nsamples/retry/retry.feature:18"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":35000000}}} +{"testCaseFinished":{"testCaseStartedId":"37","timestamp":{"seconds":0,"nanos":36000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"20","timestamp":{"seconds":0,"nanos":37000000},"success":false}} diff --git a/compatibility/retry/retry.ts b/compatibility/retry/retry.ts new file mode 100644 index 00000000..077e37cd --- /dev/null +++ b/compatibility/retry/retry.ts @@ -0,0 +1,25 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('a step that always passes', function () { + // no-op +}) + +let secondTimePass = 0 +Given('a step that passes the second time', function () { + secondTimePass++ + if (secondTimePass < 2) { + throw new Error('Exception in step') + } +}) + +let thirdTimePass = 0 +Given('a step that passes the third time', function () { + thirdTimePass++ + if (thirdTimePass < 3) { + throw new Error('Exception in step') + } +}) + +Given('a step that always fails', function () { + throw new Error('Exception in step') +}) diff --git a/compatibility/rules-backgrounds/rules-backgrounds.feature b/compatibility/rules-backgrounds/rules-backgrounds.feature new file mode 100644 index 00000000..d4f79667 --- /dev/null +++ b/compatibility/rules-backgrounds/rules-backgrounds.feature @@ -0,0 +1,23 @@ +Feature: Rules with Backgrounds + Like Features, Rules can also have Backgrounds, whose steps are prepended to those of each child Scenario. Only + one Background at the Rule level is supported. + + It's even possible to have a Background at both Feature and Rule level, in which case they are concatenated. + + Background: + Given an order for "eggs" + And an order for "milk" + And an order for "bread" + + Rule: + Background: + Given an order for "batteries" + And an order for "light bulbs" + + Example: one scenario + When an action + Then an outcome + + Example: another scenario + When an action + Then an outcome \ No newline at end of file diff --git a/compatibility/rules-backgrounds/rules-backgrounds.ndjson b/compatibility/rules-backgrounds/rules-backgrounds.ndjson new file mode 100644 index 00000000..c4ddf144 --- /dev/null +++ b/compatibility/rules-backgrounds/rules-backgrounds.ndjson @@ -0,0 +1,44 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Rules with Backgrounds\n Like Features, Rules can also have Backgrounds, whose steps are prepended to those of each child Scenario. Only\n one Background at the Rule level is supported.\n\n It's even possible to have a Background at both Feature and Rule level, in which case they are concatenated.\n\n Background:\n Given an order for \"eggs\"\n And an order for \"milk\"\n And an order for \"bread\"\n\n Rule:\n Background:\n Given an order for \"batteries\"\n And an order for \"light bulbs\"\n\n Example: one scenario\n When an action\n Then an outcome\n\n Example: another scenario\n When an action\n Then an outcome","uri":"samples/rules-backgrounds/rules-backgrounds.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Rules with Backgrounds","description":" Like Features, Rules can also have Backgrounds, whose steps are prepended to those of each child Scenario. Only\n one Background at the Rule level is supported.\n\n It's even possible to have a Background at both Feature and Rule level, in which case they are concatenated.","children":[{"background":{"id":"3","location":{"line":7,"column":3},"keyword":"Background","name":"","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"an order for \"eggs\""},{"id":"1","location":{"line":9,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"milk\""},{"id":"2","location":{"line":10,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"bread\""}]}},{"rule":{"id":"13","location":{"line":12,"column":3},"keyword":"Rule","name":"","description":"","children":[{"background":{"id":"6","location":{"line":13,"column":5},"keyword":"Background","name":"","description":"","steps":[{"id":"4","location":{"line":14,"column":7},"keyword":"Given ","keywordType":"Context","text":"an order for \"batteries\""},{"id":"5","location":{"line":15,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"an order for \"light bulbs\""}]}},{"scenario":{"id":"9","tags":[],"location":{"line":17,"column":5},"keyword":"Example","name":"one scenario","description":"","steps":[{"id":"7","location":{"line":18,"column":7},"keyword":"When ","keywordType":"Action","text":"an action"},{"id":"8","location":{"line":19,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"an outcome"}],"examples":[]}},{"scenario":{"id":"12","tags":[],"location":{"line":21,"column":5},"keyword":"Example","name":"another scenario","description":"","steps":[{"id":"10","location":{"line":22,"column":7},"keyword":"When ","keywordType":"Action","text":"an action"},{"id":"11","location":{"line":23,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"an outcome"}],"examples":[]}}],"tags":[]}}]},"comments":[],"uri":"samples/rules-backgrounds/rules-backgrounds.feature"}} +{"pickle":{"id":"21","uri":"samples/rules-backgrounds/rules-backgrounds.feature","location":{"line":17,"column":5},"astNodeIds":["9"],"tags":[],"name":"one scenario","language":"en","steps":[{"id":"14","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]},{"id":"15","text":"an order for \"milk\"","type":"Context","astNodeIds":["1"]},{"id":"16","text":"an order for \"bread\"","type":"Context","astNodeIds":["2"]},{"id":"17","text":"an order for \"batteries\"","type":"Context","astNodeIds":["4"]},{"id":"18","text":"an order for \"light bulbs\"","type":"Context","astNodeIds":["5"]},{"id":"19","text":"an action","type":"Action","astNodeIds":["7"]},{"id":"20","text":"an outcome","type":"Outcome","astNodeIds":["8"]}]}} +{"pickle":{"id":"29","uri":"samples/rules-backgrounds/rules-backgrounds.feature","location":{"line":21,"column":5},"astNodeIds":["12"],"tags":[],"name":"another scenario","language":"en","steps":[{"id":"22","text":"an order for \"eggs\"","type":"Context","astNodeIds":["0"]},{"id":"23","text":"an order for \"milk\"","type":"Context","astNodeIds":["1"]},{"id":"24","text":"an order for \"bread\"","type":"Context","astNodeIds":["2"]},{"id":"25","text":"an order for \"batteries\"","type":"Context","astNodeIds":["4"]},{"id":"26","text":"an order for \"light bulbs\"","type":"Context","astNodeIds":["5"]},{"id":"27","text":"an action","type":"Action","astNodeIds":["10"]},{"id":"28","text":"an outcome","type":"Outcome","astNodeIds":["11"]}]}} +{"stepDefinition":{"id":"30","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an order for {string}"},"sourceReference":{"uri":"samples/rules-backgrounds/rules-backgrounds.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"31","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an action"},"sourceReference":{"uri":"samples/rules-backgrounds/rules-backgrounds.ts","location":{"line":7}}}} +{"stepDefinition":{"id":"32","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an outcome"},"sourceReference":{"uri":"samples/rules-backgrounds/rules-backgrounds.ts","location":{"line":11}}}} +{"testRunStarted":{"id":"33","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"34","pickleId":"21","testSteps":[{"id":"35","pickleStepId":"14","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"eggs\"","children":[{"start":14,"value":"eggs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"36","pickleStepId":"15","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"milk\"","children":[{"start":14,"value":"milk","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"37","pickleStepId":"16","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"bread\"","children":[{"start":14,"value":"bread","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"38","pickleStepId":"17","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"batteries\"","children":[{"start":14,"value":"batteries","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"39","pickleStepId":"18","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"light bulbs\"","children":[{"start":14,"value":"light bulbs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"40","pickleStepId":"19","stepDefinitionIds":["31"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"41","pickleStepId":"20","stepDefinitionIds":["32"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"33"}} +{"testCase":{"id":"42","pickleId":"29","testSteps":[{"id":"43","pickleStepId":"22","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"eggs\"","children":[{"start":14,"value":"eggs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"44","pickleStepId":"23","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"milk\"","children":[{"start":14,"value":"milk","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"45","pickleStepId":"24","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"bread\"","children":[{"start":14,"value":"bread","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"46","pickleStepId":"25","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"batteries\"","children":[{"start":14,"value":"batteries","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"47","pickleStepId":"26","stepDefinitionIds":["30"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":13,"value":"\"light bulbs\"","children":[{"start":14,"value":"light bulbs","children":[{"children":[]}]},{"children":[{"children":[]}]}]},"parameterTypeName":"string"}]}]},{"id":"48","pickleStepId":"27","stepDefinitionIds":["31"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"49","pickleStepId":"28","stepDefinitionIds":["32"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"33"}} +{"testCaseStarted":{"id":"50","testCaseId":"34","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"35","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"35","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"36","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"36","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"37","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"37","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"38","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"38","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"39","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"39","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"40","timestamp":{"seconds":0,"nanos":12000000}}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"40","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"50","testStepId":"41","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"50","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"50","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"51","testCaseId":"42","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"43","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"44","timestamp":{"seconds":0,"nanos":20000000}}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"44","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":21000000}}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"45","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"45","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"46","timestamp":{"seconds":0,"nanos":24000000}}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"46","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":25000000}}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"47","timestamp":{"seconds":0,"nanos":26000000}}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"48","timestamp":{"seconds":0,"nanos":28000000}}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"48","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}} +{"testStepStarted":{"testCaseStartedId":"51","testStepId":"49","timestamp":{"seconds":0,"nanos":30000000}}} +{"testStepFinished":{"testCaseStartedId":"51","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":31000000}}} +{"testCaseFinished":{"testCaseStartedId":"51","timestamp":{"seconds":0,"nanos":32000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"33","timestamp":{"seconds":0,"nanos":33000000},"success":true}} diff --git a/compatibility/rules-backgrounds/rules-backgrounds.ts b/compatibility/rules-backgrounds/rules-backgrounds.ts new file mode 100644 index 00000000..cb475f6e --- /dev/null +++ b/compatibility/rules-backgrounds/rules-backgrounds.ts @@ -0,0 +1,13 @@ +import { Given, When, Then } from '@cucumber/fake-cucumber' + +Given('an order for {string}', () => { + // no-op +}) + +When('an action', () => { + // no-op +}) + +Then('an outcome', () => { + // no-op +}) diff --git a/compatibility/rules/rules.feature b/compatibility/rules/rules.feature new file mode 100644 index 00000000..5d576ac7 --- /dev/null +++ b/compatibility/rules/rules.feature @@ -0,0 +1,29 @@ +Feature: Usage of a `Rule` + You can place scenarios inside rules. This makes it possible to structure Gherkin documents + in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/). + + You can also use the Examples synonym for Scenario to make them even similar. + + Rule: A sale cannot happen if the customer does not have enough money + # Unhappy path + Example: Not enough money + Given the customer has 100 cents + And there are chocolate bars in stock + When the customer tries to buy a 125 cent chocolate bar + Then the sale should not happen + + # Happy path + Example: Enough money + Given the customer has 100 cents + And there are chocolate bars in stock + When the customer tries to buy a 75 cent chocolate bar + Then the sale should happen + + @some-tag + Rule: a sale cannot happen if there is no stock + # Unhappy path + Example: No chocolates left + Given the customer has 100 cents + And there are no chocolate bars in stock + When the customer tries to buy a 1 cent chocolate bar + Then the sale should not happen diff --git a/compatibility/rules/rules.ndjson b/compatibility/rules/rules.ndjson new file mode 100644 index 00000000..8a8fd1b9 --- /dev/null +++ b/compatibility/rules/rules.ndjson @@ -0,0 +1,47 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Usage of a `Rule`\n You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.\n\n Rule: A sale cannot happen if the customer does not have enough money\n # Unhappy path\n Example: Not enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 125 cent chocolate bar\n Then the sale should not happen\n\n # Happy path\n Example: Enough money\n Given the customer has 100 cents\n And there are chocolate bars in stock\n When the customer tries to buy a 75 cent chocolate bar\n Then the sale should happen\n\n @some-tag\n Rule: a sale cannot happen if there is no stock\n # Unhappy path\n Example: No chocolates left\n Given the customer has 100 cents\n And there are no chocolate bars in stock\n When the customer tries to buy a 1 cent chocolate bar\n Then the sale should not happen\n","uri":"samples/rules/rules.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Usage of a `Rule`","description":" You can place scenarios inside rules. This makes it possible to structure Gherkin documents\n in the same way as [example maps](https://cucumber.io/blog/bdd/example-mapping-introduction/).\n\n You can also use the Examples synonym for Scenario to make them even similar.","children":[{"rule":{"id":"10","location":{"line":7,"column":3},"keyword":"Rule","name":"A sale cannot happen if the customer does not have enough money","description":"","children":[{"scenario":{"id":"4","tags":[],"location":{"line":9,"column":5},"keyword":"Example","name":"Not enough money","description":"","steps":[{"id":"0","location":{"line":10,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"1","location":{"line":11,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are chocolate bars in stock"},{"id":"2","location":{"line":12,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 125 cent chocolate bar"},{"id":"3","location":{"line":13,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should not happen"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":16,"column":5},"keyword":"Example","name":"Enough money","description":"","steps":[{"id":"5","location":{"line":17,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"6","location":{"line":18,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are chocolate bars in stock"},{"id":"7","location":{"line":19,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 75 cent chocolate bar"},{"id":"8","location":{"line":20,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should happen"}],"examples":[]}}],"tags":[]}},{"rule":{"id":"17","location":{"line":23,"column":3},"keyword":"Rule","name":"a sale cannot happen if there is no stock","description":"","children":[{"scenario":{"id":"15","tags":[],"location":{"line":25,"column":5},"keyword":"Example","name":"No chocolates left","description":"","steps":[{"id":"11","location":{"line":26,"column":7},"keyword":"Given ","keywordType":"Context","text":"the customer has 100 cents"},{"id":"12","location":{"line":27,"column":7},"keyword":"And ","keywordType":"Conjunction","text":"there are no chocolate bars in stock"},{"id":"13","location":{"line":28,"column":7},"keyword":"When ","keywordType":"Action","text":"the customer tries to buy a 1 cent chocolate bar"},{"id":"14","location":{"line":29,"column":7},"keyword":"Then ","keywordType":"Outcome","text":"the sale should not happen"}],"examples":[]}}],"tags":[{"location":{"line":22,"column":3},"name":"@some-tag","id":"16"}]}}]},"comments":[{"location":{"line":8,"column":1},"text":" # Unhappy path"},{"location":{"line":15,"column":1},"text":" # Happy path"},{"location":{"line":24,"column":1},"text":" # Unhappy path"}],"uri":"samples/rules/rules.feature"}} +{"pickle":{"id":"22","uri":"samples/rules/rules.feature","location":{"line":9,"column":5},"astNodeIds":["4"],"tags":[],"name":"Not enough money","language":"en","steps":[{"id":"18","text":"the customer has 100 cents","type":"Context","astNodeIds":["0"]},{"id":"19","text":"there are chocolate bars in stock","type":"Context","astNodeIds":["1"]},{"id":"20","text":"the customer tries to buy a 125 cent chocolate bar","type":"Action","astNodeIds":["2"]},{"id":"21","text":"the sale should not happen","type":"Outcome","astNodeIds":["3"]}]}} +{"pickle":{"id":"27","uri":"samples/rules/rules.feature","location":{"line":16,"column":5},"astNodeIds":["9"],"tags":[],"name":"Enough money","language":"en","steps":[{"id":"23","text":"the customer has 100 cents","type":"Context","astNodeIds":["5"]},{"id":"24","text":"there are chocolate bars in stock","type":"Context","astNodeIds":["6"]},{"id":"25","text":"the customer tries to buy a 75 cent chocolate bar","type":"Action","astNodeIds":["7"]},{"id":"26","text":"the sale should happen","type":"Outcome","astNodeIds":["8"]}]}} +{"pickle":{"id":"32","uri":"samples/rules/rules.feature","location":{"line":25,"column":5},"astNodeIds":["15"],"tags":[{"name":"@some-tag","astNodeId":"16"}],"name":"No chocolates left","language":"en","steps":[{"id":"28","text":"the customer has 100 cents","type":"Context","astNodeIds":["11"]},{"id":"29","text":"there are no chocolate bars in stock","type":"Context","astNodeIds":["12"]},{"id":"30","text":"the customer tries to buy a 1 cent chocolate bar","type":"Action","astNodeIds":["13"]},{"id":"31","text":"the sale should not happen","type":"Outcome","astNodeIds":["14"]}]}} +{"stepDefinition":{"id":"33","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the customer has {int} cents"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":4}}}} +{"stepDefinition":{"id":"34","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are chocolate bars in stock"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":8}}}} +{"stepDefinition":{"id":"35","pattern":{"type":"CUCUMBER_EXPRESSION","source":"there are no chocolate bars in stock"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":12}}}} +{"stepDefinition":{"id":"36","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the customer tries to buy a {int} cent chocolate bar"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":16}}}} +{"stepDefinition":{"id":"37","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the sale should not happen"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":22}}}} +{"stepDefinition":{"id":"38","pattern":{"type":"CUCUMBER_EXPRESSION","source":"the sale should happen"},"sourceReference":{"uri":"samples/rules/rules.ts","location":{"line":26}}}} +{"testRunStarted":{"id":"39","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"40","pickleId":"22","testSteps":[{"id":"41","pickleStepId":"18","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"42","pickleStepId":"19","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"43","pickleStepId":"20","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"125","children":[]},"parameterTypeName":"int"}]}]},{"id":"44","pickleStepId":"21","stepDefinitionIds":["37"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} +{"testCase":{"id":"45","pickleId":"27","testSteps":[{"id":"46","pickleStepId":"23","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"47","pickleStepId":"24","stepDefinitionIds":["34"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"48","pickleStepId":"25","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"75","children":[]},"parameterTypeName":"int"}]}]},{"id":"49","pickleStepId":"26","stepDefinitionIds":["38"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} +{"testCase":{"id":"50","pickleId":"32","testSteps":[{"id":"51","pickleStepId":"28","stepDefinitionIds":["33"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":17,"value":"100","children":[]},"parameterTypeName":"int"}]}]},{"id":"52","pickleStepId":"29","stepDefinitionIds":["35"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"53","pickleStepId":"30","stepDefinitionIds":["36"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":28,"value":"1","children":[]},"parameterTypeName":"int"}]}]},{"id":"54","pickleStepId":"31","stepDefinitionIds":["37"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"39"}} +{"testCaseStarted":{"id":"55","testCaseId":"40","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"41","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"41","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"42","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"42","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"43","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"43","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testStepStarted":{"testCaseStartedId":"55","testStepId":"44","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"55","testStepId":"44","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testCaseFinished":{"testCaseStartedId":"55","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"56","testCaseId":"45","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"46","timestamp":{"seconds":0,"nanos":12000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"46","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"47","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"47","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"48","timestamp":{"seconds":0,"nanos":16000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"48","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":17000000}}} +{"testStepStarted":{"testCaseStartedId":"56","testStepId":"49","timestamp":{"seconds":0,"nanos":18000000}}} +{"testStepFinished":{"testCaseStartedId":"56","testStepId":"49","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"56","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"57","testCaseId":"50","timestamp":{"seconds":0,"nanos":21000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"51","timestamp":{"seconds":0,"nanos":22000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"51","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":23000000}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"52","timestamp":{"seconds":0,"nanos":24000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"52","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":25000000}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"53","timestamp":{"seconds":0,"nanos":26000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"53","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":27000000}}} +{"testStepStarted":{"testCaseStartedId":"57","testStepId":"54","timestamp":{"seconds":0,"nanos":28000000}}} +{"testStepFinished":{"testCaseStartedId":"57","testStepId":"54","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":29000000}}} +{"testCaseFinished":{"testCaseStartedId":"57","timestamp":{"seconds":0,"nanos":30000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"39","timestamp":{"seconds":0,"nanos":31000000},"success":true}} diff --git a/compatibility/rules/rules.ts b/compatibility/rules/rules.ts new file mode 100644 index 00000000..dbcbfe33 --- /dev/null +++ b/compatibility/rules/rules.ts @@ -0,0 +1,28 @@ +import assert from 'node:assert' +import { Given, When, Then } from '@cucumber/fake-cucumber' + +Given('the customer has {int} cents', function (money) { + this.money = money +}) + +Given('there are chocolate bars in stock', function () { + this.stock = ['Mars'] +}) + +Given('there are no chocolate bars in stock', function () { + this.stock = [] +}) + +When('the customer tries to buy a {int} cent chocolate bar', function (price) { + if(this.money >= price) { + this.chocolate = this.stock.pop() + } +}) + +Then('the sale should not happen', function () { + assert.strictEqual(this.chocolate, undefined) +}) + +Then('the sale should happen', function () { + assert.ok(this.chocolate) +}) diff --git a/compatibility/skipped/skipped.feature b/compatibility/skipped/skipped.feature new file mode 100644 index 00000000..73d11ad0 --- /dev/null +++ b/compatibility/skipped/skipped.feature @@ -0,0 +1,15 @@ +Feature: Skipping scenarios + + Step definitions are able to signal at runtime that the scenario should + be skipped by raising a particular kind of exception status (For example PENDING or SKIPPED). + + This can be useful in certain situations e.g. the current environment doesn't have + the right conditions for running a particular scenario. + + Scenario: Skipping from a step doesn't affect the previous steps + Given a step that does not skip + And I skip a step + + Scenario: Skipping from a step causes the rest of the scenario to be skipped + Given I skip a step + And a step that is skipped diff --git a/compatibility/skipped/skipped.ndjson b/compatibility/skipped/skipped.ndjson new file mode 100644 index 00000000..fee16080 --- /dev/null +++ b/compatibility/skipped/skipped.ndjson @@ -0,0 +1,24 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Skipping scenarios\n\n Step definitions are able to signal at runtime that the scenario should\n be skipped by raising a particular kind of exception status (For example PENDING or SKIPPED).\n\n This can be useful in certain situations e.g. the current environment doesn't have\n the right conditions for running a particular scenario.\n\n Scenario: Skipping from a step doesn't affect the previous steps\n Given a step that does not skip\n And I skip a step\n\n Scenario: Skipping from a step causes the rest of the scenario to be skipped\n Given I skip a step\n And a step that is skipped\n","uri":"samples/skipped/skipped.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Skipping scenarios","description":" Step definitions are able to signal at runtime that the scenario should\n be skipped by raising a particular kind of exception status (For example PENDING or SKIPPED).\n\n This can be useful in certain situations e.g. the current environment doesn't have\n the right conditions for running a particular scenario.","children":[{"scenario":{"id":"2","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"Skipping from a step doesn't affect the previous steps","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that does not skip"},{"id":"1","location":{"line":11,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"I skip a step"}],"examples":[]}},{"scenario":{"id":"5","tags":[],"location":{"line":13,"column":3},"keyword":"Scenario","name":"Skipping from a step causes the rest of the scenario to be skipped","description":"","steps":[{"id":"3","location":{"line":14,"column":5},"keyword":"Given ","keywordType":"Context","text":"I skip a step"},{"id":"4","location":{"line":15,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"a step that is skipped"}],"examples":[]}}]},"comments":[],"uri":"samples/skipped/skipped.feature"}} +{"pickle":{"id":"8","uri":"samples/skipped/skipped.feature","location":{"line":9,"column":3},"astNodeIds":["2"],"tags":[],"name":"Skipping from a step doesn't affect the previous steps","language":"en","steps":[{"id":"6","text":"a step that does not skip","type":"Context","astNodeIds":["0"]},{"id":"7","text":"I skip a step","type":"Context","astNodeIds":["1"]}]}} +{"pickle":{"id":"11","uri":"samples/skipped/skipped.feature","location":{"line":13,"column":3},"astNodeIds":["5"],"tags":[],"name":"Skipping from a step causes the rest of the scenario to be skipped","language":"en","steps":[{"id":"9","text":"I skip a step","type":"Context","astNodeIds":["3"]},{"id":"10","text":"a step that is skipped","type":"Context","astNodeIds":["4"]}]}} +{"stepDefinition":{"id":"12","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that does not skip"},"sourceReference":{"uri":"samples/skipped/skipped.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"13","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that is skipped"},"sourceReference":{"uri":"samples/skipped/skipped.ts","location":{"line":7}}}} +{"stepDefinition":{"id":"14","pattern":{"type":"CUCUMBER_EXPRESSION","source":"I skip a step"},"sourceReference":{"uri":"samples/skipped/skipped.ts","location":{"line":11}}}} +{"testRunStarted":{"id":"15","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"16","pickleId":"8","testSteps":[{"id":"17","pickleStepId":"6","stepDefinitionIds":["12"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"18","pickleStepId":"7","stepDefinitionIds":["14"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"15"}} +{"testCase":{"id":"19","pickleId":"11","testSteps":[{"id":"20","pickleStepId":"9","stepDefinitionIds":["14"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"21","pickleStepId":"10","stepDefinitionIds":["13"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"15"}} +{"testCaseStarted":{"id":"22","testCaseId":"16","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"17","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"17","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testStepStarted":{"testCaseStartedId":"22","testStepId":"18","timestamp":{"seconds":0,"nanos":4000000}}} +{"testStepFinished":{"testCaseStartedId":"22","testStepId":"18","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":5000000}}} +{"testCaseFinished":{"testCaseStartedId":"22","timestamp":{"seconds":0,"nanos":6000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"23","testCaseId":"19","timestamp":{"seconds":0,"nanos":7000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"23","testStepId":"20","timestamp":{"seconds":0,"nanos":8000000}}} +{"testStepFinished":{"testCaseStartedId":"23","testStepId":"20","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testStepStarted":{"testCaseStartedId":"23","testStepId":"21","timestamp":{"seconds":0,"nanos":10000000}}} +{"testStepFinished":{"testCaseStartedId":"23","testStepId":"21","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":11000000}}} +{"testCaseFinished":{"testCaseStartedId":"23","timestamp":{"seconds":0,"nanos":12000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"15","timestamp":{"seconds":0,"nanos":13000000},"success":true}} diff --git a/compatibility/skipped/skipped.ts b/compatibility/skipped/skipped.ts new file mode 100644 index 00000000..fdb86d24 --- /dev/null +++ b/compatibility/skipped/skipped.ts @@ -0,0 +1,13 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('a step that does not skip', function () { + // no-op +}) + +Given('a step that is skipped', function () { + // no-op +}) + +Given('I skip a step', function () { + return 'skipped' +}) diff --git a/compatibility/stack-traces/stack-traces.feature b/compatibility/stack-traces/stack-traces.feature new file mode 100644 index 00000000..2f6ff485 --- /dev/null +++ b/compatibility/stack-traces/stack-traces.feature @@ -0,0 +1,10 @@ +Feature: Stack traces + Stack traces can help you diagnose the source of a bug. + + Cucumber provides helpful stack traces that includes the stack frames from the + Gherkin document and remove uninteresting frames by default. + + The first line of the stack trace will contain a reference to the feature file. + + Scenario: A failing step + When a step throws an exception diff --git a/compatibility/stack-traces/stack-traces.ndjson b/compatibility/stack-traces/stack-traces.ndjson new file mode 100644 index 00000000..0fb0ad95 --- /dev/null +++ b/compatibility/stack-traces/stack-traces.ndjson @@ -0,0 +1,12 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Stack traces\n Stack traces can help you diagnose the source of a bug.\n\n Cucumber provides helpful stack traces that includes the stack frames from the\n Gherkin document and remove uninteresting frames by default.\n\n The first line of the stack trace will contain a reference to the feature file.\n\n Scenario: A failing step\n When a step throws an exception\n","uri":"samples/stack-traces/stack-traces.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Stack traces","description":" Stack traces can help you diagnose the source of a bug.\n\n Cucumber provides helpful stack traces that includes the stack frames from the\n Gherkin document and remove uninteresting frames by default.\n\n The first line of the stack trace will contain a reference to the feature file.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":9,"column":3},"keyword":"Scenario","name":"A failing step","description":"","steps":[{"id":"0","location":{"line":10,"column":5},"keyword":"When ","keywordType":"Action","text":"a step throws an exception"}],"examples":[]}}]},"comments":[],"uri":"samples/stack-traces/stack-traces.feature"}} +{"pickle":{"id":"3","uri":"samples/stack-traces/stack-traces.feature","location":{"line":9,"column":3},"astNodeIds":["1"],"tags":[],"name":"A failing step","language":"en","steps":[{"id":"2","text":"a step throws an exception","type":"Action","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step throws an exception"},"sourceReference":{"uri":"samples/stack-traces/stack-traces.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"5"}} +{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"message":"BOOM","exception":{"type":"Error","message":"BOOM","stackTrace":"Error: BOOM\nsamples/stack-traces/stack-traces.feature:10"},"status":"FAILED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":false}} diff --git a/compatibility/stack-traces/stack-traces.ts b/compatibility/stack-traces/stack-traces.ts new file mode 100644 index 00000000..2ffd339b --- /dev/null +++ b/compatibility/stack-traces/stack-traces.ts @@ -0,0 +1,5 @@ +import { When } from '@cucumber/fake-cucumber' + +When('a step throws an exception', function () { + throw new Error('BOOM') +}) diff --git a/compatibility/test-run-exception/test-run-exception.arguments.txt b/compatibility/test-run-exception/test-run-exception.arguments.txt new file mode 100644 index 00000000..6c11c1b4 --- /dev/null +++ b/compatibility/test-run-exception/test-run-exception.arguments.txt @@ -0,0 +1 @@ +--error diff --git a/compatibility/test-run-exception/test-run-exception.feature b/compatibility/test-run-exception/test-run-exception.feature new file mode 100644 index 00000000..1410da17 --- /dev/null +++ b/compatibility/test-run-exception/test-run-exception.feature @@ -0,0 +1,8 @@ +Feature: Test run exceptions + + Sometimes an exception might happen during the test run, but outside of user-supplied hook + or step code. This might be bad configuration, a bug in Cucumber, or an environmental issue. + In such cases, Cucumber will abort the test run and include the exception in the final message. + + Scenario: exception during test run + Given a step \ No newline at end of file diff --git a/compatibility/test-run-exception/test-run-exception.ndjson b/compatibility/test-run-exception/test-run-exception.ndjson new file mode 100644 index 00000000..52988388 --- /dev/null +++ b/compatibility/test-run-exception/test-run-exception.ndjson @@ -0,0 +1,7 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Test run exceptions\n\n Sometimes an exception might happen during the test run, but outside of user-supplied hook\n or step code. This might be bad configuration, a bug in Cucumber, or an environmental issue.\n In such cases, Cucumber will abort the test run and include the exception in the final message.\n\n Scenario: exception during test run\n Given a step","uri":"samples/test-run-exception/test-run-exception.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Test run exceptions","description":" Sometimes an exception might happen during the test run, but outside of user-supplied hook\n or step code. This might be bad configuration, a bug in Cucumber, or an environmental issue.\n In such cases, Cucumber will abort the test run and include the exception in the final message.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"exception during test run","description":"","steps":[{"id":"0","location":{"line":8,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step"}],"examples":[]}}]},"comments":[],"uri":"samples/test-run-exception/test-run-exception.feature"}} +{"pickle":{"id":"3","uri":"samples/test-run-exception/test-run-exception.feature","location":{"line":7,"column":3},"astNodeIds":["1"],"tags":[],"name":"exception during test run","language":"en","steps":[{"id":"2","text":"a step","type":"Context","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step"},"sourceReference":{"uri":"samples/test-run-exception/test-run-exception.ts","location":{"line":3}}}} +{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}} +{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":1000000},"success":false,"message":"Whoops!","exception":{"type":"Error","message":"Whoops!","stackTrace":"Error: Whoops!\n"}}} diff --git a/compatibility/test-run-exception/test-run-exception.ts b/compatibility/test-run-exception/test-run-exception.ts new file mode 100644 index 00000000..4eae9604 --- /dev/null +++ b/compatibility/test-run-exception/test-run-exception.ts @@ -0,0 +1,3 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('a step', () => {}) diff --git a/compatibility/undefined/undefined.feature b/compatibility/undefined/undefined.feature new file mode 100644 index 00000000..c1e19e9a --- /dev/null +++ b/compatibility/undefined/undefined.feature @@ -0,0 +1,20 @@ +Feature: Undefined steps + + At runtime, Cucumber may encounter a step in a scenario that it cannot match to a step definition. + + In these cases, the scenario is not able to run and so the step status will be UNDEFINED, with + subsequent steps being SKIPPED and the overall result will be FAILURE. + + Scenario: An undefined step causes a failure + Given a step that is yet to be defined + + Scenario: Steps before undefined steps are executed + Given an implemented step + And a step that is yet to be defined + + Scenario: Steps after undefined steps are skipped + Given a step that is yet to be defined + And a step that will be skipped + + Scenario: Snippets reflect parameter types + Given a list of 8 things diff --git a/compatibility/undefined/undefined.ndjson b/compatibility/undefined/undefined.ndjson new file mode 100644 index 00000000..57828456 --- /dev/null +++ b/compatibility/undefined/undefined.ndjson @@ -0,0 +1,39 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Undefined steps\n\n At runtime, Cucumber may encounter a step in a scenario that it cannot match to a step definition.\n\n In these cases, the scenario is not able to run and so the step status will be UNDEFINED, with\n subsequent steps being SKIPPED and the overall result will be FAILURE.\n\n Scenario: An undefined step causes a failure\n Given a step that is yet to be defined\n\n Scenario: Steps before undefined steps are executed\n Given an implemented step\n And a step that is yet to be defined\n\n Scenario: Steps after undefined steps are skipped\n Given a step that is yet to be defined\n And a step that will be skipped\n\n Scenario: Snippets reflect parameter types\n Given a list of 8 things\n","uri":"samples/undefined/undefined.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Undefined steps","description":" At runtime, Cucumber may encounter a step in a scenario that it cannot match to a step definition.\n\n In these cases, the scenario is not able to run and so the step status will be UNDEFINED, with\n subsequent steps being SKIPPED and the overall result will be FAILURE.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":8,"column":3},"keyword":"Scenario","name":"An undefined step causes a failure","description":"","steps":[{"id":"0","location":{"line":9,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is yet to be defined"}],"examples":[]}},{"scenario":{"id":"4","tags":[],"location":{"line":11,"column":3},"keyword":"Scenario","name":"Steps before undefined steps are executed","description":"","steps":[{"id":"2","location":{"line":12,"column":5},"keyword":"Given ","keywordType":"Context","text":"an implemented step"},{"id":"3","location":{"line":13,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"a step that is yet to be defined"}],"examples":[]}},{"scenario":{"id":"7","tags":[],"location":{"line":15,"column":3},"keyword":"Scenario","name":"Steps after undefined steps are skipped","description":"","steps":[{"id":"5","location":{"line":16,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is yet to be defined"},{"id":"6","location":{"line":17,"column":5},"keyword":"And ","keywordType":"Conjunction","text":"a step that will be skipped"}],"examples":[]}},{"scenario":{"id":"9","tags":[],"location":{"line":19,"column":3},"keyword":"Scenario","name":"Snippets reflect parameter types","description":"","steps":[{"id":"8","location":{"line":20,"column":5},"keyword":"Given ","keywordType":"Context","text":"a list of 8 things"}],"examples":[]}}]},"comments":[],"uri":"samples/undefined/undefined.feature"}} +{"pickle":{"id":"11","uri":"samples/undefined/undefined.feature","location":{"line":8,"column":3},"astNodeIds":["1"],"tags":[],"name":"An undefined step causes a failure","language":"en","steps":[{"id":"10","text":"a step that is yet to be defined","type":"Context","astNodeIds":["0"]}]}} +{"pickle":{"id":"14","uri":"samples/undefined/undefined.feature","location":{"line":11,"column":3},"astNodeIds":["4"],"tags":[],"name":"Steps before undefined steps are executed","language":"en","steps":[{"id":"12","text":"an implemented step","type":"Context","astNodeIds":["2"]},{"id":"13","text":"a step that is yet to be defined","type":"Context","astNodeIds":["3"]}]}} +{"pickle":{"id":"17","uri":"samples/undefined/undefined.feature","location":{"line":15,"column":3},"astNodeIds":["7"],"tags":[],"name":"Steps after undefined steps are skipped","language":"en","steps":[{"id":"15","text":"a step that is yet to be defined","type":"Context","astNodeIds":["5"]},{"id":"16","text":"a step that will be skipped","type":"Context","astNodeIds":["6"]}]}} +{"pickle":{"id":"19","uri":"samples/undefined/undefined.feature","location":{"line":19,"column":3},"astNodeIds":["9"],"tags":[],"name":"Snippets reflect parameter types","language":"en","steps":[{"id":"18","text":"a list of 8 things","type":"Context","astNodeIds":["8"]}]}} +{"stepDefinition":{"id":"20","pattern":{"type":"CUCUMBER_EXPRESSION","source":"an implemented step"},"sourceReference":{"uri":"samples/undefined/undefined.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"21","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that will be skipped"},"sourceReference":{"uri":"samples/undefined/undefined.ts","location":{"line":7}}}} +{"testRunStarted":{"id":"22","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"23","pickleId":"11","testSteps":[{"id":"24","pickleStepId":"10","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"22"}} +{"testCase":{"id":"25","pickleId":"14","testSteps":[{"id":"26","pickleStepId":"12","stepDefinitionIds":["20"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"27","pickleStepId":"13","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"22"}} +{"testCase":{"id":"28","pickleId":"17","testSteps":[{"id":"29","pickleStepId":"15","stepDefinitionIds":[],"stepMatchArgumentsLists":[]},{"id":"30","pickleStepId":"16","stepDefinitionIds":["21"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"22"}} +{"testCase":{"id":"31","pickleId":"19","testSteps":[{"id":"32","pickleStepId":"18","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"22"}} +{"testCaseStarted":{"id":"33","testCaseId":"23","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"33","testStepId":"24","timestamp":{"seconds":0,"nanos":2000000}}} +{"suggestion":{"id":"34","pickleStepId":"10","snippets":[{"language":"typescript","code":"Given(\"a step that is yet to be defined\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"33","testStepId":"24","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"33","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"35","testCaseId":"25","timestamp":{"seconds":0,"nanos":5000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"35","testStepId":"26","timestamp":{"seconds":0,"nanos":6000000}}} +{"testStepFinished":{"testCaseStartedId":"35","testStepId":"26","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":7000000}}} +{"testStepStarted":{"testCaseStartedId":"35","testStepId":"27","timestamp":{"seconds":0,"nanos":8000000}}} +{"suggestion":{"id":"36","pickleStepId":"13","snippets":[{"language":"typescript","code":"Given(\"a step that is yet to be defined\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"35","testStepId":"27","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":9000000}}} +{"testCaseFinished":{"testCaseStartedId":"35","timestamp":{"seconds":0,"nanos":10000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"37","testCaseId":"28","timestamp":{"seconds":0,"nanos":11000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"37","testStepId":"29","timestamp":{"seconds":0,"nanos":12000000}}} +{"suggestion":{"id":"38","pickleStepId":"15","snippets":[{"language":"typescript","code":"Given(\"a step that is yet to be defined\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"37","testStepId":"29","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":13000000}}} +{"testStepStarted":{"testCaseStartedId":"37","testStepId":"30","timestamp":{"seconds":0,"nanos":14000000}}} +{"testStepFinished":{"testCaseStartedId":"37","testStepId":"30","testStepResult":{"status":"SKIPPED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":15000000}}} +{"testCaseFinished":{"testCaseStartedId":"37","timestamp":{"seconds":0,"nanos":16000000},"willBeRetried":false}} +{"testCaseStarted":{"id":"39","testCaseId":"31","timestamp":{"seconds":0,"nanos":17000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"39","testStepId":"32","timestamp":{"seconds":0,"nanos":18000000}}} +{"suggestion":{"id":"40","pickleStepId":"18","snippets":[{"language":"typescript","code":"Given(\"a list of {int} things\", (int: number) => {\n return \"pending\"\n})"},{"language":"typescript","code":"Given(\"a list of {float} things\", (float: number) => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"39","testStepId":"32","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":19000000}}} +{"testCaseFinished":{"testCaseStartedId":"39","timestamp":{"seconds":0,"nanos":20000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"22","timestamp":{"seconds":0,"nanos":21000000},"success":false}} diff --git a/compatibility/undefined/undefined.ts b/compatibility/undefined/undefined.ts new file mode 100644 index 00000000..3429c26b --- /dev/null +++ b/compatibility/undefined/undefined.ts @@ -0,0 +1,9 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('an implemented step', function () { + // no-op +}) + +Given('a step that will be skipped', function () { + // no-op +}) diff --git a/compatibility/unknown-parameter-type/unknown-parameter-type.feature b/compatibility/unknown-parameter-type/unknown-parameter-type.feature new file mode 100644 index 00000000..1d68f1e6 --- /dev/null +++ b/compatibility/unknown-parameter-type/unknown-parameter-type.feature @@ -0,0 +1,7 @@ +Feature: Parameter Types + Cucumber will generate an error message if a step definition registers + an unknown parameter type, but the suite will run. Additionally, because + the step is effectively undefined, a suggestion will also be created. + + Scenario: undefined parameter type + Given CDG is closed because of a strike diff --git a/compatibility/unknown-parameter-type/unknown-parameter-type.ndjson b/compatibility/unknown-parameter-type/unknown-parameter-type.ndjson new file mode 100644 index 00000000..2c3ceeb9 --- /dev/null +++ b/compatibility/unknown-parameter-type/unknown-parameter-type.ndjson @@ -0,0 +1,13 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Parameter Types\n Cucumber will generate an error message if a step definition registers\n an unknown parameter type, but the suite will run. Additionally, because\n the step is effectively undefined, a suggestion will also be created.\n\n Scenario: undefined parameter type\n Given CDG is closed because of a strike\n","uri":"samples/unknown-parameter-type/unknown-parameter-type.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Parameter Types","description":" Cucumber will generate an error message if a step definition registers\n an unknown parameter type, but the suite will run. Additionally, because\n the step is effectively undefined, a suggestion will also be created.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":6,"column":3},"keyword":"Scenario","name":"undefined parameter type","description":"","steps":[{"id":"0","location":{"line":7,"column":5},"keyword":"Given ","keywordType":"Context","text":"CDG is closed because of a strike"}],"examples":[]}}]},"comments":[],"uri":"samples/unknown-parameter-type/unknown-parameter-type.feature"}} +{"pickle":{"id":"3","uri":"samples/unknown-parameter-type/unknown-parameter-type.feature","location":{"line":6,"column":3},"astNodeIds":["1"],"tags":[],"name":"undefined parameter type","language":"en","steps":[{"id":"2","text":"CDG is closed because of a strike","type":"Context","astNodeIds":["0"]}]}} +{"undefinedParameterType":{"name":"airport","expression":"{airport} is closed because of a strike"}} +{"testRunStarted":{"id":"5","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"6","pickleId":"3","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":[],"stepMatchArgumentsLists":[]}],"testRunStartedId":"5"}} +{"testCaseStarted":{"id":"8","testCaseId":"6","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"seconds":0,"nanos":2000000}}} +{"suggestion":{"id":"9","pickleStepId":"2","snippets":[{"language":"typescript","code":"Given(\"CDG is closed because of a strike\", () => {\n return \"pending\"\n})"}]}} +{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"status":"UNDEFINED","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"5","timestamp":{"seconds":0,"nanos":5000000},"success":false}} diff --git a/compatibility/unknown-parameter-type/unknown-parameter-type.ts b/compatibility/unknown-parameter-type/unknown-parameter-type.ts new file mode 100644 index 00000000..984130a6 --- /dev/null +++ b/compatibility/unknown-parameter-type/unknown-parameter-type.ts @@ -0,0 +1,6 @@ +import { Given } from '@cucumber/fake-cucumber' + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +Given('{airport} is closed because of a strike', function (airport) { + throw new Error('Should not be called because airport parameter type has not been defined') +}) diff --git a/compatibility/unused-steps/unused-steps.feature b/compatibility/unused-steps/unused-steps.feature new file mode 100644 index 00000000..19c48a29 --- /dev/null +++ b/compatibility/unused-steps/unused-steps.feature @@ -0,0 +1,6 @@ +Feature: Unused steps + Depending on the run, some step definitions may not be used. This is valid, and the step definitions are still + includes in the stream of messages, which allows formatters to report on step usage if desired. + + Scenario: a scenario + Given a step that is used diff --git a/compatibility/unused-steps/unused-steps.ndjson b/compatibility/unused-steps/unused-steps.ndjson new file mode 100644 index 00000000..89cc480b --- /dev/null +++ b/compatibility/unused-steps/unused-steps.ndjson @@ -0,0 +1,13 @@ +{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} +{"source":{"data":"Feature: Unused steps\n Depending on the run, some step definitions may not be used. This is valid, and the step definitions are still\n includes in the stream of messages, which allows formatters to report on step usage if desired.\n\n Scenario: a scenario\n Given a step that is used\n","uri":"samples/unused-steps/unused-steps.feature","mediaType":"text/x.cucumber.gherkin+plain"}} +{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Unused steps","description":" Depending on the run, some step definitions may not be used. This is valid, and the step definitions are still\n includes in the stream of messages, which allows formatters to report on step usage if desired.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"a scenario","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step that is used"}],"examples":[]}}]},"comments":[],"uri":"samples/unused-steps/unused-steps.feature"}} +{"pickle":{"id":"3","uri":"samples/unused-steps/unused-steps.feature","location":{"line":5,"column":3},"astNodeIds":["1"],"tags":[],"name":"a scenario","language":"en","steps":[{"id":"2","text":"a step that is used","type":"Context","astNodeIds":["0"]}]}} +{"stepDefinition":{"id":"4","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that is used"},"sourceReference":{"uri":"samples/unused-steps/unused-steps.ts","location":{"line":3}}}} +{"stepDefinition":{"id":"5","pattern":{"type":"CUCUMBER_EXPRESSION","source":"a step that is not used"},"sourceReference":{"uri":"samples/unused-steps/unused-steps.ts","location":{"line":7}}}} +{"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}} +{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}],"testRunStartedId":"6"}} +{"testCaseStarted":{"id":"9","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} +{"testStepStarted":{"testCaseStartedId":"9","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}} +{"testStepFinished":{"testCaseStartedId":"9","testStepId":"8","testStepResult":{"status":"PASSED","duration":{"seconds":0,"nanos":1000000}},"timestamp":{"seconds":0,"nanos":3000000}}} +{"testCaseFinished":{"testCaseStartedId":"9","timestamp":{"seconds":0,"nanos":4000000},"willBeRetried":false}} +{"testRunFinished":{"testRunStartedId":"6","timestamp":{"seconds":0,"nanos":5000000},"success":true}} diff --git a/compatibility/unused-steps/unused-steps.ts b/compatibility/unused-steps/unused-steps.ts new file mode 100644 index 00000000..1d1b8759 --- /dev/null +++ b/compatibility/unused-steps/unused-steps.ts @@ -0,0 +1,9 @@ +import { Given } from '@cucumber/fake-cucumber' + +Given('a step that is used', () => { + // no-op +}) + +Given('a step that is not used', () => { + // no-op +}) From 10137bed28e2085cadb7c3230176d9b8a3f2b296 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 15:40:48 +0000 Subject: [PATCH 007/196] refactored a lot of code --- cucumber_cpp/CMakeLists.txt | 2 - cucumber_cpp/acceptance_test/steps/Steps.cpp | 4 +- cucumber_cpp/devkit/CMakeLists.txt | 1 - cucumber_cpp/devkit/empty/CMakeLists.txt | 6 - .../devkit/empty/features/empty.feature | 11 - .../devkit/empty/features/empty.ndjson | 9 - cucumber_cpp/example/features/2simple.feature | 7 + cucumber_cpp/example/features/3simple.feature | 116 +++++++---- cucumber_cpp/example/features/rule.feature | 6 +- cucumber_cpp/example/features/substep.feature | 4 +- cucumber_cpp/example/features/table.feature | 25 +++ cucumber_cpp/example/hooks/Hooks.cpp | 10 +- cucumber_cpp/example/steps/Steps.cpp | 25 ++- cucumber_cpp/library/Application.hpp | 4 + cucumber_cpp/library/Body.cpp | 8 +- cucumber_cpp/library/Body.hpp | 1 - cucumber_cpp/library/Query.cpp | 36 ++++ cucumber_cpp/library/Query.hpp | 12 ++ cucumber_cpp/library/StepRegistry.cpp | 17 +- cucumber_cpp/library/StepRegistry.hpp | 12 +- cucumber_cpp/library/TraceTime.cpp | 38 ++-- cucumber_cpp/library/TraceTime.hpp | 36 ++-- cucumber_cpp/library/api/RunCucumber.cpp | 22 +- .../library/assemble/AssembleTestSuites.cpp | 23 +- .../cucumber_expression/ParameterRegistry.cpp | 38 ++-- .../cucumber_expression/ParameterRegistry.hpp | 4 + cucumber_cpp/library/engine/Step.cpp | 9 +- cucumber_cpp/library/engine/Step.hpp | 12 +- cucumber_cpp/library/engine/Table.hpp | 3 + .../library/formatter/GetColorFunctions.cpp | 12 -- .../library/formatter/GetColorFunctions.hpp | 16 +- .../library/formatter/PrettyPrinter.cpp | 196 ++++++++++++++++-- .../library/formatter/PrettyPrinter.hpp | 45 +++- .../helper/GherkinDocumentParser.cpp | 24 +++ .../helper/GherkinDocumentParser.hpp | 4 + cucumber_cpp/library/runtime/Coordinator.cpp | 52 ----- cucumber_cpp/library/runtime/Coordinator.hpp | 57 ----- .../library/runtime/TestCaseRunner.cpp | 37 ++-- .../library/runtime/TestCaseRunner.hpp | 48 ----- cucumber_cpp/library/support/Duration.cpp | 21 ++ cucumber_cpp/library/support/Duration.hpp | 27 ++- cucumber_cpp/library/support/Timestamp.cpp | 24 ++- cucumber_cpp/library/support/Timestamp.hpp | 25 ++- cucumber_cpp/runner/CMakeLists.txt | 2 +- 44 files changed, 697 insertions(+), 394 deletions(-) delete mode 100644 cucumber_cpp/devkit/CMakeLists.txt delete mode 100644 cucumber_cpp/devkit/empty/CMakeLists.txt delete mode 100644 cucumber_cpp/devkit/empty/features/empty.feature delete mode 100644 cucumber_cpp/devkit/empty/features/empty.ndjson create mode 100644 cucumber_cpp/example/features/table.feature diff --git a/cucumber_cpp/CMakeLists.txt b/cucumber_cpp/CMakeLists.txt index 0f567251..eca2965c 100644 --- a/cucumber_cpp/CMakeLists.txt +++ b/cucumber_cpp/CMakeLists.txt @@ -18,5 +18,3 @@ target_include_directories(cucumber_cpp INTERFACE target_link_libraries(cucumber_cpp INTERFACE cucumber_cpp.library ) - -add_subdirectory(devkit) diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index 5d126be1..dad2e57f 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -60,8 +60,8 @@ THEN("this should be skipped") GIVEN("Next block of text enclosed in \"\"\" characters") { - - ASSERT_THAT(docString, testing::Eq("Multiline\nDocstring")); + ASSERT_THAT(docString, testing::IsTrue()); + ASSERT_THAT(docString->content, testing::StrEq("Multiline\nDocstring")); } WHEN("this step is being used") diff --git a/cucumber_cpp/devkit/CMakeLists.txt b/cucumber_cpp/devkit/CMakeLists.txt deleted file mode 100644 index 4e40f5f0..00000000 --- a/cucumber_cpp/devkit/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(empty) diff --git a/cucumber_cpp/devkit/empty/CMakeLists.txt b/cucumber_cpp/devkit/empty/CMakeLists.txt deleted file mode 100644 index 19b3509d..00000000 --- a/cucumber_cpp/devkit/empty/CMakeLists.txt +++ /dev/null @@ -1,6 +0,0 @@ -add_executable(cucumber_cpp.devkit.empty ${CCR_EXCLUDE_FROM_ALL}) - -target_link_libraries(cucumber_cpp.devkit.empty PRIVATE - cucumber_cpp - cucumber_cpp.runner -) diff --git a/cucumber_cpp/devkit/empty/features/empty.feature b/cucumber_cpp/devkit/empty/features/empty.feature deleted file mode 100644 index 2adb6d85..00000000 --- a/cucumber_cpp/devkit/empty/features/empty.feature +++ /dev/null @@ -1,11 +0,0 @@ -# language: nl -Functionaliteit: Empty Scenarios - Sometimes we want to quickly jot down a new scenario without specifying any actual steps - for what should be executed. - - In this instance we want to stipulate what should / shouldn't run and what the output is. - - Voorbeeld: Blank Scenario - Stel abc - Wanneer ik dit doe - Dan gebeurt er niets diff --git a/cucumber_cpp/devkit/empty/features/empty.ndjson b/cucumber_cpp/devkit/empty/features/empty.ndjson deleted file mode 100644 index f90ca578..00000000 --- a/cucumber_cpp/devkit/empty/features/empty.ndjson +++ /dev/null @@ -1,9 +0,0 @@ -{"meta":{"protocolVersion":"31.0.0","implementation":{"name":"fake-cucumber","version":"123.45.6"},"cpu":{"name":"arm64"},"os":{"name":"darwin","version":"24.5.0"},"runtime":{"name":"Node.js","version":"24.4.1"},"ci":{"name":"GitHub Actions","url":"https://github.com/cucumber-ltd/shouty.rb/actions/runs/154666429","buildNumber":"154666429","git":{"revision":"99684bcacf01d95875834d87903dcb072306c9ad","remote":"https://github.com/cucumber-ltd/shouty.rb.git","branch":"main"}}}} -{"source":{"data":"Feature: Empty Scenarios\n Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.\n\n Scenario: Blank Scenario\n","uri":"samples/empty/empty.feature","mediaType":"text/x.cucumber.gherkin+plain"}} -{"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Empty Scenarios","description":" Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.","children":[{"scenario":{"id":"0","tags":[],"location":{"line":7,"column":3},"keyword":"Scenario","name":"Blank Scenario","description":"","steps":[],"examples":[]}}]},"comments":[],"uri":"samples/empty/empty.feature"}} -{"pickle":{"id":"1","uri":"samples/empty/empty.feature","location":{"line":7,"column":3},"astNodeIds":["0"],"tags":[],"name":"Blank Scenario","language":"en","steps":[]}} -{"testRunStarted":{"id":"2","timestamp":{"seconds":0,"nanos":0}}} -{"testCase":{"id":"3","pickleId":"1","testSteps":[],"testRunStartedId":"2"}} -{"testCaseStarted":{"id":"4","testCaseId":"3","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} -{"testCaseFinished":{"testCaseStartedId":"4","timestamp":{"seconds":0,"nanos":2000000},"willBeRetried":false}} -{"testRunFinished":{"testRunStartedId":"2","timestamp":{"seconds":0,"nanos":3000000},"success":true}} diff --git a/cucumber_cpp/example/features/2simple.feature b/cucumber_cpp/example/features/2simple.feature index cbb81569..8f5400d2 100644 --- a/cucumber_cpp/example/features/2simple.feature +++ b/cucumber_cpp/example/features/2simple.feature @@ -13,6 +13,13 @@ Feature: Simple feature file When I eat cucumbers Then I should have cucumbers + @ex:1 + Examples: + | x | y | z | + | 10 | 5 | 5 | + | 11 | 3 | 8 | + + @ex:2 Examples: | x | y | z | | 10 | 5 | 5 | diff --git a/cucumber_cpp/example/features/3simple.feature b/cucumber_cpp/example/features/3simple.feature index 3dc56101..25b623ea 100644 --- a/cucumber_cpp/example/features/3simple.feature +++ b/cucumber_cpp/example/features/3simple.feature @@ -5,40 +5,82 @@ Feature: Simple feature file Background: Given a background step - @result:OK @printme - Scenario Outline: Can substract - Given there are cucumbers - | foo | bar | asdasd | ad | - | boz | boo | asd | asdasd | - When I eat cucumbers - Then I should have cucumbers - - Examples: - | x | y | z | - | 10 | 5 | 5 | - | 11 | 3 | 8 | - - @result:FAILED - Scenario Outline: Is a dingus - Given there are cucumbers - When I eat cucumbers - Then I should have cucumbers - And this step should be skipped - | foo | bar | - | boz | boo | - - Examples: - | x | y | z | - | 10 | 4 | 5 | - | 11 | 3 | 6 | - - @result:UNDEFINED - Scenario: a scenario with a missing step - Given there are cucumbers - When I eat cucumbers - Then I should have cucumbers left - And this step should be skipped - Examples: - | x | y | z | - | 10 | 5 | 5 | - | 11 | 3 | 8 | + Rule: Scenarios that have a rule applied + + @result:OK @printme + Scenario Outline: Can substract + Given there are cucumbers + | foo | bar | asdasd | ad | + | boz | boo | asd | asdasd | + When I eat cucumbers + Then I should have cucumbers + + Examples: + | x | y | z | + | 10 | 5 | 5 | + | 11 | 3 | 8 | + + @result:FAILED + Scenario Outline: Is a dingus + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + And this step should be skipped + | foo | bar | + | boz | boo | + + Examples: + | x | y | z | + | 10 | 4 | 5 | + | 11 | 3 | 6 | + + @result:UNDEFINED + Scenario: a scenario with a missing step + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers left + And this step should be skipped + Examples: + | x | y | z | + | 10 | 5 | 5 | + | 11 | 3 | 8 | + + Rule: Scenarios that have another rule applied + + @result:OK @printme + Scenario Outline: Can substract + Given there are cucumbers + | foo | bar | asdasd | ad | + | boz | boo | asd | asdasd | + When I eat cucumbers + Then I should have cucumbers + + Examples: + | x | y | z | + | 10 | 5 | 5 | + | 11 | 3 | 8 | + + @result:FAILED + Scenario Outline: Is a dingus + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers + And this step should be skipped + | foo | bar | + | boz | boo | + + Examples: + | x | y | z | + | 10 | 4 | 5 | + | 11 | 3 | 6 | + + @result:UNDEFINED + Scenario: a scenario with a missing step + Given there are cucumbers + When I eat cucumbers + Then I should have cucumbers left + And this step should be skipped + Examples: + | x | y | z | + | 10 | 5 | 5 | + | 11 | 3 | 8 | diff --git a/cucumber_cpp/example/features/rule.feature b/cucumber_cpp/example/features/rule.feature index 5b54bbef..50d4ff2a 100644 --- a/cucumber_cpp/example/features/rule.feature +++ b/cucumber_cpp/example/features/rule.feature @@ -1,12 +1,16 @@ +@thishasarule Feature: Feature with a rule In order to group related scenarios and rules As a Cucumber user I want a clear, human-readable feature description + Scenario: Scenario without a rule + Given a step + Rule: Example rule In order to demonstrate rules in Cucumber As a developer I want to see how rules work in feature files - Scenario Outline: Scenario under a rule + Scenario: Scenario under a rule Given a step diff --git a/cucumber_cpp/example/features/substep.feature b/cucumber_cpp/example/features/substep.feature index e3098254..5a604b91 100644 --- a/cucumber_cpp/example/features/substep.feature +++ b/cucumber_cpp/example/features/substep.feature @@ -1,4 +1,6 @@ +@substep Feature: Feature with a step that will call another step Scenario: Scenario under a rule - Given call another step + When a step calls another step + Then the called step is executed diff --git a/cucumber_cpp/example/features/table.feature b/cucumber_cpp/example/features/table.feature new file mode 100644 index 00000000..f0c30671 --- /dev/null +++ b/cucumber_cpp/example/features/table.feature @@ -0,0 +1,25 @@ +@table_argument +Feature: Steps can access step table arguments + + Scenario Outline: Step with table argument can access table data + """ + you can have a docstring + """ + When a step stores the value at row and column from the table: + | name | age | city | + | Alice | 30 | New York | + | Bob | 25 | Los Angeles | + | Charlie | 35 | Chicago | + Then the value should be "" + + Examples: + | row | column | expected_value | + | 1 | 0 | Alice | + | 1 | 1 | 30 | + | 1 | 2 | New York | + | 2 | 0 | Bob | + | 2 | 1 | 25 | + | 2 | 2 | Los Angeles | + | 3 | 0 | Charlie | + | 3 | 1 | 35 | + | 3 | 2 | Chicago | diff --git a/cucumber_cpp/example/hooks/Hooks.cpp b/cucumber_cpp/example/hooks/Hooks.cpp index da66a4a3..f4d30d37 100644 --- a/cucumber_cpp/example/hooks/Hooks.cpp +++ b/cucumber_cpp/example/hooks/Hooks.cpp @@ -14,30 +14,24 @@ HOOK_BEFORE_ALL() HOOK_BEFORE_SCENARIO("@dingus") { - std::cout << "running only for dingus tests\n"; } -HOOK_BEFORE_SCENARIO() +HOOK_BEFORE_SCENARIO("@result:OK") { - std::cout << "running before every test case\n"; } -HOOK_BEFORE_SCENARIO("@result:OK") +HOOK_BEFORE_SCENARIO() { - std::cout << "running only for result:OK test cases\n"; } HOOK_AFTER_SCENARIO() { - std::cout << "running after every test case\n"; } HOOK_BEFORE_STEP() { - std::cout << "running before every test step\n"; } HOOK_AFTER_STEP() { - std::cout << "running after every test step\n"; } diff --git a/cucumber_cpp/example/steps/Steps.cpp b/cucumber_cpp/example/steps/Steps.cpp index 4e422292..36135f9f 100644 --- a/cucumber_cpp/example/steps/Steps.cpp +++ b/cucumber_cpp/example/steps/Steps.cpp @@ -8,7 +8,6 @@ GIVEN(R"(a background step)") { - std::cout << "this is a background step\n"; } GIVEN(R"(a simple data table)") @@ -76,17 +75,35 @@ STEP(R"(a data table with comments and newlines inside)") /* no body, example only */ } -STEP(R"(a step)") +STEP(R"(^a step$)") { - std::cout << "--a step--\n"; + context.EmplaceAt("substep", "was executed"); } -STEP(R"(call another step)") +STEP(R"(^a step calls another step$)") { Given(R"(a step)"); } +STEP(R"(^the called step is executed$)") +{ + ASSERT_THAT(context.Contains("substep"), testing::IsTrue()); + ASSERT_THAT(context.Get("substep"), testing::Eq("was executed")); +} + STEP("this step should be skipped") { /* no body, example only */ } + +STEP(R"(a step stores the value at row {int} and column {int} from the table:)", (std::int32_t row, std::int32_t column)) +{ + context.EmplaceAt("cell", table.value()[row].cells[column].value); +} + +STEP(R"(the value should be {string})", (const std::string& expected_value)) +{ + const auto& actual = context.Get("cell"); + + ASSERT_THAT(actual, testing::StrEq(expected_value)); +} diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 0a448e88..9478cc6f 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -7,6 +7,8 @@ #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/Duration.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include @@ -68,6 +70,8 @@ namespace cucumber_cpp::library cucumber_expression::ParameterRegistry parameterRegistry{}; bool removeDefaultGoogleTestListener; + support::StopWatchHighResolutionClock stopwatchHighResolutionClock; + support::TimestampGeneratorSystemClock timestampGeneratorSystemClock; }; } diff --git a/cucumber_cpp/library/Body.cpp b/cucumber_cpp/library/Body.cpp index 4363485e..d8eae73f 100644 --- a/cucumber_cpp/library/Body.cpp +++ b/cucumber_cpp/library/Body.cpp @@ -2,7 +2,7 @@ #include "cucumber/messages/exception.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/TraceTime.hpp" +#include "cucumber_cpp/library/support/Duration.hpp" #include "gtest/gtest.h" #include #include @@ -46,13 +46,12 @@ namespace cucumber_cpp::library cucumber::messages::test_step_result Body::ExecuteAndCatchExceptions(const ExecuteArgs& args) { - TraceTime traceTime; cucumber::messages::test_step_result testStepResult{ .status = cucumber::messages::test_step_result_status::PASSED }; EventListener eventListener{ testStepResult }; try { - traceTime.Start(); + support::Stopwatch::Instance().Start(); Execute(args); } catch (const FatalError& error) @@ -76,8 +75,7 @@ namespace cucumber_cpp::library }; } - auto delta = traceTime.Delta(); - auto nanoseconds = std::chrono::duration_cast(delta); + auto nanoseconds = support::Stopwatch::Instance().Duration(); static constexpr std::size_t nanosecondsPerSecond = 1e9; testStepResult.duration = { .seconds = static_cast(nanoseconds.count() / static_cast(nanosecondsPerSecond)), diff --git a/cucumber_cpp/library/Body.hpp b/cucumber_cpp/library/Body.hpp index a2fb4f69..9f3372a9 100644 --- a/cucumber_cpp/library/Body.hpp +++ b/cucumber_cpp/library/Body.hpp @@ -4,7 +4,6 @@ #include "cucumber/messages/exception.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/TraceTime.hpp" #include #include #include diff --git a/cucumber_cpp/library/Query.cpp b/cucumber_cpp/library/Query.cpp index d64af71b..2afd084a 100644 --- a/cucumber_cpp/library/Query.cpp +++ b/cucumber_cpp/library/Query.cpp @@ -6,6 +6,7 @@ #include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/hook.hpp" +#include "cucumber/messages/location.hpp" #include "cucumber/messages/meta.hpp" #include "cucumber/messages/parameter_type.hpp" #include "cucumber/messages/pickle.hpp" @@ -182,26 +183,61 @@ namespace cucumber_cpp::library return parameterTypeByName.contains(name); } + const cucumber::messages::test_case& Query::FindTestCaseBy(const cucumber::messages::test_case_started& testCaseStarted) const + { + return testCaseById.at(testCaseStarted.test_case_id); + } + const cucumber::messages::test_case& Query::FindTestCaseById(const std::string& id) const { return testCaseById.at(id); } + const cucumber::messages::pickle& Query::FindPickleBy(const cucumber::messages::test_case_started& testCaseStarted) const + { + const auto& testCase = FindTestCaseById(testCaseStarted.test_case_id); + return FindPickleById(testCase.pickle_id); + } + const cucumber::messages::pickle& Query::FindPickleById(const std::string& id) const { return pickleById.at(id); } + const cucumber::messages::pickle_step* Query::FindPickleStepBy(const cucumber::messages::test_step& testStep) const + { + if (!testStep.pickle_step_id.has_value()) + return nullptr; + return &pickleStepById.at(testStep.pickle_step_id.value()); + } + const cucumber::messages::pickle_step& Query::FindPickleStepById(const std::string& id) const { return pickleStepById.at(id); } + const cucumber::messages::test_step& Query::FindTestStepBy(const cucumber::messages::test_step_finished& testStepFinished) const + { + return testStepById.at(testStepFinished.test_step_id); + } + + const cucumber::messages::step& Query::FindStepBy(const cucumber::messages::pickle_step& pickleStep) const + { + return stepById.at(pickleStep.ast_node_ids[0]); + } + const cucumber::messages::step_definition& Query::FindStepDefinitionById(const std::string& id) const { return stepDefinitionById.at(id); } + const cucumber::messages::location& Query::FindLocationOf(const cucumber::messages::pickle& pickle) const + { + const auto& lineage = FindLineageByUri(pickle.uri); + // if (lineage.examples) + return lineage.scenario->location; + } + const std::map& Query::TestCaseStarted() const { return testCaseStartedById; diff --git a/cucumber_cpp/library/Query.hpp b/cucumber_cpp/library/Query.hpp index da0f4310..9202fec7 100644 --- a/cucumber_cpp/library/Query.hpp +++ b/cucumber_cpp/library/Query.hpp @@ -7,6 +7,7 @@ #include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/hook.hpp" +#include "cucumber/messages/location.hpp" #include "cucumber/messages/meta.hpp" #include "cucumber/messages/parameter_type.hpp" #include "cucumber/messages/pickle.hpp" @@ -82,13 +83,24 @@ namespace cucumber_cpp::library const cucumber::messages::parameter_type& FindParameterTypeByName(const std::string& name) const; bool ContainsParameterTypeByName(const std::string& name) const; + const cucumber::messages::test_case& FindTestCaseBy(const cucumber::messages::test_case_started& testCaseStarted) const; const cucumber::messages::test_case& FindTestCaseById(const std::string& id) const; + const cucumber::messages::pickle& FindPickleBy(const cucumber::messages::test_case_started& testCaseStarted) const; const cucumber::messages::pickle& FindPickleById(const std::string& id) const; + + const cucumber::messages::pickle_step* FindPickleStepBy(const cucumber::messages::test_step& testStep) const; const cucumber::messages::pickle_step& FindPickleStepById(const std::string& id) const; + const cucumber::messages::test_step& FindTestStepBy(const cucumber::messages::test_step_finished& testStepFinished) const; + + const cucumber::messages::step& + FindStepBy(const cucumber::messages::pickle_step& pickleStep) const; + const cucumber::messages::step_definition& FindStepDefinitionById(const std::string& id) const; + const cucumber::messages::location& FindLocationOf(const cucumber::messages::pickle& pickle) const; + const std::map& TestCaseStarted() const; const std::map& TestCaseFinishedByTestCaseStartedId() const; diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index 33d42193..cc6d339d 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -1,23 +1,14 @@ #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/group.hpp" -#include "cucumber/messages/location.hpp" -#include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" -#include "cucumber_cpp/library/Body.hpp" -#include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/RegularExpression.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" #include #include -#include #include #include #include @@ -31,6 +22,10 @@ namespace cucumber_cpp::library StepRegistry::StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, cucumber::gherkin::id_generator_ptr idGenerator) : parameterRegistry{ parameterRegistry } , idGenerator{ idGenerator } + { + } + + void StepRegistry::LoadSteps() { for (const auto& matcher : StepStringRegistration::Instance().GetEntries()) Register(matcher.regex, matcher.type, matcher.factory, matcher.sourceLocation); @@ -115,7 +110,7 @@ namespace cucumber_cpp::library { registry.emplace(id, Definition{ factory, - "id", + id, sourceLocation.line(), sourceLocation.file_name(), stepType, @@ -131,7 +126,7 @@ namespace cucumber_cpp::library { registry.emplace(id, Definition{ factory, - "id", + id, sourceLocation.line(), sourceLocation.file_name(), stepType, diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index ab681d91..7c58383b 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -2,16 +2,15 @@ #define CUCUMBER_CPP_STEPREGISTRY_HPP #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/pickle_doc_string.hpp" +#include "cucumber/messages/pickle_table_row.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" #include #include #include @@ -19,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -30,10 +30,10 @@ namespace cucumber_cpp::library { - using StepFactory = std::unique_ptr (&)(Context&, const engine::Table&, const std::string&); + using StepFactory = std::unique_ptr (&)(Context&, std::optional>, const std::optional&); template - std::unique_ptr StepBodyFactory(Context& context, const engine::Table& table, const std::string& docString) + std::unique_ptr StepBodyFactory(Context& context, std::optional> table, const std::optional& docString) { return std::make_unique(context, table, docString); } @@ -98,6 +98,8 @@ namespace cucumber_cpp::library explicit StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, cucumber::gherkin::id_generator_ptr idGenerator); + void LoadSteps(); + [[nodiscard]] StepMatch Query(const std::string& expression); [[nodiscard]] std::pair, std::vector> FindDefinitions(const std::string& expression); diff --git a/cucumber_cpp/library/TraceTime.cpp b/cucumber_cpp/library/TraceTime.cpp index 722076c5..8ae3b71c 100644 --- a/cucumber_cpp/library/TraceTime.cpp +++ b/cucumber_cpp/library/TraceTime.cpp @@ -1,23 +1,23 @@ -#include "cucumber_cpp/library/TraceTime.hpp" -#include +// #include "cucumber_cpp/library/TraceTime.hpp" +// #include -namespace cucumber_cpp::library -{ - void TraceTime::Start() - { - timeStart = std::chrono::high_resolution_clock::now(); - } +// namespace cucumber_cpp::library +// { +// void TraceTime::Start() +// { +// timeStart = std::chrono::high_resolution_clock::now(); +// } - void TraceTime::Stop() - { - timeStop = std::chrono::high_resolution_clock::now(); - } +// void TraceTime::Stop() +// { +// timeStop = std::chrono::high_resolution_clock::now(); +// } - TraceTime::Duration TraceTime::Delta() const - { - if (timeStop != TimePoint{}) - return timeStop - timeStart; +// TraceTime::Duration TraceTime::Delta() const +// { +// if (timeStop != TimePoint{}) +// return timeStop - timeStart; - return std::chrono::high_resolution_clock::now() - timeStart; - } -} +// return std::chrono::high_resolution_clock::now() - timeStart; +// } +// } diff --git a/cucumber_cpp/library/TraceTime.hpp b/cucumber_cpp/library/TraceTime.hpp index 9e5c5f54..815217fd 100644 --- a/cucumber_cpp/library/TraceTime.hpp +++ b/cucumber_cpp/library/TraceTime.hpp @@ -1,24 +1,24 @@ -#ifndef CUCUMBER_CPP_TRACETIME_HPP -#define CUCUMBER_CPP_TRACETIME_HPP +// #ifndef CUCUMBER_CPP_TRACETIME_HPP +// #define CUCUMBER_CPP_TRACETIME_HPP -#include +// #include -namespace cucumber_cpp::library -{ - struct TraceTime - { - using TimePoint = std::chrono::time_point; - using Duration = TimePoint::duration; +// namespace cucumber_cpp::library +// { +// struct TraceTime +// { +// using TimePoint = std::chrono::time_point; +// using Duration = TimePoint::duration; - void Start(); - void Stop(); +// void Start(); +// void Stop(); - [[nodiscard]] Duration Delta() const; +// [[nodiscard]] Duration Delta() const; - private: - TimePoint timeStart{}; - TimePoint timeStop{}; - }; -} +// private: +// TimePoint timeStart{}; +// TimePoint timeStop{}; +// }; +// } -#endif +// #endif diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 8423c9a5..a08d8893 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/location.hpp" #include "cucumber/messages/parameter_type.hpp" #include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/step_definition_pattern.hpp" @@ -16,6 +17,7 @@ #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include #include #include @@ -28,6 +30,9 @@ namespace cucumber_cpp::library::api { for (const auto& [name, parameter] : supportCodeLibrary.parameterRegistry.GetParameters()) { + if (parameter.isBuiltin) + continue; + broadcaster.BroadcastEvent({ .parameter_type = cucumber::messages::parameter_type{ .name = parameter.name, .regular_expressions = parameter.regex, @@ -38,6 +43,8 @@ namespace cucumber_cpp::library::api void EmitStepDefinitions(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { + supportCodeLibrary.stepRegistry.LoadSteps(); + for (const auto& [name, stepDefinition] : supportCodeLibrary.stepRegistry.StepDefinitions()) { broadcaster.BroadcastEvent({ .step_definition = cucumber::messages::step_definition{ @@ -46,6 +53,12 @@ namespace cucumber_cpp::library::api .source = stepDefinition.pattern, .type = stepDefinition.patternType, }, + .source_reference = { + .uri = stepDefinition.uri, + .location = cucumber::messages::location{ + .line = stepDefinition.line, + }, + }, } }); } } @@ -87,6 +100,13 @@ namespace cucumber_cpp::library::api auto runtime = runtime::MakeRuntime(options.runtime, broadcaster, filteredPickles, supportCodeLibrary, idGenerator, programContext); - return runtime->Run(); + auto& listeners = testing::UnitTest::GetInstance()->listeners(); + auto* defaultEventListener = listeners.Release(listeners.default_result_printer()); + + auto result = runtime->Run(); + + listeners.Append(defaultEventListener); + + return result; } } diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 753e98ba..641877c2 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -32,11 +32,17 @@ namespace cucumber_cpp::library::assemble for (const auto& pickleSource : sourcedPickles) { - std::vector allSteps; - allSteps.reserve(pickleSource.pickle->steps.size() * 2); // steps + hooks + cucumber::messages::test_case testCase{ + .id = idGenerator->next_id(), + .pickle_id = pickleSource.pickle->id, + .test_steps = {}, + .test_run_started_id = std::make_optional(testRunStartedId) + }; + + testCase.test_steps.reserve(pickleSource.pickle->steps.size() * 2); // steps + hooks for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::before, pickleSource.pickle->tags)) - allSteps.emplace_back(hookId, idGenerator->next_id()); + testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); for (const auto& step : pickleSource.pickle->steps) { @@ -57,7 +63,7 @@ namespace cucumber_cpp::library::assemble } } - allSteps.emplace_back( + testCase.test_steps.emplace_back( std::nullopt, idGenerator->next_id(), step.id, @@ -66,14 +72,7 @@ namespace cucumber_cpp::library::assemble } for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags)) - allSteps.emplace_back(hookId, idGenerator->next_id()); - - cucumber::messages::test_case testCase{ - .id = idGenerator->next_id(), - .pickle_id = pickleSource.pickle->id, - .test_steps = std::move(allSteps), - .test_run_started_id = std::make_optional(testRunStartedId) - }; + testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); broadcaster.BroadcastEvent(cucumber::messages::envelope{ .test_case = testCase }); diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index 4d83c8d1..15ff8818 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -64,20 +64,20 @@ namespace cucumber_cpp::library::cucumber_expression const static std::string stringSingleRegex{ R"__('([^'\\]*(\\.[^'\\]*)*)')__" }; const static std::string wordRegex{ R"__([^\s]+)__" }; - AddParameter("int", { integerNegativeRegex, integerPositiveRegex }, CreateStreamConverter()); - AddParameter("float", { floatRegex }, CreateStreamConverter()); - AddParameter("word", { wordRegex }, CreateStreamConverter()); - AddParameter("string", { stringDoubleRegex, stringSingleRegex }, CreateStringConverter()); - AddParameter("", { ".*" }, CreateStreamConverter()); - AddParameter("bigdecimal", { floatRegex }, CreateStreamConverter()); - AddParameter("biginteger", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); - AddParameter("byte", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); - AddParameter("short", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); - AddParameter("long", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); - AddParameter("double", { floatRegex }, CreateStreamConverter()); + AddBuiltinParameter("int", { integerNegativeRegex, integerPositiveRegex }, CreateStreamConverter()); + AddBuiltinParameter("float", { floatRegex }, CreateStreamConverter()); + AddBuiltinParameter("word", { wordRegex }, CreateStreamConverter()); + AddBuiltinParameter("string", { stringDoubleRegex, stringSingleRegex }, CreateStringConverter()); + AddBuiltinParameter("", { ".*" }, CreateStreamConverter()); + AddBuiltinParameter("bigdecimal", { floatRegex }, CreateStreamConverter()); + AddBuiltinParameter("biginteger", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); + AddBuiltinParameter("byte", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); + AddBuiltinParameter("short", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); + AddBuiltinParameter("long", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); + AddBuiltinParameter("double", { floatRegex }, CreateStreamConverter()); // extension - AddParameter("bool", { wordRegex }, CreateStreamConverter()); + AddBuiltinParameter("bool", { wordRegex }, CreateStreamConverter()); } const std::map& ParameterRegistry::GetParameters() const @@ -92,6 +92,17 @@ namespace cucumber_cpp::library::cucumber_expression void ParameterRegistry::AddParameter(std::string name, std::vector regex, ParameterConversion converter) { + AddParameter(name, regex, converter, false); + } + + void ParameterRegistry::AddBuiltinParameter(std::string name, std::vector regex, ParameterConversion converter) + { + AddParameter(name, regex, converter, true); + } + + void ParameterRegistry::AddParameter(std::string name, std::vector regex, ParameterConversion converter, bool isBuiltin) + { + if (parametersByName.contains(name)) { if (name.empty()) @@ -100,7 +111,6 @@ namespace cucumber_cpp::library::cucumber_expression throw CucumberExpressionError{ std::format("There is already a parameter with name {}", name) }; } - parametersByName.emplace(name, Parameter{ name, regex, converter }); + parametersByName.emplace(name, Parameter{ name, regex, converter, isBuiltin }); } - } diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp index 53c5cdad..a5f45451 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp @@ -114,6 +114,7 @@ namespace cucumber_cpp::library::cucumber_expression std::string name; std::vector regex; ParameterConversion converter; + bool isBuiltin{ false }; }; struct ParameterRegistration @@ -138,6 +139,9 @@ namespace cucumber_cpp::library::cucumber_expression void AddParameter(std::string name, std::vector regex, ParameterConversion converter) override; private: + void AddBuiltinParameter(std::string name, std::vector regex, ParameterConversion converter); + void AddParameter(std::string name, std::vector regex, ParameterConversion converter, bool isBuiltin); + std::map parametersByName; }; } diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index 6d75cc47..f5c2c24b 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -1,14 +1,17 @@ #include "cucumber_cpp/library/engine/Step.hpp" -#include "cucumber/messages/pickle_step_type.hpp" +#include "cucumber/messages/doc_string.hpp" +#include "cucumber/messages/pickle_table_row.hpp" #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" +#include +#include #include +#include #include namespace cucumber_cpp::library::engine { - Step::Step(Context& context, const Table& table, const std::string& docString) + Step::Step(Context& context, std::optional> table, const std::optional& docString) : context{ context } , table{ table } , docString{ docString } diff --git a/cucumber_cpp/library/engine/Step.hpp b/cucumber_cpp/library/engine/Step.hpp index de95fcd2..296a1940 100644 --- a/cucumber_cpp/library/engine/Step.hpp +++ b/cucumber_cpp/library/engine/Step.hpp @@ -4,10 +4,14 @@ // IWYU pragma: private, include "cucumber_cpp/CucumberCpp.hpp" // IWYU pragma: friend cucumber_cpp/.* +#include "cucumber/messages/pickle_doc_string.hpp" +#include "cucumber/messages/pickle_table_row.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" #include +#include +#include #include +#include #include #include @@ -27,7 +31,7 @@ namespace cucumber_cpp::library::engine std::source_location sourceLocation; }; - Step(Context& context, const Table& table, const std::string& docString); + Step(Context& context, std::optional> table, const std::optional& docString); virtual ~Step() = default; virtual void SetUp() @@ -48,8 +52,8 @@ namespace cucumber_cpp::library::engine [[noreturn]] static void Pending(const std::string& message, std::source_location current = std::source_location::current()) noexcept(false); Context& context; - const Table& table; - const std::string& docString; + std::optional> table; + const std::optional& docString; }; } diff --git a/cucumber_cpp/library/engine/Table.hpp b/cucumber_cpp/library/engine/Table.hpp index 4df18c47..820a97b5 100644 --- a/cucumber_cpp/library/engine/Table.hpp +++ b/cucumber_cpp/library/engine/Table.hpp @@ -1,7 +1,10 @@ #ifndef ENGINE_TABLE_HPP #define ENGINE_TABLE_HPP +#include "cucumber/messages/pickle_table_cell.hpp" +#include "cucumber/messages/pickle_table_row.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" +#include #include #include #include diff --git a/cucumber_cpp/library/formatter/GetColorFunctions.cpp b/cucumber_cpp/library/formatter/GetColorFunctions.cpp index 06f2d31e..5fe4bbf1 100644 --- a/cucumber_cpp/library/formatter/GetColorFunctions.cpp +++ b/cucumber_cpp/library/formatter/GetColorFunctions.cpp @@ -70,16 +70,4 @@ namespace cucumber_cpp::library::formatter { return ColorString(sv); } - - ColorFunctions GetColorFunctions(bool useColors) - { - std::string str; - - str += Term::color_fg(Term::Color::Name::Red); - str += "test"; - - // str += rang::fg::red; - - return {}; - } } diff --git a/cucumber_cpp/library/formatter/GetColorFunctions.hpp b/cucumber_cpp/library/formatter/GetColorFunctions.hpp index 6dac98fb..3d498484 100644 --- a/cucumber_cpp/library/formatter/GetColorFunctions.hpp +++ b/cucumber_cpp/library/formatter/GetColorFunctions.hpp @@ -10,16 +10,14 @@ namespace cucumber_cpp::library::formatter { struct ColorFunctions { - std::function ForStatus(cucumber::messages::test_step_result_status status); - std::string Location(std::string_view); - std::string Tag(std::string_view); - std::string DiffAdded(std::string_view); - std::string DiffRemoved(std::string_view); - std::string ErrorMessage(std::string_view); - std::string ErrorStack(std::string_view); + static std::function ForStatus(cucumber::messages::test_step_result_status status); + static std::string Location(std::string_view); + static std::string Tag(std::string_view); + static std::string DiffAdded(std::string_view); + static std::string DiffRemoved(std::string_view); + static std::string ErrorMessage(std::string_view); + static std::string ErrorStack(std::string_view); }; - - ColorFunctions GetColorFunctions(bool useColors); } #endif diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyPrinter.cpp index dfcb8d2d..7c05b2d3 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.cpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.cpp @@ -1,43 +1,195 @@ #include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" +#include "cucumber/messages/attachment.hpp" #include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/pickle_tag.hpp" +#include "cucumber/messages/rule.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/test_case_started.hpp" -#include "cucumber_cpp/library/formatter/Formatter.hpp" -#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" -#include "cucumber_cpp/library/formatter/helper/PickleParser.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" +#include "cucumber_cpp/library/support/Join.hpp" +#include "cucumber_cpp/library/support/Polyfill.hpp" #include +#include +#include +#include +#include +#include +#include +#include +#include +#include namespace cucumber_cpp::library::formatter { + namespace + { + std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario) + { + return std::format("{}: {}", scenario.keyword, pickle.name); + } + + std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step) + { + return std::format("{}{}", step.keyword, pickleStep.text); + } + } + void PrettyPrinter::OnEnvelope(const cucumber::messages::envelope& envelope) { + query += envelope; + if (envelope.test_case_started) { CalculateIndent(envelope.test_case_started.value()); + HandleTestCaseStarted(envelope.test_case_started.value()); + } + + if (envelope.test_step_finished) + { + HandleTestStepFinished(envelope.test_step_finished.value()); } } void PrettyPrinter::CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted) { - const auto& testCase = eventDataCollector.GetTestCase(testCaseStarted.test_case_id); - const auto& pickle = eventDataCollector.GetPickle(testCase.pickle_id); - const auto scenarioMap = helper::GetGherkinScenarioMap(eventDataCollector.GetGherkinDocument(pickle.uri)); - const auto pickleStepMap = helper::GetPickleStepMap(pickle); - const auto& scenario = scenarioMap.at(pickle.ast_node_ids[0]); - - const auto maxScenarioLength = scenario.name.length(); - auto maxStepLength = std::size_t{ 0 }; - for (const auto& testStep : testCase.test_steps) - { - if (testStep.pickle_step_id) - { - const auto& pickleStep = pickleStepMap.at(testStep.pickle_step_id.value()); - if (pickleStep.text.length() > maxStepLength) - { - maxStepLength = pickleStep.text.length(); - } - } + const auto& pickle = query.FindPickleBy(testCaseStarted); + const auto& lineage = query.FindLineageByPickle(pickle); + const auto& scenario = *lineage.scenario; + const auto scenarioLength = FormatPickleTitle(pickle, scenario).length(); + + const auto& testCase = query.FindTestCaseBy(testCaseStarted); + + const auto hasPickleStepId = [](const cucumber::messages::test_step& testStep) + { + return testStep.pickle_step_id.has_value(); + }; + const auto toLength = [this](const cucumber::messages::test_step& testStep) + { + const auto* pickleStep = query.FindPickleStepBy(testStep); + const auto& step = query.FindStepBy(*pickleStep); + return FormatStepTitle(testStep, *pickleStep, step).length(); + }; + + auto steplengths = testCase.test_steps | std::views::filter(hasPickleStepId) | std::views::transform(toLength); + const auto maxStepLengthIter = std::ranges::max_element(steplengths); + const auto maxStepLength = (maxStepLengthIter != steplengths.end()) ? *maxStepLengthIter : 0; + + maxContentLengthByTestCaseStartedId[testCaseStarted.id] = std::max(scenarioLength, maxStepLength); + + std::size_t scenarioIndent{ 0 }; + scenarioIndent += 2; + if (lineage.rule) + scenarioIndent += 2; + scenarioIndentByTestCaseStartedId[testCaseStarted.id] = scenarioIndent; + } + + void PrettyPrinter::HandleTestCaseStarted(const cucumber::messages::test_case_started& testCaseStarted) + { + const auto& pickle = query.FindPickleBy(testCaseStarted); + const auto& location = query.FindLocationOf(pickle); + const auto& lineage = query.FindLineageByPickle(pickle); + const auto& scenario = lineage.scenario; + const auto& rule = lineage.rule; + const auto& feature = lineage.feature; + + const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(testCaseStarted.id); + const auto maxContentLength = maxContentLengthByTestCaseStartedId.at(testCaseStarted.id); + + PrintFeatureLine(*feature); + if (rule) + PrintRuleLine(*rule); + outputStream << "\n"; + PrintTags(pickle, scenarioIndent); + PrintScenarioLine(pickle, *scenario, scenarioIndent, maxContentLength); + } + + void PrettyPrinter::HandleAttachment(const cucumber::messages::attachment& attachment) + {} + + void PrettyPrinter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) + { + const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(testStepFinished.test_case_started_id); + const auto maxContentLength = maxContentLengthByTestCaseStartedId.at(testStepFinished.test_case_started_id); + + const auto& testStep = query.FindTestStepBy(testStepFinished); + const auto* pickleStep = query.FindPickleStepBy(testStep); + + if (pickleStep != nullptr) + { + const auto& step = query.FindStepBy(*pickleStep); + const auto* stepDefinition = (testStep.step_definition_ids && !testStep.step_definition_ids->empty()) ? &query.FindStepDefinitionById(testStep.step_definition_ids->front()) : nullptr; + + PrintStepLine(testStepFinished, testStep, *pickleStep, step, stepDefinition, scenarioIndent, maxContentLength); + } + } + + void PrettyPrinter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) + {} + + void PrettyPrinter::PrintFeatureLine(const cucumber::messages::feature& feature) + { + if (printedFeatureUris.contains(&feature)) + return; + + support::print(outputStream, "{}: {}\n", feature.keyword, feature.name); + printedFeatureUris.insert(&feature); + } + + void PrettyPrinter::PrintRuleLine(const cucumber::messages::rule& rule) + { + if (printedRuleIds.contains(&rule)) + return; + + support::print(outputStream, "{:{}}{}: {}\n", "", 2, rule.keyword, rule.name); + printedRuleIds.insert(&rule); + } + + void PrettyPrinter::PrintTags(const cucumber::messages::pickle& pickle, std::size_t scenarioIndent) + { + if (pickle.tags.empty()) + return; + + auto tags = pickle.tags | std::views::transform([](const cucumber::messages::pickle_tag& tag) + { + return tag.name; + }); + std::vector tagVec{ tags.begin(), tags.end() }; + support::print(outputStream, "{:{}}{}\n", "", scenarioIndent, ColorFunctions::Tag(support::Join(tagVec, " "))); + } + + void PrettyPrinter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) + { + PrintGherkinLine(std::format("{}: {}", scenario.keyword, pickle.name), nullptr, pickle.uri, scenario.location.line, scenarioIndent, maxContentLength); + } + + void PrettyPrinter::PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength) + { + const auto uri = stepDefinition ? std::make_optional(*stepDefinition->source_reference.uri) : std::nullopt; + const auto line = stepDefinition ? std::make_optional(stepDefinition->source_reference.location->line) : std::nullopt; + + PrintGherkinLine(std::format("{}{}", step.keyword, pickleStep.text), ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); + } + + void PrettyPrinter::PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) + { + if (title.length() > maxContentLength) + { + std::abort(); } + const auto padding = maxContentLength - title.length(); - scenarioIndent = std::max(maxScenarioLength, maxStepLength) + 2; + if (uri && line) + support::print(outputStream, "{:{}}{}{:{}} {}\n", "", indent, formatTitle ? formatTitle(title) : std::string(title), "", padding, ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); + else + support::print(outputStream, "{:{}}{}\n", "", indent, formatTitle ? formatTitle(title) : std::string(title)); } } diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.hpp b/cucumber_cpp/library/formatter/PrettyPrinter.hpp index 43175f85..6123bb77 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.hpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.hpp @@ -1,10 +1,30 @@ #ifndef FORMATTER_PRETTY_PRINTER_HPP #define FORMATTER_PRETTY_PRINTER_HPP +#include "cucumber/messages/attachment.hpp" #include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/rule.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" #include +#include +#include +#include +#include +#include +#include +#include namespace cucumber_cpp::library::formatter { @@ -14,11 +34,34 @@ namespace cucumber_cpp::library::formatter using Formatter::Formatter; private: + bool eventDataCollector{ false }; + void OnEnvelope(const cucumber::messages::envelope& envelope) override; void CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted); - std::size_t scenarioIndent{ 0 }; + void HandleTestCaseStarted(const cucumber::messages::test_case_started& testCaseStarted); + void HandleAttachment(const cucumber::messages::attachment& attachment); + void HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished); + void HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished); + + void PrintFeatureLine(const cucumber::messages::feature& feature); + void PrintRuleLine(const cucumber::messages::rule& rule); + void PrintTags(const cucumber::messages::pickle& pickle, std::size_t scenarioIndent); + void PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength); + void PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength); + + void PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength); + + std::map testCaseStartedIdToScenarioMap; + + std::map maxContentLengthByTestCaseStartedId; + std::map scenarioIndentByTestCaseStartedId; + + std::set printedFeatureUris; + std::set printedRuleIds; + + Query query; }; } diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp index 0c280f50..bb9e074d 100644 --- a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp +++ b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp @@ -3,6 +3,7 @@ #include "cucumber/messages/feature_child.hpp" #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/location.hpp" +#include "cucumber/messages/rule.hpp" #include "cucumber/messages/rule_child.hpp" #include "cucumber/messages/scenario.hpp" #include "cucumber/messages/step.hpp" @@ -32,6 +33,13 @@ namespace cucumber_cpp::library::formatter::helper return {}; } + std::vector ExtractRulesFeatureChild(const cucumber::messages::feature_child& child) + { + if (child.rule) + return { *child.rule }; + return {}; + } + std::vector ExtractScenarioContainers(const cucumber::messages::feature_child& child) { if (child.rule) @@ -84,6 +92,22 @@ namespace cucumber_cpp::library::formatter::helper return map; } + GherkinScenarioRuleMap GetGherkinScenarioRuleMap(const cucumber::messages::gherkin_document& gherkinDocument) + { + auto rules = gherkinDocument.feature->children | std::views::transform(ExtractRulesFeatureChild) | std::views::join; + + GherkinScenarioRuleMap map; + + for (const auto& rule : rules) + { + auto scenarios = rule.children | std::views::transform(ExtractScenarioFromRuleChild) | std::views::join; + for (const auto& scenario : scenarios) + map.emplace(scenario.id, rule); + } + + return map; + } + GherkinScenarioMap GetGherkinScenarioMap(const cucumber::messages::gherkin_document& gherkinDocument) { GherkinScenarioMap map; diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp index 49106a7f..d1f272fd 100644 --- a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp +++ b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp @@ -1,8 +1,10 @@ #ifndef HELPER_GHERKIN_DOCUMENT_PARSER_HPP #define HELPER_GHERKIN_DOCUMENT_PARSER_HPP +#include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/location.hpp" +#include "cucumber/messages/rule.hpp" #include "cucumber/messages/scenario.hpp" #include "cucumber/messages/step.hpp" #include @@ -11,10 +13,12 @@ namespace cucumber_cpp::library::formatter::helper { using GherkinStepMap = std::map; + using GherkinScenarioRuleMap = std::map; using GherkinScenarioMap = std::map; using GherkinScenarioLocationMap = std::map; GherkinStepMap GetGherkinStepMap(const cucumber::messages::gherkin_document& gherkinDocument); + GherkinScenarioRuleMap GetGherkinScenarioRuleMap(const cucumber::messages::gherkin_document& gherkinDocument); GherkinScenarioMap GetGherkinScenarioMap(const cucumber::messages::gherkin_document& gherkinDocument); GherkinScenarioLocationMap GetGherkinScenarioLocationMap(const cucumber::messages::gherkin_document& gherkinDocument); } diff --git a/cucumber_cpp/library/runtime/Coordinator.cpp b/cucumber_cpp/library/runtime/Coordinator.cpp index 2b4ad0ad..00eb785b 100644 --- a/cucumber_cpp/library/runtime/Coordinator.cpp +++ b/cucumber_cpp/library/runtime/Coordinator.cpp @@ -1,68 +1,16 @@ #include "cucumber_cpp/library/runtime/Coordinator.hpp" -#include "cucumber/gherkin/app.hpp" -#include "cucumber/gherkin/exceptions.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/gherkin/pickle_compiler.hpp" -#include "cucumber/gherkin/utils.hpp" -#include "cucumber/messages/duration.hpp" -#include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/feature.hpp" -#include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/parameter_type.hpp" -#include "cucumber/messages/parse_error.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/source.hpp" -#include "cucumber/messages/source_reference.hpp" -#include "cucumber/messages/step_definition.hpp" -#include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber/messages/step_match_argument.hpp" -#include "cucumber/messages/suggestion.hpp" -#include "cucumber/messages/test_case.hpp" -#include "cucumber/messages/test_case_finished.hpp" -#include "cucumber/messages/test_case_started.hpp" #include "cucumber/messages/test_run_finished.hpp" -#include "cucumber/messages/test_run_hook_finished.hpp" -#include "cucumber/messages/test_run_hook_started.hpp" #include "cucumber/messages/test_run_started.hpp" -#include "cucumber/messages/test_step.hpp" -#include "cucumber/messages/test_step_finished.hpp" -#include "cucumber/messages/test_step_result.hpp" -#include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber/messages/test_step_started.hpp" -#include "cucumber/messages/timestamp.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" -#include "cucumber_cpp/library/Body.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" -#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" -#include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include -#include #include -#include -#include #include -#include -#include namespace cucumber_cpp::library::runtime { diff --git a/cucumber_cpp/library/runtime/Coordinator.hpp b/cucumber_cpp/library/runtime/Coordinator.hpp index 01aa23d1..f5d4073c 100644 --- a/cucumber_cpp/library/runtime/Coordinator.hpp +++ b/cucumber_cpp/library/runtime/Coordinator.hpp @@ -1,70 +1,13 @@ #ifndef RUNTIME_COORDINATOR_HPP #define RUNTIME_COORDINATOR_HPP -#include "cucumber/gherkin/app.hpp" -#include "cucumber/gherkin/exceptions.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/gherkin/pickle_compiler.hpp" -#include "cucumber/gherkin/utils.hpp" -#include "cucumber/messages/duration.hpp" -#include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/feature.hpp" -#include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/parameter_type.hpp" -#include "cucumber/messages/parse_error.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/source.hpp" -#include "cucumber/messages/source_reference.hpp" -#include "cucumber/messages/step_definition.hpp" -#include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber/messages/step_match_argument.hpp" -#include "cucumber/messages/suggestion.hpp" -#include "cucumber/messages/test_case.hpp" -#include "cucumber/messages/test_case_finished.hpp" -#include "cucumber/messages/test_case_started.hpp" -#include "cucumber/messages/test_run_finished.hpp" -#include "cucumber/messages/test_run_hook_finished.hpp" -#include "cucumber/messages/test_run_hook_started.hpp" -#include "cucumber/messages/test_run_started.hpp" -#include "cucumber/messages/test_step.hpp" -#include "cucumber/messages/test_step_finished.hpp" -#include "cucumber/messages/test_step_result.hpp" -#include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber/messages/test_step_started.hpp" -#include "cucumber/messages/timestamp.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" -#include "cucumber_cpp/library/Body.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" -#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" -#include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include -#include -#include -#include -#include -#include #include -#include -#include -#include #include -#include #include -#include -#include -#include -#include -#include namespace cucumber_cpp::library::runtime { diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index a1ff3f8c..fd1035de 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -5,6 +5,7 @@ #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/pickle_table_row.hpp" #include "cucumber/messages/step_match_argument.hpp" #include "cucumber/messages/suggestion.hpp" #include "cucumber/messages/test_case.hpp" @@ -21,6 +22,7 @@ #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -28,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -41,16 +44,16 @@ namespace cucumber_cpp::library::runtime { namespace { - cucumber::messages::duration operator+=(cucumber::messages::duration durationA, cucumber::messages::duration durationB) - { - const auto seconds = durationA.seconds + durationB.seconds; - const auto nanos = durationA.nanos + durationB.nanos; + // cucumber::messages::duration operator+=(cucumber::messages::duration durationA, cucumber::messages::duration durationB) + // { + // const auto seconds = durationA.seconds + durationB.seconds; + // const auto nanos = durationA.nanos + durationB.nanos; - if (nanos >= support::nanosecondsPerSecond) - return { seconds + 1, nanos - support::nanosecondsPerSecond }; - else - return { seconds, nanos }; - } + // if (nanos >= support::nanosecondsPerSecond) + // return { seconds + 1, nanos - support::nanosecondsPerSecond }; + // else + // return { seconds, nanos }; + // } std::vector BuildExpressionParameters(std::span arguments, cucumber_expression::ParameterRegistry& parameterRegistry) { @@ -247,7 +250,14 @@ namespace cucumber_cpp::library::runtime parameters = BuildRegularParameters(testStep.step_match_arguments_lists->front().step_match_arguments); } - const auto result = InvokeStep(definition.factory(testCaseContext, {}, {}), parameters); + const auto toOptionalTable = [](const cucumber::messages::pickle_step& pickleStep) -> std::optional> + { + if (pickleStep.argument && pickleStep.argument->data_table) + return pickleStep.argument->data_table->rows; + return std::nullopt; + }; + + const auto result = InvokeStep(definition.factory(testCaseContext, toOptionalTable(pickleStep), pickleStep.argument ? pickleStep.argument->doc_string : std::nullopt), parameters); stepResults.push_back(result); } @@ -256,11 +266,12 @@ namespace cucumber_cpp::library::runtime stepResults.insert(stepResults.end(), afterStepHookResults.begin(), afterStepHookResults.end()); auto finalStepResult = util::GetWorstTestStepResult(stepResults); + + cucumber::messages::duration finalDuration{}; for (const auto& stepResult : stepResults) - { - finalStepResult.duration += stepResult.duration; - } + finalDuration += stepResult.duration; + finalStepResult.duration = finalDuration; return finalStepResult; } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.hpp b/cucumber_cpp/library/runtime/TestCaseRunner.hpp index 2f52fee0..0cabc603 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.hpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.hpp @@ -1,69 +1,21 @@ #ifndef RUNTIME_TEST_CASE_RUNNER_HPP #define RUNTIME_TEST_CASE_RUNNER_HPP -#include "cucumber/gherkin/app.hpp" -#include "cucumber/gherkin/exceptions.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/gherkin/pickle_compiler.hpp" -#include "cucumber/gherkin/utils.hpp" -#include "cucumber/messages/duration.hpp" -#include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/parameter_type.hpp" -#include "cucumber/messages/parse_error.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/source.hpp" -#include "cucumber/messages/source_reference.hpp" -#include "cucumber/messages/step_definition.hpp" -#include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber/messages/step_match_argument.hpp" -#include "cucumber/messages/suggestion.hpp" #include "cucumber/messages/test_case.hpp" -#include "cucumber/messages/test_case_finished.hpp" -#include "cucumber/messages/test_case_started.hpp" -#include "cucumber/messages/test_run_finished.hpp" -#include "cucumber/messages/test_run_hook_finished.hpp" -#include "cucumber/messages/test_run_hook_started.hpp" -#include "cucumber/messages/test_run_started.hpp" #include "cucumber/messages/test_step.hpp" -#include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber/messages/test_step_started.hpp" -#include "cucumber/messages/timestamp.hpp" #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" -#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" -#include "cucumber_cpp/library/support/Types.hpp" -#include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include -#include -#include #include -#include -#include #include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include namespace cucumber_cpp::library::runtime diff --git a/cucumber_cpp/library/support/Duration.cpp b/cucumber_cpp/library/support/Duration.cpp index 510ff747..566ce826 100644 --- a/cucumber_cpp/library/support/Duration.cpp +++ b/cucumber_cpp/library/support/Duration.cpp @@ -24,6 +24,27 @@ namespace cucumber_cpp::library::support } } + Stopwatch::Stopwatch() + { + Stopwatch::instance = this; + } + + Stopwatch& Stopwatch::Instance() + { + return *instance; + } + + void StopWatchHighResolutionClock::Start() + { + timeStart = std::chrono::high_resolution_clock::now(); + } + + std::chrono::nanoseconds StopWatchHighResolutionClock::Duration() + { + const auto timeStop = std::chrono::high_resolution_clock::now(); + return std::chrono::duration_cast(timeStop - timeStart); + } + cucumber::messages::duration MillisecondsToDuration(std::chrono::milliseconds millis) { return ToDuration(millis); diff --git a/cucumber_cpp/library/support/Duration.hpp b/cucumber_cpp/library/support/Duration.hpp index 3701491a..7e52f33b 100644 --- a/cucumber_cpp/library/support/Duration.hpp +++ b/cucumber_cpp/library/support/Duration.hpp @@ -3,7 +3,6 @@ #include "cucumber/messages/duration.hpp" #include -#include namespace cucumber_cpp::library::support { @@ -11,6 +10,32 @@ namespace cucumber_cpp::library::support std::chrono::milliseconds DurationToMilliseconds(const cucumber::messages::duration& duration); cucumber::messages::duration& operator+=(cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs); + + struct Stopwatch + { + protected: + Stopwatch(); + ~Stopwatch() = default; + + public: + static Stopwatch& Instance(); + + virtual void Start() = 0; + virtual std::chrono::nanoseconds Duration() = 0; + + private: + static inline Stopwatch* instance{ nullptr }; + }; + + struct StopWatchHighResolutionClock : Stopwatch + { + virtual ~StopWatchHighResolutionClock() = default; + void Start() override; + std::chrono::nanoseconds Duration() override; + + private: + std::chrono::high_resolution_clock::time_point timeStart{}; + }; } namespace cucumber::messages diff --git a/cucumber_cpp/library/support/Timestamp.cpp b/cucumber_cpp/library/support/Timestamp.cpp index e7c0e904..c5d61047 100644 --- a/cucumber_cpp/library/support/Timestamp.cpp +++ b/cucumber_cpp/library/support/Timestamp.cpp @@ -21,10 +21,30 @@ namespace cucumber_cpp::library::support } } - cucumber::messages::timestamp TimestampNow() + TimestampGenerator::TimestampGenerator() + { + instance = this; + } + + TimestampGenerator::~TimestampGenerator() + { + instance = nullptr; + } + + TimestampGenerator& TimestampGenerator::Instance() + { + return *instance; + } + + std::chrono::milliseconds TimestampGeneratorSystemClock::Now() { const auto now = std::chrono::system_clock::now().time_since_epoch(); - const auto nowMillis = std::chrono::duration_cast(now).count(); + return std::chrono::duration_cast(now); + } + + cucumber::messages::timestamp TimestampNow() + { + const auto nowMillis = TimestampGenerator::Instance().Now().count(); const auto seconds = nowMillis / millisecondsPerSecond; const auto nanos = (nowMillis % millisecondsPerSecond) * nanosecondsPerMillisecond; return cucumber::messages::timestamp{ diff --git a/cucumber_cpp/library/support/Timestamp.hpp b/cucumber_cpp/library/support/Timestamp.hpp index 62282282..1b432490 100644 --- a/cucumber_cpp/library/support/Timestamp.hpp +++ b/cucumber_cpp/library/support/Timestamp.hpp @@ -3,6 +3,7 @@ #include "cucumber/messages/duration.hpp" #include "cucumber/messages/timestamp.hpp" +#include #include namespace cucumber_cpp::library::support @@ -11,7 +12,29 @@ namespace cucumber_cpp::library::support constexpr std::size_t nanosecondsPerMillisecond = 1e6; constexpr std::size_t nanosecondsPerSecond = 1e9; - cucumber::messages::timestamp TimestampNow(); + struct TimestampGenerator + { + protected: + TimestampGenerator(); + ~TimestampGenerator(); + + public: + static TimestampGenerator& Instance(); + virtual std::chrono::milliseconds Now() = 0; + + private: + static inline TimestampGenerator* instance; + }; + + struct TimestampGeneratorSystemClock : TimestampGenerator + { + virtual ~TimestampGeneratorSystemClock() = default; + + std::chrono::milliseconds Now() override; + }; + + cucumber::messages::timestamp + TimestampNow(); cucumber::messages::duration operator-(const cucumber::messages::timestamp& lhs, const cucumber::messages::timestamp& rhs); } diff --git a/cucumber_cpp/runner/CMakeLists.txt b/cucumber_cpp/runner/CMakeLists.txt index b10dbdb2..97dafc8d 100644 --- a/cucumber_cpp/runner/CMakeLists.txt +++ b/cucumber_cpp/runner/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.runner ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.runner STATIC ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.runner PRIVATE Main.cpp From 7ac6e9c777ddc6a14efd44bbb8ea49de588d429f Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 15:43:54 +0000 Subject: [PATCH 008/196] removed unused files --- cucumber_cpp/library/engine/CMakeLists.txt | 5 --- cucumber_cpp/library/engine/NewRuntime.cpp | 1 - cucumber_cpp/library/engine/NewRuntime.hpp | 46 ---------------------- cucumber_cpp/library/engine/Result.hpp | 17 -------- cucumber_cpp/library/engine/Table.cpp | 27 ------------- cucumber_cpp/library/engine/Table.hpp | 33 ---------------- 6 files changed, 129 deletions(-) delete mode 100644 cucumber_cpp/library/engine/NewRuntime.cpp delete mode 100644 cucumber_cpp/library/engine/NewRuntime.hpp delete mode 100644 cucumber_cpp/library/engine/Result.hpp delete mode 100644 cucumber_cpp/library/engine/Table.cpp delete mode 100644 cucumber_cpp/library/engine/Table.hpp diff --git a/cucumber_cpp/library/engine/CMakeLists.txt b/cucumber_cpp/library/engine/CMakeLists.txt index 413880db..f1c914ff 100644 --- a/cucumber_cpp/library/engine/CMakeLists.txt +++ b/cucumber_cpp/library/engine/CMakeLists.txt @@ -1,15 +1,10 @@ add_library(cucumber_cpp.library.engine STATIC ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.engine PRIVATE - NewRuntime.cpp - NewRuntime.hpp - Result.hpp Step.cpp Step.hpp StepType.hpp StringTo.hpp - Table.cpp - Table.hpp ) target_include_directories(cucumber_cpp.library.engine PUBLIC diff --git a/cucumber_cpp/library/engine/NewRuntime.cpp b/cucumber_cpp/library/engine/NewRuntime.cpp deleted file mode 100644 index 428c8c20..00000000 --- a/cucumber_cpp/library/engine/NewRuntime.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "cucumber_cpp/library/engine/NewRuntime.hpp" diff --git a/cucumber_cpp/library/engine/NewRuntime.hpp b/cucumber_cpp/library/engine/NewRuntime.hpp deleted file mode 100644 index 87d398ec..00000000 --- a/cucumber_cpp/library/engine/NewRuntime.hpp +++ /dev/null @@ -1,46 +0,0 @@ -#ifndef ENGINE_NEW_RUNTIME_HPP -#define ENGINE_NEW_RUNTIME_HPP - -#include "cucumber/gherkin/app.hpp" -#include "cucumber/gherkin/exceptions.hpp" -#include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/gherkin/pickle_compiler.hpp" -#include "cucumber/gherkin/utils.hpp" -#include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/parameter_type.hpp" -#include "cucumber/messages/parse_error.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/source.hpp" -#include "cucumber/messages/source_reference.hpp" -#include "cucumber/messages/step_definition.hpp" -#include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/runtime/Coordinator.hpp" -#include "cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp" -#include "cucumber_cpp/library/runtime/Worker.hpp" -#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Types.hpp" -#include "cucumber_cpp/library/tag_expression/Parser.hpp" -#include "cucumber_cpp/library/util/Broadcaster.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - -} - -#endif diff --git a/cucumber_cpp/library/engine/Result.hpp b/cucumber_cpp/library/engine/Result.hpp deleted file mode 100644 index a850ba3f..00000000 --- a/cucumber_cpp/library/engine/Result.hpp +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef ENGINE_RESULT_HPP -#define ENGINE_RESULT_HPP - -namespace cucumber_cpp::library::engine -{ - enum struct Result - { - passed, - skipped, - pending, - undefined, - ambiguous, - failed - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/Table.cpp b/cucumber_cpp/library/engine/Table.cpp deleted file mode 100644 index 65db3680..00000000 --- a/cucumber_cpp/library/engine/Table.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef ENGINE_TABLE_HPP -#define ENGINE_TABLE_HPP - -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct TableValue - { - template - T As(std::source_location sourceLocation = std::source_location::current()) const; - - explicit TableValue(std::string value) - : value(std::move(value)) - {} - - private: - std::string value; - }; - - using Table = std::vector>; -} - -#endif diff --git a/cucumber_cpp/library/engine/Table.hpp b/cucumber_cpp/library/engine/Table.hpp deleted file mode 100644 index 820a97b5..00000000 --- a/cucumber_cpp/library/engine/Table.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef ENGINE_TABLE_HPP -#define ENGINE_TABLE_HPP - -#include "cucumber/messages/pickle_table_cell.hpp" -#include "cucumber/messages/pickle_table_row.hpp" -#include "cucumber_cpp/library/engine/StringTo.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct TableValue - { - template - T As() const - { - return StringTo(value); - } - - explicit TableValue(std::string value) - : value(std::move(value)) - {} - - private: - std::string value; - }; - - using Table = std::vector>; -} - -#endif From 77b476273cbbe839db66109769aa24bb65ffe0b3 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 15:44:02 +0000 Subject: [PATCH 009/196] update devcontainers for CI --- .github/workflows/ci.yml | 6 +++--- .github/workflows/static-analysis.yml | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 644bb6df..341b0a52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: build-windows: name: Windows Host Build runs-on: [ubuntu-latest] - container: ghcr.io/philips-software/amp-devcontainer-cpp:5.6.2@sha256:a0804f7454d52564f07317f7e09a012261b6d9553dbe8854fcf265dce571cf86 # v5.6.2 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.0@sha256:3f65569a719ca9b2d996222bcdce3dfe9797903ba4c3aafff364fa6fe9ca114d steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 @@ -70,7 +70,7 @@ jobs: build-linux-devcontainer: name: Linux Host Build in Devcontainer runs-on: [ubuntu-latest] - container: ghcr.io/philips-software/amp-devcontainer-cpp:5.6.2@sha256:a0804f7454d52564f07317f7e09a012261b6d9553dbe8854fcf265dce571cf86 # v5.6.2 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.0@sha256:3f65569a719ca9b2d996222bcdce3dfe9797903ba4c3aafff364fa6fe9ca114d steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: hendrikmuhs/ccache-action@bfa03e1de4d7f7c3e80ad9109feedd05c4f5a716 # v1.2.19 @@ -98,7 +98,7 @@ jobs: issues: read checks: write pull-requests: write - container: ghcr.io/philips-software/amp-devcontainer-cpp:5.6.2@sha256:a0804f7454d52564f07317f7e09a012261b6d9553dbe8854fcf265dce571cf86 # v5.6.2 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.0@sha256:3f65569a719ca9b2d996222bcdce3dfe9797903ba4c3aafff364fa6fe9ca114d steps: - uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1 - uses: hendrikmuhs/ccache-action@bfa03e1de4d7f7c3e80ad9109feedd05c4f5a716 # v1.2.19 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 835a7928..d57c9978 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -18,7 +18,7 @@ jobs: sonar: name: SonarCloud runs-on: ubuntu-latest - container: ghcr.io/philips-software/amp-devcontainer-cpp:5.6.2@sha256:a0804f7454d52564f07317f7e09a012261b6d9553dbe8854fcf265dce571cf86 # v5.6.2 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.0@sha256:3f65569a719ca9b2d996222bcdce3dfe9797903ba4c3aafff364fa6fe9ca114d env: SONAR_SERVER_URL: "https://sonarcloud.io" steps: @@ -67,7 +67,7 @@ jobs: codeql: name: CodeQL runs-on: ubuntu-latest - container: ghcr.io/philips-software/amp-devcontainer-cpp:5.6.2@sha256:a0804f7454d52564f07317f7e09a012261b6d9553dbe8854fcf265dce571cf86 # v5.6.2 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.0@sha256:3f65569a719ca9b2d996222bcdce3dfe9797903ba4c3aafff364fa6fe9ca114d permissions: security-events: write steps: From 50a2bceddafc21900a1e6967fb9ef9c00aaa3afa Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 16:03:29 +0000 Subject: [PATCH 010/196] removed agauniyal/rang from dependencies --- cucumber_cpp/library/formatter/CMakeLists.txt | 1 - .../library/formatter/GetColorFunctions.cpp | 1 - external/CMakeLists.txt | 1 - external/agauniyal/CMakeLists.txt | 1 - external/agauniyal/rang/CMakeLists.txt | 2 - external/agauniyal/rang/include/rang.hpp | 562 ------------------ .../jupyter-xeus/cpp-terminal/CMakeLists.txt | 5 + 7 files changed, 5 insertions(+), 568 deletions(-) delete mode 100644 external/agauniyal/CMakeLists.txt delete mode 100644 external/agauniyal/rang/CMakeLists.txt delete mode 100644 external/agauniyal/rang/include/rang.hpp diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index a7b0cd86..644fa4a5 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -20,7 +20,6 @@ target_link_libraries(cucumber_cpp.library.formatter PUBLIC # cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression cucumber_cpp.library.formatter.helper - rang cpp-terminal ) diff --git a/cucumber_cpp/library/formatter/GetColorFunctions.cpp b/cucumber_cpp/library/formatter/GetColorFunctions.cpp index 5fe4bbf1..307b2bc6 100644 --- a/cucumber_cpp/library/formatter/GetColorFunctions.cpp +++ b/cucumber_cpp/library/formatter/GetColorFunctions.cpp @@ -2,7 +2,6 @@ #include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" #include "cpp-terminal/color.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "rang.hpp" #include #include #include diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 2721d451..1cd0d97a 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -2,7 +2,6 @@ set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "") add_subdirectory(nlohmann) # before cucumber -add_subdirectory(agauniyal) add_subdirectory(cliutils) add_subdirectory(cucumber) add_subdirectory(googletest) diff --git a/external/agauniyal/CMakeLists.txt b/external/agauniyal/CMakeLists.txt deleted file mode 100644 index d2cf05f6..00000000 --- a/external/agauniyal/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(rang) diff --git a/external/agauniyal/rang/CMakeLists.txt b/external/agauniyal/rang/CMakeLists.txt deleted file mode 100644 index 1b9d7d0c..00000000 --- a/external/agauniyal/rang/CMakeLists.txt +++ /dev/null @@ -1,2 +0,0 @@ -add_library(rang INTERFACE) -target_include_directories(rang INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include) diff --git a/external/agauniyal/rang/include/rang.hpp b/external/agauniyal/rang/include/rang.hpp deleted file mode 100644 index e30a76d9..00000000 --- a/external/agauniyal/rang/include/rang.hpp +++ /dev/null @@ -1,562 +0,0 @@ -#ifndef RANG_DOT_HPP -#define RANG_DOT_HPP - -#if defined(__unix__) || defined(__unix) || defined(__linux__) -#define OS_LINUX -#elif defined(WIN32) || defined(_WIN32) || defined(_WIN64) -#define OS_WIN -#elif defined(__APPLE__) || defined(__MACH__) -#define OS_MAC -#else -#error Unknown Platform -#endif - -#if defined(OS_LINUX) || defined(OS_MAC) -#include - -#elif defined(OS_WIN) - -#if defined(_WIN32_WINNT) && (_WIN32_WINNT < 0x0600) -#error \ - "Please include rang.hpp before any windows system headers or set _WIN32_WINNT at least to _WIN32_WINNT_VISTA" -#elif !defined(_WIN32_WINNT) -#define _WIN32_WINNT _WIN32_WINNT_VISTA -#endif - -#include -#include -#include - -// Only defined in windows 10 onwards, redefining in lower windows since it -// doesn't gets used in lower versions -// https://docs.microsoft.com/en-us/windows/console/getconsolemode -#ifndef ENABLE_VIRTUAL_TERMINAL_PROCESSING -#define ENABLE_VIRTUAL_TERMINAL_PROCESSING 0x0004 -#endif - -#endif - -#include -#include -#include -#include -#include - -namespace rang -{ - - /* For better compability with most of terminals do not use any style settings - * except of reset, bold and reversed. - * Note that on Windows terminals bold style is same as fgB color. - */ - enum class style - { - reset = 0, - bold = 1, - dim = 2, - italic = 3, - underline = 4, - blink = 5, - rblink = 6, - reversed = 7, - conceal = 8, - crossed = 9 - }; - - enum class fg - { - black = 30, - red = 31, - green = 32, - yellow = 33, - blue = 34, - magenta = 35, - cyan = 36, - gray = 37, - reset = 39 - }; - - enum class bg - { - black = 40, - red = 41, - green = 42, - yellow = 43, - blue = 44, - magenta = 45, - cyan = 46, - gray = 47, - reset = 49 - }; - - enum class fgB - { - black = 90, - red = 91, - green = 92, - yellow = 93, - blue = 94, - magenta = 95, - cyan = 96, - gray = 97 - }; - - enum class bgB - { - black = 100, - red = 101, - green = 102, - yellow = 103, - blue = 104, - magenta = 105, - cyan = 106, - gray = 107 - }; - - enum class control - { // Behaviour of rang function calls - Off = 0, // toggle off rang style/color calls - Auto = 1, // (Default) autodect terminal and colorize if needed - Force = 2 // force ansi color output to non terminal streams - }; - // Use rang::setControlMode to set rang control mode - - enum class winTerm - { // Windows Terminal Mode - Auto = 0, // (Default) automatically detects wheter Ansi or Native API - Ansi = 1, // Force use Ansi API - Native = 2 // Force use Native API - }; - - // Use rang::setWinTermMode to explicitly set terminal API for Windows - // Calling rang::setWinTermMode have no effect on other OS - - namespace rang_implementation - { - - inline std::atomic& controlMode() noexcept - { - static std::atomic value(control::Auto); - return value; - } - - inline std::atomic& winTermMode() noexcept - { - static std::atomic termMode(winTerm::Auto); - return termMode; - } - - inline bool supportsColor() noexcept - { -#if defined(OS_LINUX) || defined(OS_MAC) - - static const bool result = [] - { - const char* Terms[] = { "ansi", "color", "console", "cygwin", "gnome", - "konsole", "kterm", "linux", "msys", "putty", - "rxvt", "screen", "vt100", "xterm" }; - - const char* env_p = std::getenv("TERM"); - if (env_p == nullptr) - { - return false; - } - return std::any_of(std::begin(Terms), std::end(Terms), - [&](const char* term) - { - return std::strstr(env_p, term) != nullptr; - }); - }(); - -#elif defined(OS_WIN) - // All windows versions support colors through native console methods - static constexpr bool result = true; -#endif - return result; - } - -#ifdef OS_WIN - - inline bool isMsysPty(int fd) noexcept - { - // Dynamic load for binary compability with old Windows - const auto ptrGetFileInformationByHandleEx = reinterpret_cast( - GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), - "GetFileInformationByHandleEx")); - if (!ptrGetFileInformationByHandleEx) - { - return false; - } - - HANDLE h = reinterpret_cast(_get_osfhandle(fd)); - if (h == INVALID_HANDLE_VALUE) - { - return false; - } - - // Check that it's a pipe: - if (GetFileType(h) != FILE_TYPE_PIPE) - { - return false; - } - - // POD type is binary compatible with FILE_NAME_INFO from WinBase.h - // It have the same alignment and used to avoid UB in caller code - struct MY_FILE_NAME_INFO - { - DWORD FileNameLength; - WCHAR FileName[MAX_PATH]; - }; - - auto pNameInfo = std::unique_ptr( - new (std::nothrow) MY_FILE_NAME_INFO()); - if (!pNameInfo) - { - return false; - } - - // Check pipe name is template of - // {"cygwin-","msys-"}XXXXXXXXXXXXXXX-ptyX-XX - if (!ptrGetFileInformationByHandleEx(h, FileNameInfo, pNameInfo.get(), - sizeof(MY_FILE_NAME_INFO))) - { - return false; - } - std::wstring name(pNameInfo->FileName, pNameInfo->FileNameLength / sizeof(WCHAR)); - if ((name.find(L"msys-") == std::wstring::npos && name.find(L"cygwin-") == std::wstring::npos) || name.find(L"-pty") == std::wstring::npos) - { - return false; - } - - return true; - } - -#endif - - inline bool isTerminal(const std::streambuf* osbuf) noexcept - { - using std::cerr; - using std::clog; - using std::cout; -#if defined(OS_LINUX) || defined(OS_MAC) - if (osbuf == cout.rdbuf()) - { - static const bool cout_term = isatty(fileno(stdout)) != 0; - return cout_term; - } - else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) - { - static const bool cerr_term = isatty(fileno(stderr)) != 0; - return cerr_term; - } -#elif defined(OS_WIN) - if (osbuf == cout.rdbuf()) - { - static const bool cout_term = (_isatty(_fileno(stdout)) || isMsysPty(_fileno(stdout))); - return cout_term; - } - else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) - { - static const bool cerr_term = (_isatty(_fileno(stderr)) || isMsysPty(_fileno(stderr))); - return cerr_term; - } -#endif - return false; - } - - template - using enableStd = typename std::enable_if< - std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value || std::is_same::value, - std::ostream&>::type; - -#ifdef OS_WIN - - struct SGR - { // Select Graphic Rendition parameters for Windows console - BYTE fgColor; // foreground color (0-15) lower 3 rgb bits + intense bit - BYTE bgColor; // background color (0-15) lower 3 rgb bits + intense bit - BYTE bold; // emulated as FOREGROUND_INTENSITY bit - BYTE underline; // emulated as BACKGROUND_INTENSITY bit - BOOLEAN inverse; // swap foreground/bold & background/underline - BOOLEAN conceal; // set foreground/bold to background/underline - }; - - enum class AttrColor : BYTE - { // Color attributes for console screen buffer - black = 0, - red = 4, - green = 2, - yellow = 6, - blue = 1, - magenta = 5, - cyan = 3, - gray = 7 - }; - - inline HANDLE getConsoleHandle(const std::streambuf* osbuf) noexcept - { - if (osbuf == std::cout.rdbuf()) - { - static const HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE); - return hStdout; - } - else if (osbuf == std::cerr.rdbuf() || osbuf == std::clog.rdbuf()) - { - static const HANDLE hStderr = GetStdHandle(STD_ERROR_HANDLE); - return hStderr; - } - return INVALID_HANDLE_VALUE; - } - - inline bool setWinTermAnsiColors(const std::streambuf* osbuf) noexcept - { - HANDLE h = getConsoleHandle(osbuf); - if (h == INVALID_HANDLE_VALUE) - { - return false; - } - DWORD dwMode = 0; - if (!GetConsoleMode(h, &dwMode)) - { - return false; - } - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (!SetConsoleMode(h, dwMode)) - { - return false; - } - return true; - } - - inline bool supportsAnsi(const std::streambuf* osbuf) noexcept - { - using std::cerr; - using std::clog; - using std::cout; - if (osbuf == cout.rdbuf()) - { - static const bool cout_ansi = (isMsysPty(_fileno(stdout)) || setWinTermAnsiColors(osbuf)); - return cout_ansi; - } - else if (osbuf == cerr.rdbuf() || osbuf == clog.rdbuf()) - { - static const bool cerr_ansi = (isMsysPty(_fileno(stderr)) || setWinTermAnsiColors(osbuf)); - return cerr_ansi; - } - return false; - } - - inline const SGR& defaultState() noexcept - { - static const SGR defaultSgr = []() -> SGR - { - CONSOLE_SCREEN_BUFFER_INFO info; - WORD attrib = FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE; - if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), - &info) || - GetConsoleScreenBufferInfo(GetStdHandle(STD_ERROR_HANDLE), - &info)) - { - attrib = info.wAttributes; - } - SGR sgr = { 0, 0, 0, 0, FALSE, FALSE }; - sgr.fgColor = attrib & 0x0F; - sgr.bgColor = (attrib & 0xF0) >> 4; - return sgr; - }(); - return defaultSgr; - } - - inline BYTE ansi2attr(BYTE rgb) noexcept - { - static const AttrColor rev[8] = { AttrColor::black, AttrColor::red, AttrColor::green, - AttrColor::yellow, AttrColor::blue, AttrColor::magenta, - AttrColor::cyan, AttrColor::gray }; - return static_cast(rev[rgb]); - } - - inline void setWinSGR(rang::bg col, SGR& state) noexcept - { - if (col != rang::bg::reset) - { - state.bgColor = ansi2attr(static_cast(col) - 40); - } - else - { - state.bgColor = defaultState().bgColor; - } - } - - inline void setWinSGR(rang::fg col, SGR& state) noexcept - { - if (col != rang::fg::reset) - { - state.fgColor = ansi2attr(static_cast(col) - 30); - } - else - { - state.fgColor = defaultState().fgColor; - } - } - - inline void setWinSGR(rang::bgB col, SGR& state) noexcept - { - state.bgColor = (BACKGROUND_INTENSITY >> 4) | ansi2attr(static_cast(col) - 100); - } - - inline void setWinSGR(rang::fgB col, SGR& state) noexcept - { - state.fgColor = FOREGROUND_INTENSITY | ansi2attr(static_cast(col) - 90); - } - - inline void setWinSGR(rang::style style, SGR& state) noexcept - { - switch (style) - { - case rang::style::reset: - state = defaultState(); - break; - case rang::style::bold: - state.bold = FOREGROUND_INTENSITY; - break; - case rang::style::underline: - case rang::style::blink: - state.underline = BACKGROUND_INTENSITY; - break; - case rang::style::reversed: - state.inverse = TRUE; - break; - case rang::style::conceal: - state.conceal = TRUE; - break; - default: - break; - } - } - - inline SGR& current_state() noexcept - { - static SGR state = defaultState(); - return state; - } - - inline WORD SGR2Attr(const SGR& state) noexcept - { - WORD attrib = 0; - if (state.conceal) - { - if (state.inverse) - { - attrib = (state.fgColor << 4) | state.fgColor; - if (state.bold) - attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - else - { - attrib = (state.bgColor << 4) | state.bgColor; - if (state.underline) - attrib |= FOREGROUND_INTENSITY | BACKGROUND_INTENSITY; - } - } - else if (state.inverse) - { - attrib = (state.fgColor << 4) | state.bgColor; - if (state.bold) - attrib |= BACKGROUND_INTENSITY; - if (state.underline) - attrib |= FOREGROUND_INTENSITY; - } - else - { - attrib = state.fgColor | (state.bgColor << 4) | state.bold | state.underline; - } - return attrib; - } - - template - inline void setWinColorAnsi(std::ostream& os, T const value) - { - os << "\033[" << static_cast(value) << "m"; - } - - template - inline void setWinColorNative(std::ostream& os, T const value) - { - const HANDLE h = getConsoleHandle(os.rdbuf()); - if (h != INVALID_HANDLE_VALUE) - { - setWinSGR(value, current_state()); - // Out all buffered text to console with previous settings: - os.flush(); - SetConsoleTextAttribute(h, SGR2Attr(current_state())); - } - } - - template - inline enableStd setColor(std::ostream& os, T const value) - { - if (winTermMode() == winTerm::Auto) - { - if (supportsAnsi(os.rdbuf())) - { - setWinColorAnsi(os, value); - } - else - { - setWinColorNative(os, value); - } - } - else if (winTermMode() == winTerm::Ansi) - { - setWinColorAnsi(os, value); - } - else - { - setWinColorNative(os, value); - } - return os; - } -#else - template - inline enableStd setColor(std::ostream& os, T const value) - { - return os << "\033[" << static_cast(value) << "m"; - } -#endif - } // namespace rang_implementation - - template - inline rang_implementation::enableStd operator<<(std::ostream& os, - const T value) - { - const control option = rang_implementation::controlMode(); - switch (option) - { - case control::Auto: - return rang_implementation::supportsColor() && rang_implementation::isTerminal(os.rdbuf()) - ? rang_implementation::setColor(os, value) - : os; - case control::Force: - return rang_implementation::setColor(os, value); - default: - return os; - } - } - - inline void setWinTermMode(const rang::winTerm value) noexcept - { - rang_implementation::winTermMode() = value; - } - - inline void setControlMode(const control value) noexcept - { - rang_implementation::controlMode() = value; - } - -} // namespace rang - -#undef OS_LINUX -#undef OS_WIN -#undef OS_MAC - -#endif /* ifndef RANG_DOT_HPP */ diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt index 88c3701e..2b635e9e 100644 --- a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt +++ b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt @@ -11,4 +11,9 @@ add_compile_options( -Wno-unused-but-set-variable ) +set(CPPTERMINAL_BUILD_EXAMPLES Off) +set(CPPTERMINAL_ENABLE_INSTALL Off) +set(CPPTERMINAL_ENABLE_TESTING Off) +set(CPPTERMINAL_ENABLE_DOCS Off) + FetchContent_MakeAvailable(cpp-terminal) From 4054607cb6ecac508ff76bc71e73edd677159aba Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 12 Dec 2025 16:03:46 +0000 Subject: [PATCH 011/196] cleanup Application --- cucumber_cpp/library/Application.cpp | 41 ++-------------------------- cucumber_cpp/library/Application.hpp | 2 ++ 2 files changed, 5 insertions(+), 38 deletions(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 4fdc6604..4205b547 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -9,7 +9,6 @@ #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/engine/NewRuntime.hpp" #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -174,33 +173,10 @@ namespace cucumber_cpp::library void Application::RunFeatures() { - struct BroadcastListener - { - explicit BroadcastListener(util::Broadcaster& broadcaster) - : listener(broadcaster, [this](const cucumber::messages::envelope& envelope) - { - OnEvent(envelope); - }) - {} - - void OnEvent(const cucumber::messages::envelope& envelope) - { - std::cout << envelope.to_json() << "\n"; - } - - private: - util::Listener listener; - }; - - // BroadcastListener broadcastListener{ broadcaster }; - - // for (const auto& selectedReporter : options.reporters) - // reporters.Use(selectedReporter); - const auto tagExpression = Join(options.tags, " "); const auto featureFiles = GetFeatureFiles(options); - support::RunOptions runOptions{ + const auto runOptions = support::RunOptions{ .sources = { .paths = featureFiles, .tagExpression = tagExpression, @@ -213,15 +189,12 @@ namespace cucumber_cpp::library auto& listeners = testing::UnitTest::GetInstance()->listeners(); auto* defaultEventListener = listeners.Release(listeners.default_result_printer()); - api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster); + runPassed = api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster); listeners.Append(defaultEventListener); // if (options.printStepsNotUsed) // PrintStepsNotUsed(stepRegistry); - - std::cout << '\n' - << std::flush; } void Application::PrintStepsNotUsed(const StepRegistry& stepRegistry) const @@ -245,14 +218,6 @@ namespace cucumber_cpp::library int Application::GetExitCode() const { - return 0; - // if (testing::UnitTest::GetInstance()->Failed()) - // return GetExitCode(engine::Result::failed); - // return GetExitCode(engine::Result::passed); + return runPassed ? 0 : 1; } - - // int Application::GetExitCode(engine::Result result) const - // { - // return static_cast>(result) - static_cast>(engine::Result::passed); - // } } diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 9478cc6f..95152ceb 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -72,6 +72,8 @@ namespace cucumber_cpp::library bool removeDefaultGoogleTestListener; support::StopWatchHighResolutionClock stopwatchHighResolutionClock; support::TimestampGeneratorSystemClock timestampGeneratorSystemClock; + + bool runPassed{ false }; }; } From 45a43808b8cab8bf161091af248da21671e1e3dd Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 14 Dec 2025 00:42:36 +0000 Subject: [PATCH 012/196] sanitize and dump actual and expected json --- .gitignore | 4 ++ compatibility/compatibility.cpp | 86 ++++++++++++++++++++++++--------- 2 files changed, 66 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 7cf593e7..2da3bbc9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,7 @@ .xwin-cache .vs/ megalinter-reports/ + +# compatibility generated files +actual.ndjson +expected.ndjson diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 35a88163..c108f4ca 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -8,6 +8,8 @@ #include "library/support/Duration.hpp" #include "nlohmann/json.hpp" #include "nlohmann/json_fwd.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" #include #include #include @@ -63,12 +65,27 @@ namespace compatibility for (auto& [key, value] : json.items()) { if (value.is_object()) + { SanitizeExpectedJson(value); + if (value.size() == 0) + json.erase(key); + } else if (value.is_array()) { + auto idx = 0; for (auto& item : value) + { if (item.is_object()) SanitizeExpectedJson(item); + + if (item.size() == 0) + value.erase(idx); + + ++idx; + } + + if (value.size() == 0) + json.erase(key); } else if (key == "uri") { @@ -87,12 +104,27 @@ namespace compatibility for (auto& [key, value] : json.items()) { if (value.is_object()) + { SanitizeActualJson(value); + if (value.size() == 0) + json.erase(key); + } else if (value.is_array()) { + auto idx = 0; for (auto& item : value) + { if (item.is_object()) SanitizeActualJson(item); + + if (item.size() == 0) + value.erase(idx); + + ++idx; + } + + if (value.size() == 0) + json.erase(key); } else if (key == "line") json.erase(key); @@ -101,13 +133,14 @@ namespace compatibility struct BroadcastListener { - BroadcastListener(std::filesystem::path ndjsonin, std::filesystem::path ndout, cucumber_cpp::library::util::Broadcaster& broadcaster) + BroadcastListener(std::filesystem::path ndjsonin, std::filesystem::path expectedndjson, std::filesystem::path ndout, cucumber_cpp::library::util::Broadcaster& broadcaster) : listener(broadcaster, [this](const cucumber::messages::envelope& envelope) { OnEvent(envelope); }) , ndjsonin{ std::move(ndjsonin) } - , ndout(std::move(ndout)) + , expectedndjson{ std::move(expectedndjson) } + , actualndjson(std::move(ndout)) { while (!ifs.eof()) { @@ -133,8 +166,6 @@ namespace compatibility nlohmann::json actualJson{}; to_json(actualJson, envelope); - ofs << envelope.to_json() << "\n"; - if (expectedEnvelopes.empty()) { std::cerr << "Unexpected envelope: " << actualJson.dump() << "\n\n"; @@ -146,27 +177,31 @@ namespace compatibility const auto expectedJson = expectedEnvelopes.front(); expectedEnvelopes.pop_front(); + expectedOfs << expectedJson.dump() << "\n"; + actualOfs << actualJson.dump() << "\n"; + const auto expectedMessage = expectedJson.items().begin().key(); const auto diff = nlohmann::json::diff(expectedJson, actualJson); - if (actualJson.contains(expectedMessage)) - { - if (actualJson[expectedMessage] == expectedJson[expectedMessage]) - { - std::cout << "matching!!!! " << expectedMessage << "\n\n"; - } - else - { - std::cerr << std::format("Mismatch {}: {}\n", expectedMessage, diff.dump()); - std::cerr << "expected: " << expectedJson[expectedMessage] << "\n"; - std::cerr << "actual : " << actualJson[expectedMessage] << "\n\n"; - } - } - else - { - std::cerr << std::format("Missing {}: {}\n", expectedMessage, diff.dump()); - } + EXPECT_THAT(actualJson, testing::Eq(expectedJson)); + // if (actualJson.contains(expectedMessage)) + // { + // if (actualJson[expectedMessage] == expectedJson[expectedMessage]) + // { + // std::cout << "matching!!!! " << expectedMessage << "\n\n"; + // } + // else + // { + // std::cerr << std::format("Mismatch {}: {}\n", expectedMessage, diff.dump()); + // std::cerr << "expected: " << expectedJson[expectedMessage] << "\n"; + // std::cerr << "actual : " << actualJson[expectedMessage] << "\n\n"; + // } + // } + // else + // { + // std::cerr << std::format("Missing {}: {}\n", expectedMessage, diff.dump()); + // } } private: @@ -174,8 +209,11 @@ namespace compatibility std::filesystem::path ndjsonin; std::ifstream ifs{ ndjsonin }; - std::filesystem::path ndout; - std::ofstream ofs{ ndout }; + std::filesystem::path expectedndjson; + std::ofstream expectedOfs{ expectedndjson }; + + std::filesystem::path actualndjson; + std::ofstream actualOfs{ actualndjson }; std::list expectedEnvelopes; }; @@ -248,7 +286,7 @@ namespace compatibility cucumber_cpp::library::util::Broadcaster broadcaster; - BroadcastListener broadcastListener{ devkit.ndjsonFile, devkit.ndjsonFile.parent_path() / "out.ndjson", broadcaster }; + BroadcastListener broadcastListener{ devkit.ndjsonFile, devkit.ndjsonFile.parent_path() / "expected.ndjson", devkit.ndjsonFile.parent_path() / "actual.ndjson", broadcaster }; cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster); // EXPECT_NONFATAL_FAILURE(cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster), ""); From 958e05c3516fb3e48270c840a358b40508354f65 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 14 Dec 2025 00:42:56 +0000 Subject: [PATCH 013/196] set option variables for dependencies as cache variables --- CMakeLists.txt | 2 +- external/jbeder/yaml-cpp/CMakeLists.txt | 4 +++- external/jupyter-xeus/cpp-terminal/CMakeLists.txt | 8 ++++---- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ac5a8d8e..b4a82feb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,7 +25,7 @@ option(CCR_FETCH_DEPS "Fetch dependencies via FetchContent." On ) option(CCR_BUILD_TESTS "Enable build of the tests" ${CCR_DEFAULTOPT}) option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements" Off) -set(BUILD_SHARED_LIBS OFF) +set(BUILD_SHARED_LIBS Off CACHE STRING "") add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) diff --git a/external/jbeder/yaml-cpp/CMakeLists.txt b/external/jbeder/yaml-cpp/CMakeLists.txt index 221af32e..5de41095 100644 --- a/external/jbeder/yaml-cpp/CMakeLists.txt +++ b/external/jbeder/yaml-cpp/CMakeLists.txt @@ -3,5 +3,7 @@ FetchContent_Declare( GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git GIT_TAG 2f86d13775d119edbb69af52e5f566fd65c6953b # Unreleased ) -set(YAML_ENABLE_PIC OFF) + +set(YAML_ENABLE_PIC OFF CACHE STRING "") + FetchContent_MakeAvailable(yaml-cpp) diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt index 2b635e9e..b9fe8d3a 100644 --- a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt +++ b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt @@ -11,9 +11,9 @@ add_compile_options( -Wno-unused-but-set-variable ) -set(CPPTERMINAL_BUILD_EXAMPLES Off) -set(CPPTERMINAL_ENABLE_INSTALL Off) -set(CPPTERMINAL_ENABLE_TESTING Off) -set(CPPTERMINAL_ENABLE_DOCS Off) +set(CPPTERMINAL_BUILD_EXAMPLES Off CACHE STRING "") +set(CPPTERMINAL_ENABLE_INSTALL Off CACHE STRING "") +set(CPPTERMINAL_ENABLE_TESTING Off CACHE STRING "") +set(CPPTERMINAL_ENABLE_DOCS Off CACHE STRING "") FetchContent_MakeAvailable(cpp-terminal) From 6eb2da0ff3a6da9f8a48c1d7d146298e55e62f3c Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 14 Dec 2025 00:43:36 +0000 Subject: [PATCH 014/196] fix grouping for string parameter --- .../cucumber_expression/ParameterRegistry.cpp | 21 ++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index 15ff8818..ff632f68 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -37,14 +38,24 @@ namespace cucumber_cpp::library::cucumber_expression { return { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group { - std::string str = matches[1].matched ? matches[1].str() : matches[3].str(); - str = std::regex_replace(str, std::regex(R"__(\\")__"), "\""); - str = std::regex_replace(str, std::regex(R"__(\\')__"), "'"); - return { .value = str }; + return { + .children = { + matches[1].matched ? cucumber::messages::group{ .value = matches[1].str() } : cucumber::messages::group{}, + matches[3].matched ? cucumber::messages::group{ .value = matches[3].str() } : cucumber::messages::group{}, + }, + .value = matches[0].str(), + }; }, .toAny = [](const cucumber::messages::group& matches) -> std::any { - return matches.value.value(); + std::string str = matches.children.front().value.has_value() + ? matches.children.front().value.value() + : matches.children.back().value.value(); + + str = std::regex_replace(str, std::regex(R"__(\\")__"), "\""); + str = std::regex_replace(str, std::regex(R"__(\\')__"), "'"); + + return str; } }; } } From ece0b626cff85acb45bb507e2b499a41b078dc6a Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 14 Dec 2025 00:44:11 +0000 Subject: [PATCH 015/196] use ScopedFakeTestPartResultReporter instead of EmptyTestEventListener to catch errors --- cucumber_cpp/library/Body.cpp | 47 +++++++++++++++++++---------------- cucumber_cpp/library/Body.hpp | 11 -------- 2 files changed, 25 insertions(+), 33 deletions(-) diff --git a/cucumber_cpp/library/Body.cpp b/cucumber_cpp/library/Body.cpp index d8eae73f..09a5d97a 100644 --- a/cucumber_cpp/library/Body.cpp +++ b/cucumber_cpp/library/Body.cpp @@ -9,45 +9,48 @@ #include #include #include +#include #include #include #include +#include namespace cucumber_cpp::library { - EventListener::EventListener(cucumber::messages::test_step_result& testStepResult) - : testStepResult{ testStepResult } + struct CucumberResultReporter : public testing::ScopedFakeTestPartResultReporter { - testing::UnitTest::GetInstance()->listeners().Append(this); - } - - EventListener::~EventListener() - { - testing::UnitTest::GetInstance()->listeners().Release(this); - } + explicit CucumberResultReporter(cucumber::messages::test_step_result& testStepResult) + : testing::ScopedFakeTestPartResultReporter{ nullptr } + , testStepResult{ testStepResult } + { + } - void EventListener::OnTestPartResult(const testing::TestPartResult& testPartResult) - { - if (testPartResult.failed()) + void ReportTestPartResult(const testing::TestPartResult& testPartResult) { - testStepResult.status = cucumber::messages::test_step_result_status::FAILED; + if (testPartResult.failed()) + { + testStepResult.status = cucumber::messages::test_step_result_status::FAILED; - auto fileName = std::filesystem::relative(testPartResult.file_name(), std::filesystem::current_path()).string(); + auto fileName = std::filesystem::relative(testPartResult.file_name(), std::filesystem::current_path()).string(); - if (testStepResult.message) - testStepResult.message = std::format("{}\n{}:{}: Failure\n{}", testStepResult.message.value(), fileName, testPartResult.line_number(), testPartResult.message()); - else - testStepResult.message = std::format("{}:{}: Failure\n{}", fileName, testPartResult.line_number(), testPartResult.message()); + if (testStepResult.message) + testStepResult.message = std::format("{}\n{}:{}: Failure\n{}", testStepResult.message.value(), fileName, testPartResult.line_number(), testPartResult.message()); + else + testStepResult.message = std::format("{}:{}: Failure\n{}", fileName, testPartResult.line_number(), testPartResult.message()); + } + + if (testPartResult.fatally_failed()) + throw FatalError{ testPartResult.message() }; } - if (testPartResult.fatally_failed()) - throw FatalError{ testPartResult.message() }; - } + private: + cucumber::messages::test_step_result& testStepResult; + }; cucumber::messages::test_step_result Body::ExecuteAndCatchExceptions(const ExecuteArgs& args) { cucumber::messages::test_step_result testStepResult{ .status = cucumber::messages::test_step_result_status::PASSED }; - EventListener eventListener{ testStepResult }; + CucumberResultReporter reportListener{ testStepResult }; try { diff --git a/cucumber_cpp/library/Body.hpp b/cucumber_cpp/library/Body.hpp index 9f3372a9..15832aae 100644 --- a/cucumber_cpp/library/Body.hpp +++ b/cucumber_cpp/library/Body.hpp @@ -27,17 +27,6 @@ namespace cucumber_cpp::library using std::runtime_error::runtime_error; }; - struct EventListener : testing::EmptyTestEventListener - { - explicit EventListener(cucumber::messages::test_step_result& testStepResult); - ~EventListener(); - - void OnTestPartResult(const testing::TestPartResult& testPartResult) override; - - private: - cucumber::messages::test_step_result& testStepResult; - }; - struct Body { virtual ~Body() = default; From 6f1f4c8538815b841b0eecbd95252af2850cee86 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 14 Dec 2025 00:45:00 +0000 Subject: [PATCH 016/196] Enable the following compatibility tests: ambiguous, backgrounds, cdata, doc-strings, empty, examples-tables and minimal --- compatibility/ambiguous/ambiguous.cpp | 12 ++ compatibility/backgrounds/backgrounds.cpp | 17 +++ compatibility/cdata/cdata.cpp | 5 + compatibility/compatibility.cpp | 142 ++++++++++-------- compatibility/doc-strings/doc-strings.cpp | 9 ++ compatibility/empty/empty.cpp | 6 - .../examples-tables/examples-tables.cpp | 31 ++++ compatibility/minimal/minimal.cpp | 1 + 8 files changed, 157 insertions(+), 66 deletions(-) create mode 100644 compatibility/ambiguous/ambiguous.cpp create mode 100644 compatibility/backgrounds/backgrounds.cpp create mode 100644 compatibility/cdata/cdata.cpp create mode 100644 compatibility/doc-strings/doc-strings.cpp create mode 100644 compatibility/examples-tables/examples-tables.cpp diff --git a/compatibility/ambiguous/ambiguous.cpp b/compatibility/ambiguous/ambiguous.cpp new file mode 100644 index 00000000..b6c1430e --- /dev/null +++ b/compatibility/ambiguous/ambiguous.cpp @@ -0,0 +1,12 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +STEP(R"(^a (.*?) with (.*?)$)", (const std::string& arg1, const std::string& arg2)) +{ + // no-op +} + +STEP(R"(^a step with (.*)$)", (const std::string& arg1)) +{ + // no-op +} diff --git a/compatibility/backgrounds/backgrounds.cpp b/compatibility/backgrounds/backgrounds.cpp new file mode 100644 index 00000000..72fffe8f --- /dev/null +++ b/compatibility/backgrounds/backgrounds.cpp @@ -0,0 +1,17 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +STEP(R"(an order for {string})", ([[maybe_unused]] const std::string& item)) +{ + // no-op +} + +STEP(R"(an action)") +{ + // no-op +} + +STEP(R"(an outcome)") +{ + // no-op +} diff --git a/compatibility/cdata/cdata.cpp b/compatibility/cdata/cdata.cpp new file mode 100644 index 00000000..5477f624 --- /dev/null +++ b/compatibility/cdata/cdata.cpp @@ -0,0 +1,5 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +GIVEN(R"(I have {int} in my belly)", ([[maybe_unused]] std::int32_t number)) +{} diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index c108f4ca..c157f3ab 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -62,72 +63,107 @@ namespace compatibility void SanitizeExpectedJson(nlohmann::json& json) { - for (auto& [key, value] : json.items()) + for (auto jsonIter = json.begin(); jsonIter != json.end();) { - if (value.is_object()) + auto& key = jsonIter.key(); + auto& value = jsonIter.value(); + + if (key == "parameterTypeName" && value.get().empty()) + jsonIter = json.erase(jsonIter); + else if (key == "exception") + jsonIter = json.erase(jsonIter); + else if (key == "message") + jsonIter = json.erase(jsonIter); + else if (key == "line") + jsonIter = json.erase(jsonIter); + else if (key == "start") + jsonIter = json.erase(jsonIter); + else if (value.is_object()) { SanitizeExpectedJson(value); if (value.size() == 0) - json.erase(key); + jsonIter = json.erase(jsonIter); + else + ++jsonIter; } else if (value.is_array()) { auto idx = 0; - for (auto& item : value) + for (auto valueIter = value.begin(); valueIter != value.end();) { + auto& item = *valueIter; + if (item.is_object()) SanitizeExpectedJson(item); if (item.size() == 0) - value.erase(idx); - - ++idx; + valueIter = value.erase(valueIter); + else + ++valueIter; } if (value.size() == 0) - json.erase(key); + jsonIter = json.erase(jsonIter); + else + ++jsonIter; } else if (key == "uri") { json[key] = std::regex_replace(value.get(), std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); json[key] = std::regex_replace(value.get(), std::regex(R"(\.ts$)"), ".cpp"); + ++jsonIter; } - else if (key == "line") - json.erase(key); - else if (key == "start") - json.erase(key); + else + ++jsonIter; } } void SanitizeActualJson(nlohmann::json& json) { - for (auto& [key, value] : json.items()) + for (auto jsonIter = json.begin(); jsonIter != json.end();) { - if (value.is_object()) + auto& key = jsonIter.key(); + auto& value = jsonIter.value(); + + if (key == "parameterTypeName" && value.get().empty()) + jsonIter = json.erase(jsonIter); + else if (key == "exception") + jsonIter = json.erase(jsonIter); + else if (key == "message") + jsonIter = json.erase(jsonIter); + else if (key == "line") + jsonIter = json.erase(jsonIter); + else if (value.is_object()) { SanitizeActualJson(value); if (value.size() == 0) - json.erase(key); + jsonIter = json.erase(jsonIter); + else + ++jsonIter; } else if (value.is_array()) { auto idx = 0; - for (auto& item : value) + for (auto valueIter = value.begin(); valueIter != value.end();) { + auto& item = *valueIter; + if (item.is_object()) SanitizeActualJson(item); if (item.size() == 0) - value.erase(idx); - - ++idx; + valueIter = value.erase(valueIter); + else + ++valueIter; } if (value.size() == 0) - json.erase(key); + jsonIter = json.erase(jsonIter); + else + ++jsonIter; } - else if (key == "line") - json.erase(key); + else + ++jsonIter; } } @@ -155,8 +191,6 @@ namespace compatibility if (json.contains("meta")) continue; - SanitizeExpectedJson(json); - expectedEnvelopes.emplace_back(std::move(json)); } } @@ -166,42 +200,29 @@ namespace compatibility nlohmann::json actualJson{}; to_json(actualJson, envelope); - if (expectedEnvelopes.empty()) + actualEnvelopes.emplace_back(std::move(actualJson)); + } + + void CompareEnvelopes() + { + ASSERT_THAT(actualEnvelopes.size(), testing::Eq(expectedEnvelopes.size())); + + while (!actualEnvelopes.empty() && !expectedEnvelopes.empty()) { - std::cerr << "Unexpected envelope: " << actualJson.dump() << "\n\n"; - return; - } + auto actualJson = actualEnvelopes.front(); + actualEnvelopes.pop_front(); - SanitizeActualJson(actualJson); - - const auto expectedJson = expectedEnvelopes.front(); - expectedEnvelopes.pop_front(); - - expectedOfs << expectedJson.dump() << "\n"; - actualOfs << actualJson.dump() << "\n"; - - const auto expectedMessage = expectedJson.items().begin().key(); - - const auto diff = nlohmann::json::diff(expectedJson, actualJson); - - EXPECT_THAT(actualJson, testing::Eq(expectedJson)); - // if (actualJson.contains(expectedMessage)) - // { - // if (actualJson[expectedMessage] == expectedJson[expectedMessage]) - // { - // std::cout << "matching!!!! " << expectedMessage << "\n\n"; - // } - // else - // { - // std::cerr << std::format("Mismatch {}: {}\n", expectedMessage, diff.dump()); - // std::cerr << "expected: " << expectedJson[expectedMessage] << "\n"; - // std::cerr << "actual : " << actualJson[expectedMessage] << "\n\n"; - // } - // } - // else - // { - // std::cerr << std::format("Missing {}: {}\n", expectedMessage, diff.dump()); - // } + auto expectedJson = expectedEnvelopes.front(); + expectedEnvelopes.pop_front(); + + SanitizeActualJson(actualJson); + SanitizeExpectedJson(expectedJson); + + expectedOfs << expectedJson.dump() << "\n"; + actualOfs << actualJson.dump() << "\n"; + + EXPECT_THAT(actualJson, testing::Eq(expectedJson)); + } } private: @@ -216,6 +237,7 @@ namespace compatibility std::ofstream actualOfs{ actualndjson }; std::list expectedEnvelopes; + std::list actualEnvelopes; }; bool IsFeatureFile(const std::filesystem::directory_entry& entry) @@ -289,8 +311,8 @@ namespace compatibility BroadcastListener broadcastListener{ devkit.ndjsonFile, devkit.ndjsonFile.parent_path() / "expected.ndjson", devkit.ndjsonFile.parent_path() / "actual.ndjson", broadcaster }; cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster); - // EXPECT_NONFATAL_FAILURE(cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster), ""); - // EXPECT_FATAL_FAILURE(cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster), ""); + + broadcastListener.CompareEnvelopes(); } } } diff --git a/compatibility/doc-strings/doc-strings.cpp b/compatibility/doc-strings/doc-strings.cpp new file mode 100644 index 00000000..b30c03ff --- /dev/null +++ b/compatibility/doc-strings/doc-strings.cpp @@ -0,0 +1,9 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include "gmock/gmock.h" +#include + +STEP(R"(a doc string:)") +{ + ASSERT_THAT(docString, testing::IsTrue()); + ASSERT_THAT(docString->content, testing::Not(testing::IsEmpty())); +} diff --git a/compatibility/empty/empty.cpp b/compatibility/empty/empty.cpp index ff514795..e69de29b 100644 --- a/compatibility/empty/empty.cpp +++ b/compatibility/empty/empty.cpp @@ -1,6 +0,0 @@ -#include -#include - -namespace cucumber_cpp::devkit -{ -} diff --git a/compatibility/examples-tables/examples-tables.cpp b/compatibility/examples-tables/examples-tables.cpp new file mode 100644 index 00000000..000fd1e2 --- /dev/null +++ b/compatibility/examples-tables/examples-tables.cpp @@ -0,0 +1,31 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include +#include + +GIVEN(R"(there are {int} cucumbers)", (std::int32_t cucumbers)) +{ + context.InsertAt("cucumbers", cucumbers); +} + +GIVEN(R"(there are {int} friends)", (std::int32_t friends)) +{ + context.InsertAt("friends", friends); +} + +WHEN(R"(I eat {int} cucumbers)", (std::int32_t eatCount)) +{ + context.Get("cucumbers") -= eatCount; +} + +THEN(R"(I should have {int} cucumbers)", (std::int32_t expectedCount)) +{ + ASSERT_THAT(context.Get("cucumbers"), testing::Eq(expectedCount)); +} + +THEN(R"(each person can eat {int} cucumbers)", (std::int32_t expectedShare)) +{ + const auto share = context.Get("cucumbers") / (1 + context.Get("friends")); + ASSERT_THAT(share, testing::Eq(expectedShare)); +} diff --git a/compatibility/minimal/minimal.cpp b/compatibility/minimal/minimal.cpp index aa40c108..805969b1 100644 --- a/compatibility/minimal/minimal.cpp +++ b/compatibility/minimal/minimal.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/CucumberCpp.hpp" #include + STEP(R"(I have {int} cukes in my belly)", (std::int32_t number)) { // no-op From 07a77d7eea8374c75e32b0b4fe85c2bf51bd04b4 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 15 Dec 2025 10:44:30 +0000 Subject: [PATCH 017/196] add examples-tables-attachment compatibility test --- .../examples-tables-attachment.cpp | 24 + cucumber_cpp/library/StepRegistry.hpp | 9 +- cucumber_cpp/library/engine/CMakeLists.txt | 3 + .../library/engine/ExecutionContext.cpp | 107 +++ .../library/engine/ExecutionContext.hpp | 45 ++ cucumber_cpp/library/engine/Step.cpp | 9 +- cucumber_cpp/library/engine/Step.hpp | 8 +- .../library/runtime/TestCaseRunner.cpp | 17 +- .../library/runtime/TestCaseRunner.hpp | 3 +- external/CMakeLists.txt | 1 + external/tobiaslocker/CMakeLists.txt | 1 + external/tobiaslocker/base64/CMakeLists.txt | 4 + external/tobiaslocker/base64/LICENSE | 21 + external/tobiaslocker/base64/README.md | 48 ++ .../tobiaslocker/base64/include/base64.hpp | 737 ++++++++++++++++++ 15 files changed, 1016 insertions(+), 21 deletions(-) create mode 100644 compatibility/examples-tables-attachment/examples-tables-attachment.cpp create mode 100644 cucumber_cpp/library/engine/ExecutionContext.cpp create mode 100644 cucumber_cpp/library/engine/ExecutionContext.hpp create mode 100644 external/tobiaslocker/CMakeLists.txt create mode 100644 external/tobiaslocker/base64/CMakeLists.txt create mode 100644 external/tobiaslocker/base64/LICENSE create mode 100644 external/tobiaslocker/base64/README.md create mode 100644 external/tobiaslocker/base64/include/base64.hpp diff --git a/compatibility/examples-tables-attachment/examples-tables-attachment.cpp b/compatibility/examples-tables-attachment/examples-tables-attachment.cpp new file mode 100644 index 00000000..1588ef0a --- /dev/null +++ b/compatibility/examples-tables-attachment/examples-tables-attachment.cpp @@ -0,0 +1,24 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include +#include +#include +#include +#include +#include + +namespace +{ + const std::filesystem::path currentCompileDir = std::filesystem::path{ std::source_location::current().file_name() }.parent_path(); +} + +WHEN(R"(a JPEG image is attached)") +{ + std::ifstream jpegFile{ currentCompileDir / "cucumber.jpeg", std::ios::binary }; + Attach(jpegFile, "image/jpeg"); +} + +WHEN(R"(a PNG image is attached)") +{ + std::ifstream pngFile{ currentCompileDir / "cucumber.png", std::ios::binary }; + Attach(pngFile, "image/png"); +} diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index 7c58383b..e7d513f8 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -10,7 +10,9 @@ #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include @@ -29,13 +31,12 @@ namespace cucumber_cpp::library { - - using StepFactory = std::unique_ptr (&)(Context&, std::optional>, const std::optional&); + using StepFactory = std::unique_ptr (&)(util::Broadcaster& broadCaster, Context&, engine::StepOrHookStarted stepOrHookStarted, std::optional>, const std::optional&); template - std::unique_ptr StepBodyFactory(Context& context, std::optional> table, const std::optional& docString) + std::unique_ptr StepBodyFactory(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, std::optional> table, const std::optional& docString) { - return std::make_unique(context, table, docString); + return std::make_unique(broadCaster, context, stepOrHookStarted, table, docString); } struct StepMatch diff --git a/cucumber_cpp/library/engine/CMakeLists.txt b/cucumber_cpp/library/engine/CMakeLists.txt index f1c914ff..5297e8c3 100644 --- a/cucumber_cpp/library/engine/CMakeLists.txt +++ b/cucumber_cpp/library/engine/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(cucumber_cpp.library.engine STATIC ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.engine PRIVATE + ExecutionContext.cpp + ExecutionContext.hpp Step.cpp Step.hpp StepType.hpp @@ -12,6 +14,7 @@ target_include_directories(cucumber_cpp.library.engine PUBLIC ) target_link_libraries(cucumber_cpp.library.engine PUBLIC + base64 cucumber_cpp.library cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression diff --git a/cucumber_cpp/library/engine/ExecutionContext.cpp b/cucumber_cpp/library/engine/ExecutionContext.cpp new file mode 100644 index 00000000..fcf06792 --- /dev/null +++ b/cucumber_cpp/library/engine/ExecutionContext.cpp @@ -0,0 +1,107 @@ +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "base64.hpp" +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/attachment_content_encoding.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::engine +{ + namespace + { + constexpr auto LogMediaType{ "text/x.cucumber.log+plain" }; + constexpr auto LinkMediaType{ "text/uri-list" }; + + std::pair, std::optional> ReadTestStepStartedIds(StepOrHookStarted stepOrHookStarted) + { + if (std::holds_alternative(stepOrHookStarted)) + { + return { + std::get(stepOrHookStarted).test_case_started_id, + std::get(stepOrHookStarted).test_step_id, + }; + } + + return {}; + } + + std::pair, std::optional> ReadTestRunHookStartedIds(StepOrHookStarted stepOrHookStarted) + { + if (std::holds_alternative(stepOrHookStarted)) + { + return { + std::get(stepOrHookStarted).test_run_started_id, + std::get(stepOrHookStarted).id, + }; + } + + return {}; + } + } + + ExecutionContext::ExecutionContext(util::Broadcaster& broadCaster, Context& context, StepOrHookStarted stepOrHookStarted) + : context{ context } + , broadCaster{ broadCaster } + , stepOrHookStarted{ std::move(stepOrHookStarted) } + {} + + void ExecutionContext::Attach(std::string data, OptionsOrMediaType mediaType) + { + Attach(std::move(data), cucumber::messages::attachment_content_encoding::IDENTITY, std::move(mediaType)); + } + + void ExecutionContext::Attach(std::istream& data, OptionsOrMediaType mediaType) + { + std::string buffer{ std::istreambuf_iterator{ data }, std::istreambuf_iterator{} }; + + buffer = base64::to_base64(buffer); + + Attach(std::move(buffer), cucumber::messages::attachment_content_encoding::BASE64, mediaType); + } + + void ExecutionContext::Log(std::string text) + { + Attach(std::move(text), std::string{ LogMediaType }); + } + + void ExecutionContext::Link(std::string url, std::optional title) + { + Attach(std::move(url), AttachOptions{ + .mediaType = LinkMediaType, + .fileName = std::move(title), + }); + } + + void ExecutionContext::Attach(std::string data, cucumber::messages::attachment_content_encoding encoding, OptionsOrMediaType mediaType) + { + const auto options = std::holds_alternative(mediaType) + ? AttachOptions{ .mediaType = std::get(mediaType) } + : std::get(mediaType); + + auto [test_case_started_id, test_step_id] = ReadTestStepStartedIds(stepOrHookStarted); + auto [test_run_started_id, test_run_hook_started_id] = ReadTestRunHookStartedIds(stepOrHookStarted); + + broadCaster.BroadcastEvent({ + .attachment = cucumber::messages::attachment{ + .body = std::move(data), + .content_encoding = encoding, + .file_name = std::move(options.fileName), + .media_type = std::move(options.mediaType), + .test_case_started_id = std::move(test_case_started_id), + .test_step_id = std::move(test_step_id), + .test_run_started_id = std::move(test_run_started_id), + .test_run_hook_started_id = std::move(test_run_hook_started_id), + .timestamp = support::TimestampNow(), + }, + }); + } +} diff --git a/cucumber_cpp/library/engine/ExecutionContext.hpp b/cucumber_cpp/library/engine/ExecutionContext.hpp new file mode 100644 index 00000000..85e70a17 --- /dev/null +++ b/cucumber_cpp/library/engine/ExecutionContext.hpp @@ -0,0 +1,45 @@ +#ifndef ENGINE_EXECUTION_CONTEXT_HPP +#define ENGINE_EXECUTION_CONTEXT_HPP + +#include "cucumber/messages/attachment_content_encoding.hpp" +#include "cucumber/messages/test_run_hook_started.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include + +namespace cucumber_cpp::library::engine +{ + struct AttachOptions + { + std::string mediaType; + std::optional fileName; + }; + + using OptionsOrMediaType = std::variant; + using StepOrHookStarted = std::variant; + + struct ExecutionContext + { + ExecutionContext(util::Broadcaster& broadCaster, Context& context, StepOrHookStarted stepOrHookStarted); + + protected: + void Attach(std::string data, OptionsOrMediaType mediaType); + void Attach(std::istream& data, OptionsOrMediaType mediaType); + void Log(std::string text); + void Link(std::string url, std::optional title); + + Context& context; + + private: + void Attach(std::string data, cucumber::messages::attachment_content_encoding encoding, OptionsOrMediaType mediaType); + + util::Broadcaster& broadCaster; + StepOrHookStarted stepOrHookStarted; + }; +} + +#endif diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index f5c2c24b..f8daa557 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -1,9 +1,10 @@ #include "cucumber_cpp/library/engine/Step.hpp" -#include "cucumber/messages/doc_string.hpp" +#include "cucumber/messages/pickle_doc_string.hpp" #include "cucumber/messages/pickle_table_row.hpp" #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Context.hpp" -#include +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include @@ -11,8 +12,8 @@ namespace cucumber_cpp::library::engine { - Step::Step(Context& context, std::optional> table, const std::optional& docString) - : context{ context } + Step::Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, std::optional> table, const std::optional& docString) + : ExecutionContext{ broadCaster, context, stepOrHookStarted } , table{ table } , docString{ docString } {} diff --git a/cucumber_cpp/library/engine/Step.hpp b/cucumber_cpp/library/engine/Step.hpp index 296a1940..b57d4b84 100644 --- a/cucumber_cpp/library/engine/Step.hpp +++ b/cucumber_cpp/library/engine/Step.hpp @@ -7,8 +7,9 @@ #include "cucumber/messages/pickle_doc_string.hpp" #include "cucumber/messages/pickle_table_row.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include -#include #include #include #include @@ -17,7 +18,7 @@ namespace cucumber_cpp::library::engine { - struct Step + struct Step : ExecutionContext { struct StepPending : std::exception { @@ -31,7 +32,7 @@ namespace cucumber_cpp::library::engine std::source_location sourceLocation; }; - Step(Context& context, std::optional> table, const std::optional& docString); + Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, std::optional> table, const std::optional& docString); virtual ~Step() = default; virtual void SetUp() @@ -51,7 +52,6 @@ namespace cucumber_cpp::library::engine [[noreturn]] static void Pending(const std::string& message, std::source_location current = std::source_location::current()) noexcept(false); - Context& context; std::optional> table; const std::optional& docString; }; diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index fd1035de..855e3247 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -133,11 +133,12 @@ namespace cucumber_cpp::library::runtime for (const auto& testStep : testCase.test_steps) { - broadcaster.BroadcastEvent({ .test_step_started = cucumber::messages::test_step_started{ - .test_case_started_id = currentTestCaseStartedId, - .test_step_id = testStep.id, - .timestamp = support::TimestampNow(), - } }); + auto testStepStarted = cucumber::messages::test_step_started{ + .test_case_started_id = currentTestCaseStartedId, + .test_step_id = testStep.id, + .timestamp = support::TimestampNow(), + }; + broadcaster.BroadcastEvent({ .test_step_started = testStepStarted }); cucumber::messages::test_step_result testStepResult; @@ -148,7 +149,7 @@ namespace cucumber_cpp::library::runtime else { auto pickleStepIter = std::ranges::find(pickle.steps, testStep.pickle_step_id.value(), &cucumber::messages::pickle_step::id); - testStepResult = RunStep(*pickleStepIter, testStep, testCaseContext); + testStepResult = RunStep(*pickleStepIter, testStep, testCaseContext, testStepStarted); seenSteps = true; } testStepResults.emplace_back(testStepResult); @@ -198,7 +199,7 @@ namespace cucumber_cpp::library::runtime return results; } - cucumber::messages::test_step_result TestCaseRunner::RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext) + cucumber::messages::test_step_result TestCaseRunner::RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted) { auto stepDefinitions = (*testStep.step_definition_ids) | std::views::transform([&](const std::string& id) { @@ -257,7 +258,7 @@ namespace cucumber_cpp::library::runtime return std::nullopt; }; - const auto result = InvokeStep(definition.factory(testCaseContext, toOptionalTable(pickleStep), pickleStep.argument ? pickleStep.argument->doc_string : std::nullopt), parameters); + const auto result = InvokeStep(definition.factory(broadcaster, testCaseContext, testStepStarted, toOptionalTable(pickleStep), pickleStep.argument ? pickleStep.argument->doc_string : std::nullopt), parameters); stepResults.push_back(result); } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.hpp b/cucumber_cpp/library/runtime/TestCaseRunner.hpp index 0cabc603..e16a637a 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.hpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.hpp @@ -9,6 +9,7 @@ #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber/messages/test_step_started.hpp" #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" @@ -40,7 +41,7 @@ namespace cucumber_cpp::library::runtime std::vector RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext); - cucumber::messages::test_step_result RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext); + cucumber::messages::test_step_result RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); cucumber::messages::test_step_result InvokeStep(std::unique_ptr body, const ExecuteArgs& args = {}); cucumber::messages::test_step_result GetWorstStepResult() const; diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 1cd0d97a..86bda483 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -7,4 +7,5 @@ add_subdirectory(cucumber) add_subdirectory(googletest) add_subdirectory(jbeder) add_subdirectory(jupyter-xeus) +add_subdirectory(tobiaslocker) add_subdirectory(zeux) diff --git a/external/tobiaslocker/CMakeLists.txt b/external/tobiaslocker/CMakeLists.txt new file mode 100644 index 00000000..f453a035 --- /dev/null +++ b/external/tobiaslocker/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(base64) diff --git a/external/tobiaslocker/base64/CMakeLists.txt b/external/tobiaslocker/base64/CMakeLists.txt new file mode 100644 index 00000000..d678405f --- /dev/null +++ b/external/tobiaslocker/base64/CMakeLists.txt @@ -0,0 +1,4 @@ +# https://github.com/tobiaslocker/base64/tree/8d96a2a737ac1396304b1de289beb3a5ea0cb752 + +add_library(base64 INTERFACE) +target_include_directories(base64 INTERFACE include) diff --git a/external/tobiaslocker/base64/LICENSE b/external/tobiaslocker/base64/LICENSE new file mode 100644 index 00000000..ffa390dd --- /dev/null +++ b/external/tobiaslocker/base64/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Tobias Locker + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/external/tobiaslocker/base64/README.md b/external/tobiaslocker/base64/README.md new file mode 100644 index 00000000..2c1f4756 --- /dev/null +++ b/external/tobiaslocker/base64/README.md @@ -0,0 +1,48 @@ +# base64 + +A simple, header-only C++17 library for Base64 encoding and decoding. Uses modern C++ features when available +(e.g., `std::bit_cast` in C++20) while remaining portable and efficient. + +## Features + +- Header-only, single-file library +- Fast encoding/decoding using lookup tables +- Safe type punning using `std::bit_cast` (avoids undefined behavior with unions) +- Throws `std::runtime_error` for invalid Base64 input (size, padding, or characters) + +## Platform Support + +- Compilers: GCC, Clang, MSVC +- Architectures: x86, x64, ARM, AArch64 (little-endian) +- Requires C++17 (C++20 features optional) + +## Usage + +```cpp +#include +#include "base64.hpp" + +int main() { + auto encoded = base64::to_base64("Hello, World!"); + std::cout << encoded << std::endl; // SGVsbG8sIFdvcmxkIQ== + + auto decoded = base64::from_base64("SGVsbG8sIFdvcmxkIQ=="); + std::cout << decoded << std::endl; // Hello, World! +} +``` + +## Notes + +- Inspired by Nick Galbreath's modp_b64 (used by Chromium) for high performance +- Compatible with C++17; optionally uses C++20 features +- Avoids union-based type punning for safety +- Faster implementations exist using SIMD or multithreading but are not header-only + +## References + +- Benchmark of C/C++ Base64 libraries: https://github.com/gaspardpetit/base64/ +- Chromium modp_b64: https://github.com/chromium/chromium/tree/main/third_party/modp_b64 +- SIMD/optimized alternatives: + - https://github.com/aklomp/base64 + - https://github.com/simdutf/simdutf + - https://github.com/powturbo/Turbo-Base64 diff --git a/external/tobiaslocker/base64/include/base64.hpp b/external/tobiaslocker/base64/include/base64.hpp new file mode 100644 index 00000000..c7df5e43 --- /dev/null +++ b/external/tobiaslocker/base64/include/base64.hpp @@ -0,0 +1,737 @@ +#ifndef BASE64_HPP_ +#define BASE64_HPP_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if defined(__cpp_lib_bit_cast) +#include // For std::bit_cast. +#endif + +namespace base64 +{ + namespace detail + { + +#if defined(__cpp_lib_bit_cast) + using std::bit_cast; +#else + template + std::enable_if_t && + std::is_trivially_copyable_v, + To> + bit_cast(const From& src) noexcept + { + static_assert(std::is_trivially_constructible_v, + "This implementation additionally requires " + "destination type to be trivially constructible"); + + To dst; + std::memcpy(&dst, &src, sizeof(To)); + return dst; + } +#endif + + inline constexpr char padding_char{ '=' }; + inline constexpr uint32_t bad_char{ 0x01FFFFFF }; + +#if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__) +#if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) || \ + (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) || \ + (defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN) || \ + (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) || \ + (defined(__sun) && defined(__SVR4) && defined(_BIG_ENDIAN)) || \ + defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \ + defined(_MIBSEB) || defined(__MIBSEB) || defined(__MIBSEB__) || \ + defined(_M_PPC) +#define __BIG_ENDIAN__ +#elif (defined(__BYTE_ORDER__) && \ + __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || /* gcc */ \ + (defined(__BYTE_ORDER) && \ + __BYTE_ORDER == __LITTLE_ENDIAN) /* linux header */ \ + || (defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN) || \ + (defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN) /* mingw header */ || \ + (defined(__sun) && defined(__SVR4) && \ + defined(_LITTLE_ENDIAN)) || /* solaris */ \ + defined(__ARMEL__) || \ + defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) || \ + defined(__MIPSEL) || defined(__MIPSEL__) || defined(_M_IX86) || \ + defined(_M_X64) || defined(_M_IA64) || /* msvc for intel processors */ \ + defined(_M_ARM) || \ + defined(_M_ARM64) /* msvc code on arm executes in little endian mode */ +#define __LITTLE_ENDIAN__ +#endif +#endif + +#if !defined(__LITTLE_ENDIAN__) & !defined(__BIG_ENDIAN__) +#error "UNKNOWN Platform / endianness. Configure endianness explicitly." +#endif + +#if defined(__LITTLE_ENDIAN__) + std::array constexpr decode_table_0 = { + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x000000f8, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x000000fc, + 0x000000d0, 0x000000d4, 0x000000d8, 0x000000dc, 0x000000e0, 0x000000e4, + 0x000000e8, 0x000000ec, 0x000000f0, 0x000000f4, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, + 0x00000004, 0x00000008, 0x0000000c, 0x00000010, 0x00000014, 0x00000018, + 0x0000001c, 0x00000020, 0x00000024, 0x00000028, 0x0000002c, 0x00000030, + 0x00000034, 0x00000038, 0x0000003c, 0x00000040, 0x00000044, 0x00000048, + 0x0000004c, 0x00000050, 0x00000054, 0x00000058, 0x0000005c, 0x00000060, + 0x00000064, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x00000068, 0x0000006c, 0x00000070, 0x00000074, 0x00000078, + 0x0000007c, 0x00000080, 0x00000084, 0x00000088, 0x0000008c, 0x00000090, + 0x00000094, 0x00000098, 0x0000009c, 0x000000a0, 0x000000a4, 0x000000a8, + 0x000000ac, 0x000000b0, 0x000000b4, 0x000000b8, 0x000000bc, 0x000000c0, + 0x000000c4, 0x000000c8, 0x000000cc, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff + }; + + std::array constexpr decode_table_1 = { + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x0000e003, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000f003, + 0x00004003, 0x00005003, 0x00006003, 0x00007003, 0x00008003, 0x00009003, + 0x0000a003, 0x0000b003, 0x0000c003, 0x0000d003, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, + 0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, + 0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, + 0x0000d000, 0x0000e000, 0x0000f000, 0x00000001, 0x00001001, 0x00002001, + 0x00003001, 0x00004001, 0x00005001, 0x00006001, 0x00007001, 0x00008001, + 0x00009001, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x0000a001, 0x0000b001, 0x0000c001, 0x0000d001, 0x0000e001, + 0x0000f001, 0x00000002, 0x00001002, 0x00002002, 0x00003002, 0x00004002, + 0x00005002, 0x00006002, 0x00007002, 0x00008002, 0x00009002, 0x0000a002, + 0x0000b002, 0x0000c002, 0x0000d002, 0x0000e002, 0x0000f002, 0x00000003, + 0x00001003, 0x00002003, 0x00003003, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff + }; + + std::array constexpr decode_table_2 = { + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x00800f00, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00c00f00, + 0x00000d00, 0x00400d00, 0x00800d00, 0x00c00d00, 0x00000e00, 0x00400e00, + 0x00800e00, 0x00c00e00, 0x00000f00, 0x00400f00, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, + 0x00400000, 0x00800000, 0x00c00000, 0x00000100, 0x00400100, 0x00800100, + 0x00c00100, 0x00000200, 0x00400200, 0x00800200, 0x00c00200, 0x00000300, + 0x00400300, 0x00800300, 0x00c00300, 0x00000400, 0x00400400, 0x00800400, + 0x00c00400, 0x00000500, 0x00400500, 0x00800500, 0x00c00500, 0x00000600, + 0x00400600, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x00800600, 0x00c00600, 0x00000700, 0x00400700, 0x00800700, + 0x00c00700, 0x00000800, 0x00400800, 0x00800800, 0x00c00800, 0x00000900, + 0x00400900, 0x00800900, 0x00c00900, 0x00000a00, 0x00400a00, 0x00800a00, + 0x00c00a00, 0x00000b00, 0x00400b00, 0x00800b00, 0x00c00b00, 0x00000c00, + 0x00400c00, 0x00800c00, 0x00c00c00, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff + }; + + std::array constexpr decode_table_3 = { + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x003e0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x003f0000, + 0x00340000, 0x00350000, 0x00360000, 0x00370000, 0x00380000, 0x00390000, + 0x003a0000, 0x003b0000, 0x003c0000, 0x003d0000, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, + 0x00010000, 0x00020000, 0x00030000, 0x00040000, 0x00050000, 0x00060000, + 0x00070000, 0x00080000, 0x00090000, 0x000a0000, 0x000b0000, 0x000c0000, + 0x000d0000, 0x000e0000, 0x000f0000, 0x00100000, 0x00110000, 0x00120000, + 0x00130000, 0x00140000, 0x00150000, 0x00160000, 0x00170000, 0x00180000, + 0x00190000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x001a0000, 0x001b0000, 0x001c0000, 0x001d0000, 0x001e0000, + 0x001f0000, 0x00200000, 0x00210000, 0x00220000, 0x00230000, 0x00240000, + 0x00250000, 0x00260000, 0x00270000, 0x00280000, 0x00290000, 0x002a0000, + 0x002b0000, 0x002c0000, 0x002d0000, 0x002e0000, 0x002f0000, 0x00300000, + 0x00310000, 0x00320000, 0x00330000, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff + }; + + // TODO fix decoding tables to avoid the need for different indices in big + // endian? + inline constexpr size_t decidx0{ 0 }; + inline constexpr size_t decidx1{ 1 }; + inline constexpr size_t decidx2{ 2 }; + +#elif defined(__BIG_ENDIAN__) + + std::array constexpr decode_table_0 = { + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x00f80000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00fc0000, + 0x00d00000, 0x00d40000, 0x00d80000, 0x00dc0000, 0x00e00000, 0x00e40000, + 0x00e80000, 0x00ec0000, 0x00f00000, 0x00f40000, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, + 0x00040000, 0x00080000, 0x000c0000, 0x00100000, 0x00140000, 0x00180000, + 0x001c0000, 0x00200000, 0x00240000, 0x00280000, 0x002c0000, 0x00300000, + 0x00340000, 0x00380000, 0x003c0000, 0x00400000, 0x00440000, 0x00480000, + 0x004c0000, 0x00500000, 0x00540000, 0x00580000, 0x005c0000, 0x00600000, + 0x00640000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x00680000, 0x006c0000, 0x00700000, 0x00740000, 0x00780000, + 0x007c0000, 0x00800000, 0x00840000, 0x00880000, 0x008c0000, 0x00900000, + 0x00940000, 0x00980000, 0x009c0000, 0x00a00000, 0x00a40000, 0x00a80000, + 0x00ac0000, 0x00b00000, 0x00b40000, 0x00b80000, 0x00bc0000, 0x00c00000, + 0x00c40000, 0x00c80000, 0x00cc0000, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff + }; + + std::array constexpr decode_table_1 = { + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x0003e000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0003f000, + 0x00034000, 0x00035000, 0x00036000, 0x00037000, 0x00038000, 0x00039000, + 0x0003a000, 0x0003b000, 0x0003c000, 0x0003d000, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, + 0x00001000, 0x00002000, 0x00003000, 0x00004000, 0x00005000, 0x00006000, + 0x00007000, 0x00008000, 0x00009000, 0x0000a000, 0x0000b000, 0x0000c000, + 0x0000d000, 0x0000e000, 0x0000f000, 0x00010000, 0x00011000, 0x00012000, + 0x00013000, 0x00014000, 0x00015000, 0x00016000, 0x00017000, 0x00018000, + 0x00019000, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x0001a000, 0x0001b000, 0x0001c000, 0x0001d000, 0x0001e000, + 0x0001f000, 0x00020000, 0x00021000, 0x00022000, 0x00023000, 0x00024000, + 0x00025000, 0x00026000, 0x00027000, 0x00028000, 0x00029000, 0x0002a000, + 0x0002b000, 0x0002c000, 0x0002d000, 0x0002e000, 0x0002f000, 0x00030000, + 0x00031000, 0x00032000, 0x00033000, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff + }; + + std::array constexpr decode_table_2 = { + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x00000f80, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000fc0, + 0x00000d00, 0x00000d40, 0x00000d80, 0x00000dc0, 0x00000e00, 0x00000e40, + 0x00000e80, 0x00000ec0, 0x00000f00, 0x00000f40, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, + 0x00000040, 0x00000080, 0x000000c0, 0x00000100, 0x00000140, 0x00000180, + 0x000001c0, 0x00000200, 0x00000240, 0x00000280, 0x000002c0, 0x00000300, + 0x00000340, 0x00000380, 0x000003c0, 0x00000400, 0x00000440, 0x00000480, + 0x000004c0, 0x00000500, 0x00000540, 0x00000580, 0x000005c0, 0x00000600, + 0x00000640, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x00000680, 0x000006c0, 0x00000700, 0x00000740, 0x00000780, + 0x000007c0, 0x00000800, 0x00000840, 0x00000880, 0x000008c0, 0x00000900, + 0x00000940, 0x00000980, 0x000009c0, 0x00000a00, 0x00000a40, 0x00000a80, + 0x00000ac0, 0x00000b00, 0x00000b40, 0x00000b80, 0x00000bc0, 0x00000c00, + 0x00000c40, 0x00000c80, 0x00000cc0, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff + }; + + std::array constexpr decode_table_3 = { + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x0000003e, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x0000003f, + 0x00000034, 0x00000035, 0x00000036, 0x00000037, 0x00000038, 0x00000039, + 0x0000003a, 0x0000003b, 0x0000003c, 0x0000003d, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x00000000, + 0x00000001, 0x00000002, 0x00000003, 0x00000004, 0x00000005, 0x00000006, + 0x00000007, 0x00000008, 0x00000009, 0x0000000a, 0x0000000b, 0x0000000c, + 0x0000000d, 0x0000000e, 0x0000000f, 0x00000010, 0x00000011, 0x00000012, + 0x00000013, 0x00000014, 0x00000015, 0x00000016, 0x00000017, 0x00000018, + 0x00000019, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x0000001a, 0x0000001b, 0x0000001c, 0x0000001d, 0x0000001e, + 0x0000001f, 0x00000020, 0x00000021, 0x00000022, 0x00000023, 0x00000024, + 0x00000025, 0x00000026, 0x00000027, 0x00000028, 0x00000029, 0x0000002a, + 0x0000002b, 0x0000002c, 0x0000002d, 0x0000002e, 0x0000002f, 0x00000030, + 0x00000031, 0x00000032, 0x00000033, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff, + 0x01ffffff, 0x01ffffff, 0x01ffffff, 0x01ffffff + }; + + // TODO fix decoding tables to avoid the need for different indices in big + // endian? + inline constexpr size_t decidx0{ 1 }; + inline constexpr size_t decidx1{ 2 }; + inline constexpr size_t decidx2{ 3 }; + +#endif + + std::array constexpr encode_table_0 = { + 'A', 'A', 'A', 'A', 'B', 'B', 'B', 'B', 'C', 'C', 'C', 'C', 'D', 'D', 'D', + 'D', 'E', 'E', 'E', 'E', 'F', 'F', 'F', 'F', 'G', 'G', 'G', 'G', 'H', 'H', + 'H', 'H', 'I', 'I', 'I', 'I', 'J', 'J', 'J', 'J', 'K', 'K', 'K', 'K', 'L', + 'L', 'L', 'L', 'M', 'M', 'M', 'M', 'N', 'N', 'N', 'N', 'O', 'O', 'O', 'O', + 'P', 'P', 'P', 'P', 'Q', 'Q', 'Q', 'Q', 'R', 'R', 'R', 'R', 'S', 'S', 'S', + 'S', 'T', 'T', 'T', 'T', 'U', 'U', 'U', 'U', 'V', 'V', 'V', 'V', 'W', 'W', + 'W', 'W', 'X', 'X', 'X', 'X', 'Y', 'Y', 'Y', 'Y', 'Z', 'Z', 'Z', 'Z', 'a', + 'a', 'a', 'a', 'b', 'b', 'b', 'b', 'c', 'c', 'c', 'c', 'd', 'd', 'd', 'd', + 'e', 'e', 'e', 'e', 'f', 'f', 'f', 'f', 'g', 'g', 'g', 'g', 'h', 'h', 'h', + 'h', 'i', 'i', 'i', 'i', 'j', 'j', 'j', 'j', 'k', 'k', 'k', 'k', 'l', 'l', + 'l', 'l', 'm', 'm', 'm', 'm', 'n', 'n', 'n', 'n', 'o', 'o', 'o', 'o', 'p', + 'p', 'p', 'p', 'q', 'q', 'q', 'q', 'r', 'r', 'r', 'r', 's', 's', 's', 's', + 't', 't', 't', 't', 'u', 'u', 'u', 'u', 'v', 'v', 'v', 'v', 'w', 'w', 'w', + 'w', 'x', 'x', 'x', 'x', 'y', 'y', 'y', 'y', 'z', 'z', 'z', 'z', '0', '0', + '0', '0', '1', '1', '1', '1', '2', '2', '2', '2', '3', '3', '3', '3', '4', + '4', '4', '4', '5', '5', '5', '5', '6', '6', '6', '6', '7', '7', '7', '7', + '8', '8', '8', '8', '9', '9', '9', '9', '+', '+', '+', '+', '/', '/', '/', + '/' + }; + + std::array constexpr encode_table_1 = { + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', + 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', + 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', + 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', + 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', + 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', + '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', + 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', + 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', + 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', + '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', + 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', + 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', + 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', + 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', + '/' + }; + + } // namespace detail + + template + inline OutputBuffer encode_into(InputIterator begin, InputIterator end) + { + typedef std::decay_t input_value_type; + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v); + typedef typename OutputBuffer::value_type output_value_type; + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v); + const size_t binarytextsize = end - begin; + const size_t encodedsize = (binarytextsize / 3 + (binarytextsize % 3 > 0)) + << 2; + OutputBuffer encoded(encodedsize, detail::padding_char); + + const uint8_t* bytes = reinterpret_cast(&*begin); + char* currEncoding = reinterpret_cast(&encoded[0]); + + for (size_t i = binarytextsize / 3; i; --i) + { + const uint8_t t1 = *bytes++; + const uint8_t t2 = *bytes++; + const uint8_t t3 = *bytes++; + *currEncoding++ = detail::encode_table_0[t1]; + *currEncoding++ = + detail::encode_table_1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *currEncoding++ = + detail::encode_table_1[((t2 & 0x0F) << 2) | ((t3 >> 6) & 0x03)]; + *currEncoding++ = detail::encode_table_1[t3]; + } + + switch (binarytextsize % 3) + { + case 0: + { + break; + } + case 1: + { + const uint8_t t1 = bytes[0]; + *currEncoding++ = detail::encode_table_0[t1]; + *currEncoding++ = detail::encode_table_1[(t1 & 0x03) << 4]; + break; + } + case 2: + { + const uint8_t t1 = bytes[0]; + const uint8_t t2 = bytes[1]; + *currEncoding++ = detail::encode_table_0[t1]; + *currEncoding++ = + detail::encode_table_1[((t1 & 0x03) << 4) | ((t2 >> 4) & 0x0F)]; + *currEncoding++ = detail::encode_table_1[(t2 & 0x0F) << 2]; + break; + } + default: + { + throw std::runtime_error{ "Invalid base64 encoded data" }; + } + } + + return encoded; + } + + template + inline OutputBuffer encode_into(std::string_view data) + { + return encode_into(std::begin(data), std::end(data)); + } + + inline std::string to_base64(std::string_view data) + { + return encode_into(std::begin(data), std::end(data)); + } + + template + inline OutputBuffer decode_into(std::string_view base64Text) + { + typedef typename OutputBuffer::value_type output_value_type; + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v); + if (base64Text.empty()) + { + return OutputBuffer(); + } + + if ((base64Text.size() & 3) != 0) + { + throw std::runtime_error{ + "Invalid base64 encoded data - Size not divisible by 4" + }; + } + + const size_t numPadding = + std::count(base64Text.rbegin(), base64Text.rbegin() + 4, '='); + if (numPadding > 2) + { + throw std::runtime_error{ + "Invalid base64 encoded data - Found more than 2 padding signs" + }; + } + + const size_t decodedsize = (base64Text.size() * 3 >> 2) - numPadding; + OutputBuffer decoded(decodedsize, '.'); + + const uint8_t* bytes = reinterpret_cast(&base64Text[0]); + char* currDecoding = reinterpret_cast(&decoded[0]); + + for (size_t i = (base64Text.size() >> 2) - (numPadding != 0); i; --i) + { + const uint8_t t1 = *bytes++; + const uint8_t t2 = *bytes++; + const uint8_t t3 = *bytes++; + const uint8_t t4 = *bytes++; + + const uint32_t d1 = detail::decode_table_0[t1]; + const uint32_t d2 = detail::decode_table_1[t2]; + const uint32_t d3 = detail::decode_table_2[t3]; + const uint32_t d4 = detail::decode_table_3[t4]; + + const uint32_t temp = d1 | d2 | d3 | d4; + + if (temp >= detail::bad_char) + { + throw std::runtime_error{ + "Invalid base64 encoded data - Invalid character" + }; + } + + // Use bit_cast instead of union and type punning to avoid + // undefined behaviour risk: + // https://en.wikipedia.org/wiki/Type_punning#Use_of_union + const std::array tempBytes = + detail::bit_cast, uint32_t>(temp); + + *currDecoding++ = tempBytes[detail::decidx0]; + *currDecoding++ = tempBytes[detail::decidx1]; + *currDecoding++ = tempBytes[detail::decidx2]; + } + + switch (numPadding) + { + case 0: + { + break; + } + case 1: + { + const uint8_t t1 = *bytes++; + const uint8_t t2 = *bytes++; + const uint8_t t3 = *bytes++; + + const uint32_t d1 = detail::decode_table_0[t1]; + const uint32_t d2 = detail::decode_table_1[t2]; + const uint32_t d3 = detail::decode_table_2[t3]; + + const uint32_t temp = d1 | d2 | d3; + + if (temp >= detail::bad_char) + { + throw std::runtime_error{ + "Invalid base64 encoded data - Invalid character" + }; + } + + // Use bit_cast instead of union and type punning to avoid + // undefined behaviour risk: + // https://en.wikipedia.org/wiki/Type_punning#Use_of_union + const std::array tempBytes = + detail::bit_cast, uint32_t>(temp); + *currDecoding++ = tempBytes[detail::decidx0]; + *currDecoding++ = tempBytes[detail::decidx1]; + break; + } + case 2: + { + const uint8_t t1 = *bytes++; + const uint8_t t2 = *bytes++; + + const uint32_t d1 = detail::decode_table_0[t1]; + const uint32_t d2 = detail::decode_table_1[t2]; + + const uint32_t temp = d1 | d2; + + if (temp >= detail::bad_char) + { + throw std::runtime_error{ + "Invalid base64 encoded data - Invalid character" + }; + } + + const std::array tempBytes = + detail::bit_cast, uint32_t>(temp); + *currDecoding++ = tempBytes[detail::decidx0]; + break; + } + default: + { + throw std::runtime_error{ + "Invalid base64 encoded data - Invalid padding number" + }; + } + } + + return decoded; + } + + template + inline OutputBuffer decode_into(InputIterator begin, InputIterator end) + { + typedef std::decay_t input_value_type; + static_assert(std::is_same_v || + std::is_same_v || + std::is_same_v || + std::is_same_v); + std::string_view data(reinterpret_cast(&*begin), end - begin); + return decode_into(data); + } + + inline std::string from_base64(std::string_view data) + { + return decode_into(data); + } + +} // namespace base64 + +#endif // BASE64_HPP_ From 028dcc5f364a8d0c997297fe7a983a5224504f63 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 15 Dec 2025 10:44:46 +0000 Subject: [PATCH 018/196] add attachments compatibility test --- .vscode/settings.json | 1 + compatibility/attachments/attachments.cpp | 51 +++++++++++++++++++ .../library/engine/ExecutionContext.hpp | 2 +- 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 compatibility/attachments/attachments.cpp diff --git a/.vscode/settings.json b/.vscode/settings.json index 6b729d44..1d82165e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,6 +1,7 @@ { "cmake.useCMakePresets": "always", "cucumberautocomplete.steps": [ + "compatibility/*/*.cpp", "cucumber_cpp/example/steps/*.cpp", "cucumber_cpp/acceptance_test/steps/*.cpp" ], diff --git a/compatibility/attachments/attachments.cpp b/compatibility/attachments/attachments.cpp new file mode 100644 index 00000000..5812ff0d --- /dev/null +++ b/compatibility/attachments/attachments.cpp @@ -0,0 +1,51 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include "library/engine/ExecutionContext.hpp" +#include +#include +#include +#include +#include + +namespace +{ + const std::filesystem::path currentCompileDir = std::filesystem::path{ std::source_location::current().file_name() }.parent_path(); +} + +WHEN(R"(the string {string} is attached as {string})", (const std::string& text, const std::string& mediaType)) +{ + Attach(text, mediaType); +} + +WHEN(R"(the string {string} is logged)", (const std::string& text)) +{ + Log(text); +} + +WHEN(R"(text with ANSI escapes is logged)") +{ + Log("This displays a \x1b[31mr\x1b[0m\x1b[91ma\x1b[0m\x1b[33mi\x1b[0m\x1b[32mn\x1b[0m\x1b[34mb\x1b[0m\x1b[95mo\x1b[0m\x1b[35mw\x1b[0m"); +} + +WHEN(R"(the following string is attached as {string}:)", (const std::string& mediaType)) +{ + Attach(docString->content, mediaType); +} + +WHEN(R"(an array with {int} bytes is attached as {string})", (std::int32_t size, const std::string& mediaType)) +{ + std::string data(size, '\0'); + std::iota(data.begin(), data.end(), 0); + std::stringstream stream{ data }; + Attach(stream, mediaType); +} + +WHEN(R"(a PDF document is attached and renamed)") +{ + std::ifstream pdfFile{ currentCompileDir / "document.pdf", std::ios::binary }; + Attach(pdfFile, cucumber_cpp::library::engine::AttachOptions{ "application/pdf", "renamed.pdf" }); +} + +WHEN(R"(a link to {string} is attached)", (const std::string& url)) +{ + Link(url); +} diff --git a/cucumber_cpp/library/engine/ExecutionContext.hpp b/cucumber_cpp/library/engine/ExecutionContext.hpp index 85e70a17..b839f906 100644 --- a/cucumber_cpp/library/engine/ExecutionContext.hpp +++ b/cucumber_cpp/library/engine/ExecutionContext.hpp @@ -30,7 +30,7 @@ namespace cucumber_cpp::library::engine void Attach(std::string data, OptionsOrMediaType mediaType); void Attach(std::istream& data, OptionsOrMediaType mediaType); void Log(std::string text); - void Link(std::string url, std::optional title); + void Link(std::string url, std::optional title = std::nullopt); Context& context; From 640986fbe60cc095b69e49b256100700a280afd0 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 16 Dec 2025 11:32:55 +0000 Subject: [PATCH 019/196] enabled examples-tables-undefined and global-hooks compatibility test --- compatibility/compatibility.cpp | 4 + .../examples-tables-undefined.cpp | 19 +++ compatibility/global-hooks/global-hooks.cpp | 32 +++++ cucumber_cpp/example/hooks/Hooks.cpp | 9 +- cucumber_cpp/library/HookRegistry.cpp | 53 ++++++++- cucumber_cpp/library/HookRegistry.hpp | 76 +++++++++--- cucumber_cpp/library/Hooks.hpp | 52 ++++---- cucumber_cpp/library/StepRegistry.cpp | 10 +- cucumber_cpp/library/StepRegistry.hpp | 3 +- cucumber_cpp/library/Steps.hpp | 3 +- cucumber_cpp/library/api/RunCucumber.cpp | 22 +++- cucumber_cpp/library/runtime/Coordinator.cpp | 5 +- cucumber_cpp/library/runtime/Coordinator.hpp | 3 - cucumber_cpp/library/runtime/MakeRuntime.cpp | 6 +- .../library/runtime/SerialRuntimeAdapter.cpp | 7 +- .../library/runtime/SerialRuntimeAdapter.hpp | 4 +- cucumber_cpp/library/runtime/Worker.cpp | 39 +----- cucumber_cpp/library/support/CMakeLists.txt | 1 + .../library/support/SupportCodeLibrary.cpp | 112 ++++++++++++++++++ .../library/support/SupportCodeLibrary.hpp | 72 +++++++++++ cucumber_cpp/library/support/Types.hpp | 2 +- 21 files changed, 426 insertions(+), 108 deletions(-) create mode 100644 compatibility/examples-tables-undefined/examples-tables-undefined.cpp create mode 100644 compatibility/global-hooks/global-hooks.cpp create mode 100644 cucumber_cpp/library/support/SupportCodeLibrary.cpp diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index c157f3ab..fb58a2f4 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -78,6 +78,8 @@ namespace compatibility jsonIter = json.erase(jsonIter); else if (key == "start") jsonIter = json.erase(jsonIter); + else if (key == "snippets") + jsonIter = json.erase(jsonIter); else if (value.is_object()) { SanitizeExpectedJson(value); @@ -133,6 +135,8 @@ namespace compatibility jsonIter = json.erase(jsonIter); else if (key == "line") jsonIter = json.erase(jsonIter); + else if (key == "snippets") + jsonIter = json.erase(jsonIter); else if (value.is_object()) { SanitizeActualJson(value); diff --git a/compatibility/examples-tables-undefined/examples-tables-undefined.cpp b/compatibility/examples-tables-undefined/examples-tables-undefined.cpp new file mode 100644 index 00000000..13a3e619 --- /dev/null +++ b/compatibility/examples-tables-undefined/examples-tables-undefined.cpp @@ -0,0 +1,19 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include "gtest/gtest.h" +#include +#include + +GIVEN(R"(there are {int} cucumbers)", (std::int32_t initialCount)) +{ + context.Insert(initialCount); +} + +WHEN(R"(I eat {int} cucumbers)", (std::int32_t eatCount)) +{ + context.Get() -= eatCount; +} + +THEN(R"(I should have {int} cucumbers)", (std::int32_t expectedCount)) +{ + ASSERT_THAT(context.Get(), testing::Eq(expectedCount)); +} diff --git a/compatibility/global-hooks/global-hooks.cpp b/compatibility/global-hooks/global-hooks.cpp new file mode 100644 index 00000000..29ac1fd4 --- /dev/null +++ b/compatibility/global-hooks/global-hooks.cpp @@ -0,0 +1,32 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_ALL() +{ + // no-op +} + +HOOK_BEFORE_ALL() +{ + // no-op +} + +WHEN(R"(a step passes)") +{ + // no-op +} + +WHEN(R"(a step fails)") +{ + ASSERT_THAT(true, false); +} + +HOOK_AFTER_ALL() +{ + // no-op +} + +HOOK_AFTER_ALL() +{ + // no-op +} diff --git a/cucumber_cpp/example/hooks/Hooks.cpp b/cucumber_cpp/example/hooks/Hooks.cpp index f4d30d37..5aae64b5 100644 --- a/cucumber_cpp/example/hooks/Hooks.cpp +++ b/cucumber_cpp/example/hooks/Hooks.cpp @@ -12,7 +12,14 @@ HOOK_BEFORE_ALL() context.Emplace(); } -HOOK_BEFORE_SCENARIO("@dingus") +HOOK_BEFORE_ALL(.name = "Initialize something") // does NOT have a .tagExpression +{} + +HOOK_BEFORE_SCENARIO(.name = "explicit name only") +{ +} + +HOOK_BEFORE_SCENARIO("@dingus", "name", 10) { } diff --git a/cucumber_cpp/library/HookRegistry.cpp b/cucumber_cpp/library/HookRegistry.cpp index 0dbd1935..ad53dd70 100644 --- a/cucumber_cpp/library/HookRegistry.cpp +++ b/cucumber_cpp/library/HookRegistry.cpp @@ -1,15 +1,24 @@ #include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/hook_type.hpp" +#include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle_tag.hpp" +#include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/tag.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" #include #include +#include +#include #include #include #include #include #include +#include #include namespace cucumber_cpp::library @@ -39,16 +48,51 @@ namespace cucumber_cpp::library return keyValue.second.tagExpression->Evaluate(tags); }; } + + std::map HookTypeMap{ + { HookType::beforeAll, cucumber::messages::hook_type::BEFORE_TEST_RUN }, + { HookType::afterAll, cucumber::messages::hook_type::AFTER_TEST_RUN }, + // { HookType::beforeFeature, cucumber::messages::hook_type::BEFORE_TEST_CASE }, + // { HookType::afterFeature, cucumber::messages::hook_type::AFTER_TEST_CASE }, + { HookType::before, cucumber::messages::hook_type::BEFORE_TEST_CASE }, + { HookType::after, cucumber::messages::hook_type::AFTER_TEST_CASE }, + { HookType::beforeStep, cucumber::messages::hook_type::BEFORE_TEST_STEP }, + { HookType::afterStep, cucumber::messages::hook_type::AFTER_TEST_STEP }, + }; } HookBase::HookBase(Context& context) : context{ context } {} - HookRegistry::HookRegistry() + HookRegistry::Definition::Definition(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) + : type{ type } + , tagExpression{ tag_expression::Parse(expression) } + , factory{ factory } + , hook{ + .id = id, + .source_reference = cucumber::messages::source_reference{ + .uri = sourceLocation.file_name(), + .location = cucumber::messages::location{ + .line = sourceLocation.line(), + }, + }, + .tag_expression = !expression.empty() ? std::make_optional(std::string{ expression }) : std::nullopt, + .type = HookTypeMap.contains(type) ? std::make_optional(HookTypeMap.at(type)) : std::nullopt, + } + {} + + HookRegistry::HookRegistry(cucumber::gherkin::id_generator_ptr idGenerator) + : idGenerator{ std::move(idGenerator) } { - for (const auto& matcher : HookRegistration::Instance().GetEntries()) - Register(matcher.type, matcher.expression, matcher.factory, matcher.sourceLocation); + } + + void HookRegistry::LoadHooks() + { + + for (const auto& matcher : support::DefinitionRegistration::Instance().GetHooks()) + // for (const auto& matcher : HookRegistration::Instance().GetEntries()) + Register(matcher.id, matcher.type, matcher.expression, matcher.factory, matcher.sourceLocation); } std::vector HookRegistry::FindIds(HookType hookType, std::span tags) const @@ -83,9 +127,8 @@ namespace cucumber_cpp::library return registry.at(id); } - void HookRegistry::Register(HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) + void HookRegistry::Register(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) { - auto id = std::to_string(nextId++); registry.emplace(id, Definition{ id, type, expression, factory, sourceLocation }); } diff --git a/cucumber_cpp/library/HookRegistry.hpp b/cucumber_cpp/library/HookRegistry.hpp index 798a0eb5..ddff0ccc 100644 --- a/cucumber_cpp/library/HookRegistry.hpp +++ b/cucumber_cpp/library/HookRegistry.hpp @@ -1,7 +1,9 @@ #ifndef CUCUMBER_CPP_HOOKREGISTRY_HPP #define CUCUMBER_CPP_HOOKREGISTRY_HPP +#include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/hook.hpp" +#include "cucumber/messages/hook_type.hpp" #include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle_tag.hpp" #include "cucumber/messages/source_reference.hpp" @@ -14,6 +16,8 @@ #include #include #include +#include +#include #include #include #include @@ -76,21 +80,7 @@ namespace cucumber_cpp::library { struct Definition { - Definition(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) - : type{ type } - , tagExpression{ tag_expression::Parse(expression) } - , factory{ factory } - , hook{ - .id = id, - .source_reference = cucumber::messages::source_reference{ - .uri = sourceLocation.file_name(), - .location = cucumber::messages::location{ - .line = sourceLocation.line(), - }, - }, - .tag_expression = std::string{ expression }, - } - {} + Definition(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation); HookType type; std::unique_ptr tagExpression; @@ -98,11 +88,29 @@ namespace cucumber_cpp::library cucumber::messages::hook hook; }; - explicit HookRegistry(); + explicit HookRegistry(cucumber::gherkin::id_generator_ptr idGenerator); + + void LoadHooks(); std::vector FindIds(HookType hookType, std::span tags = {}) const; std::vector FindIds(HookType hookType, std::span tags) const; + std::vector HooksByType(HookType hookType) const + { + auto filtered = registry | + std::views::values | + std::views::filter([hookType](const Definition& definition) + { + return definition.type == hookType; + }) | + std::views::transform([](const Definition& definition) + { + return definition.hook; + }); + + return { filtered.begin(), filtered.end() }; + } + [[nodiscard]] std::size_t Size() const; [[nodiscard]] std::size_t Size(HookType hookType) const; @@ -110,12 +118,25 @@ namespace cucumber_cpp::library const Definition& GetDefinitionById(std::string id) const; private: - void Register(HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation); + void Register(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation); - std::uint32_t nextId{ 1 }; + cucumber::gherkin::id_generator_ptr idGenerator; std::map registry; }; + struct GlobalHook + { + std::string_view name{ "anonymous" }; + std::int32_t order{ 0 }; + }; + + struct Hook + { + std::string_view tagExpression{ "" }; + std::string_view name{ "anonymous" }; + std::int32_t order{ 0 }; + }; + struct HookRegistration { private: @@ -141,10 +162,15 @@ namespace cucumber_cpp::library std::string_view expression; HookFactory factory; std::source_location sourceLocation; + std::string id{ "unassigned" }; }; template static std::size_t Register(std::string_view tagExpression, HookType hookType, std::source_location sourceLocation = std::source_location::current()); + template + static std::size_t Register(Hook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); + template + static std::size_t Register(GlobalHook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); std::span GetEntries(); [[nodiscard]] std::span GetEntries() const; @@ -163,6 +189,20 @@ namespace cucumber_cpp::library Instance().registry.emplace_back(hookType, tagExpression, HookBodyFactory, sourceLocation); return Instance().registry.size(); } + + template + std::size_t HookRegistration::Register(Hook hook, HookType hookType, std::source_location sourceLocation) + { + Instance().registry.emplace_back(hookType, hook.tagExpression, HookBodyFactory, sourceLocation); + return Instance().registry.size(); + } + + template + std::size_t HookRegistration::Register(GlobalHook hook, HookType hookType, std::source_location sourceLocation) + { + Instance().registry.emplace_back(hookType, "", HookBodyFactory, sourceLocation); + return Instance().registry.size(); + } } #endif diff --git a/cucumber_cpp/library/Hooks.hpp b/cucumber_cpp/library/Hooks.hpp index de5f0733..e894a68f 100644 --- a/cucumber_cpp/library/Hooks.hpp +++ b/cucumber_cpp/library/Hooks.hpp @@ -6,47 +6,49 @@ #include "cucumber_cpp/library/BodyMacro.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::HookRegistration::Register, cucumber_cpp::library::HookBase) +// #define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::HookRegistration::Register, cucumber_cpp::library::HookBase) +#define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::support::DefinitionRegistration::Register, cucumber_cpp::library::HookBase) -#define HOOK_BEFORE_ALL() \ - HOOK_( \ - "", \ +#define HOOK_BEFORE_ALL(...) \ + HOOK_( \ + (cucumber_cpp::library::GlobalHook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::beforeAll) -#define HOOK_AFTER_ALL() \ - HOOK_( \ - "", \ +#define HOOK_AFTER_ALL(...) \ + HOOK_( \ + (cucumber_cpp::library::GlobalHook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::afterAll) -#define HOOK_BEFORE_FEATURE(...) \ - HOOK_( \ - BODY_MATCHER(__VA_ARGS__ __VA_OPT__(, ) ""), \ +#define HOOK_BEFORE_FEATURE(...) \ + HOOK_( \ + (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::beforeFeature) -#define HOOK_AFTER_FEATURE(...) \ - HOOK_( \ - BODY_MATCHER(__VA_ARGS__ __VA_OPT__(, ) ""), \ +#define HOOK_AFTER_FEATURE(...) \ + HOOK_( \ + (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::afterFeature) -#define HOOK_BEFORE_SCENARIO(...) \ - HOOK_( \ - BODY_MATCHER(__VA_ARGS__ __VA_OPT__(, ) ""), \ +#define HOOK_BEFORE_SCENARIO(...) \ + HOOK_( \ + (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::before) -#define HOOK_AFTER_SCENARIO(...) \ - HOOK_( \ - BODY_MATCHER(__VA_ARGS__ __VA_OPT__(, ) ""), \ +#define HOOK_AFTER_SCENARIO(...) \ + HOOK_( \ + (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::after) -#define HOOK_BEFORE_STEP(...) \ - HOOK_( \ - BODY_MATCHER(__VA_ARGS__ __VA_OPT__(, ) ""), \ +#define HOOK_BEFORE_STEP(...) \ + HOOK_( \ + (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::beforeStep) -#define HOOK_AFTER_STEP(...) \ - HOOK_( \ - BODY_MATCHER(__VA_ARGS__ __VA_OPT__(, ) ""), \ +#define HOOK_AFTER_STEP(...) \ + HOOK_( \ + (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::afterStep) #endif diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index cc6d339d..cc34683d 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -7,6 +7,7 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/RegularExpression.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include #include #include @@ -27,8 +28,9 @@ namespace cucumber_cpp::library void StepRegistry::LoadSteps() { - for (const auto& matcher : StepStringRegistration::Instance().GetEntries()) - Register(matcher.regex, matcher.type, matcher.factory, matcher.sourceLocation); + for (const auto& matcher : support::DefinitionRegistration::Instance().GetSteps()) + // for (const auto& matcher : StepStringRegistration::Instance().GetEntries()) + Register(matcher.id, matcher.regex, matcher.type, matcher.factory, matcher.sourceLocation); } StepMatch StepRegistry::Query(const std::string& expression) @@ -102,10 +104,8 @@ namespace cucumber_cpp::library return registry; } - void StepRegistry::Register(const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) + void StepRegistry::Register(std::string id, const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) { - auto id = idGenerator->next_id(); - if (matcher.starts_with('^') || matcher.ends_with('$')) { registry.emplace(id, Definition{ diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index e7d513f8..fbacf4ae 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -114,7 +114,7 @@ namespace cucumber_cpp::library const std::map& StepDefinitions() const; private: - void Register(const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); + void Register(std::string id, const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); cucumber_expression::ParameterRegistry& parameterRegistry; cucumber::gherkin::id_generator_ptr idGenerator; @@ -143,6 +143,7 @@ namespace cucumber_cpp::library std::string regex; StepFactory factory; std::source_location sourceLocation; + std::string id{ "unassigned" }; }; template diff --git a/cucumber_cpp/library/Steps.hpp b/cucumber_cpp/library/Steps.hpp index ad1b459f..2eb7f727 100644 --- a/cucumber_cpp/library/Steps.hpp +++ b/cucumber_cpp/library/Steps.hpp @@ -7,8 +7,9 @@ #include "cucumber_cpp/library/BodyMacro.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/engine/Step.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#define STEP_(matcher, type, args, fixture) BODY(matcher, type, args, cucumber_cpp::library::StepStringRegistration::Register, fixture) +#define STEP_(matcher, type, args, fixture) BODY(matcher, type, args, cucumber_cpp::library::support::DefinitionRegistration::Register, fixture) #define STEP_TYPE_(fixture, type, ...) \ STEP_( \ diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index a08d8893..2156777d 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include namespace cucumber_cpp::library::api @@ -63,13 +64,30 @@ namespace cucumber_cpp::library::api } } + void EmitTestRunHooks(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster) + { + supportCodeLibrary.hookRegistry.LoadHooks(); + + const auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::beforeAll); + + for (const auto& hook : beforeAllHooks) + broadcaster.BroadcastEvent({ .hook = std::move(hook) }); + + const auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::afterAll); + + for (const auto& hook : afterAllHooks) + broadcaster.BroadcastEvent({ .hook = std::move(hook) }); + } + void EmitSupportCodeMessages(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { + support::DefinitionRegistration::Instance().LoadIds(idGenerator); + EmitParameters(supportCodeLibrary, broadcaster, idGenerator); // undefined parameters EmitStepDefinitions(supportCodeLibrary, broadcaster, idGenerator); // test case hooks - // test run hooks + EmitTestRunHooks(supportCodeLibrary, broadcaster); } } @@ -78,7 +96,7 @@ namespace cucumber_cpp::library::api cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); StepRegistry stepRegistry{ parameterRegistry, idGenerator }; - HookRegistry hookRegistry{}; + HookRegistry hookRegistry{ idGenerator }; support::SupportCodeLibrary supportCodeLibrary{ .hookRegistry = hookRegistry, .stepRegistry = stepRegistry, .parameterRegistry = parameterRegistry }; diff --git a/cucumber_cpp/library/runtime/Coordinator.cpp b/cucumber_cpp/library/runtime/Coordinator.cpp index 00eb785b..7a7ba189 100644 --- a/cucumber_cpp/library/runtime/Coordinator.cpp +++ b/cucumber_cpp/library/runtime/Coordinator.cpp @@ -17,13 +17,11 @@ namespace cucumber_cpp::library::runtime Coordinator::Coordinator(std::string testRunStartedId, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, - std::span sourcedPickles, std::unique_ptr&& runtimeAdapter, support::SupportCodeLibrary supportCodeLibrary) : testRunStartedId{ testRunStartedId } , broadcaster{ broadcaster } , idGenerator{ idGenerator } - , sourcedPickles{ sourcedPickles } , runtimeAdapter{ std::move(runtimeAdapter) } , supportCodeLibrary{ supportCodeLibrary } {} @@ -35,8 +33,7 @@ namespace cucumber_cpp::library::runtime .id = std::string{ testRunStartedId }, } }); - const auto assembledTestCases = assemble::AssembleTestSuites(supportCodeLibrary, testRunStartedId, broadcaster, sourcedPickles, idGenerator); - const auto success = runtimeAdapter->Run(assembledTestCases); + const auto success = runtimeAdapter->Run(); broadcaster.BroadcastEvent({ .test_run_finished = cucumber::messages::test_run_finished{ .success = success, diff --git a/cucumber_cpp/library/runtime/Coordinator.hpp b/cucumber_cpp/library/runtime/Coordinator.hpp index f5d4073c..1d32cdb9 100644 --- a/cucumber_cpp/library/runtime/Coordinator.hpp +++ b/cucumber_cpp/library/runtime/Coordinator.hpp @@ -16,7 +16,6 @@ namespace cucumber_cpp::library::runtime Coordinator(std::string testRunStartedId, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, - std::span sourcedPickles, std::unique_ptr&& runtimeAdapter, support::SupportCodeLibrary supportCodeLibrary); @@ -26,8 +25,6 @@ namespace cucumber_cpp::library::runtime std::string testRunStartedId; util::Broadcaster& broadcaster; cucumber::gherkin::id_generator_ptr idGenerator; - - std::span sourcedPickles; std::unique_ptr runtimeAdapter; support::SupportCodeLibrary supportCodeLibrary; }; diff --git a/cucumber_cpp/library/runtime/MakeRuntime.cpp b/cucumber_cpp/library/runtime/MakeRuntime.cpp index 254be3fd..0deb5c62 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.cpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.cpp @@ -12,12 +12,13 @@ namespace cucumber_cpp::library::runtime { - std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) + std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, std::span sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) { return std::make_unique( testRunStartedId, broadcaster, idGenerator, + sourcedPickles, options, supportCodeLibrary, programContext); @@ -30,8 +31,7 @@ namespace cucumber_cpp::library::runtime testRunStartedId, broadcaster, idGenerator, - sourcedPickles, - MakeAdapter(options, testRunStartedId, broadcaster, supportCodeLibrary, idGenerator, programContext), + MakeAdapter(options, testRunStartedId, broadcaster, sourcedPickles, supportCodeLibrary, idGenerator, programContext), supportCodeLibrary); } } diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp index 44b69347..218bf7c1 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp @@ -1,6 +1,7 @@ #include "cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp" #include "cucumber/gherkin/id_generator.hpp" #include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" #include "cucumber_cpp/library/runtime/Worker.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" @@ -14,24 +15,28 @@ namespace cucumber_cpp::library::runtime SerialRuntimeAdapter::SerialRuntimeAdapter(std::string testRunStartedId, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, + std::span sourcedPickles, const support::RunOptions::Runtime& options, support::SupportCodeLibrary supportCodeLibrary, Context& programContext) : testRunStartedId{ testRunStartedId } , broadcaster{ broadcaster } , idGenerator{ idGenerator } + , sourcedPickles{ sourcedPickles } , options{ options } , supportCodeLibrary{ supportCodeLibrary } , programContext{ programContext } {} - bool SerialRuntimeAdapter::Run(std::span assembledTestSuites) + bool SerialRuntimeAdapter::Run() { bool failing = false; runtime::Worker worker{ testRunStartedId, broadcaster, idGenerator, options, supportCodeLibrary, programContext }; worker.RunBeforeAllHooks(); + auto assembledTestSuites = assemble::AssembleTestSuites(supportCodeLibrary, testRunStartedId, broadcaster, sourcedPickles, idGenerator); + for (const auto& assembledTestSuite : assembledTestSuites) { try diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp index 916d6328..96590af9 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp @@ -17,16 +17,18 @@ namespace cucumber_cpp::library::runtime SerialRuntimeAdapter(std::string testRunStartedId, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, + std::span sourcedPickles, const support::RunOptions::Runtime& options, support::SupportCodeLibrary supportCodeLibrary, Context& programContext); - bool Run(std::span assembledTestSuites) override; + bool Run() override; private: std::string testRunStartedId; util::Broadcaster& broadcaster; cucumber::gherkin::id_generator_ptr idGenerator; + std::span sourcedPickles; const support::RunOptions::Runtime& options; support::SupportCodeLibrary supportCodeLibrary; Context& programContext; diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index 01c1a6a1..dc14f823 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -1,57 +1,23 @@ #include "cucumber_cpp/library/runtime/Worker.hpp" -#include "cucumber/gherkin/app.hpp" -#include "cucumber/gherkin/exceptions.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/gherkin/pickle_compiler.hpp" -#include "cucumber/gherkin/utils.hpp" #include "cucumber/messages/duration.hpp" -#include "cucumber/messages/envelope.hpp" #include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/parameter_type.hpp" -#include "cucumber/messages/parse_error.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/source.hpp" -#include "cucumber/messages/source_reference.hpp" -#include "cucumber/messages/step_definition.hpp" -#include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber/messages/step_match_argument.hpp" -#include "cucumber/messages/suggestion.hpp" -#include "cucumber/messages/test_case.hpp" -#include "cucumber/messages/test_case_finished.hpp" -#include "cucumber/messages/test_case_started.hpp" -#include "cucumber/messages/test_run_finished.hpp" #include "cucumber/messages/test_run_hook_finished.hpp" #include "cucumber/messages/test_run_hook_started.hpp" -#include "cucumber/messages/test_run_started.hpp" -#include "cucumber/messages/test_step.hpp" -#include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber/messages/test_step_started.hpp" -#include "cucumber/messages/timestamp.hpp" #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" #include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" -#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/runtime/TestCaseRunner.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" -#include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include -#include -#include -#include -#include -#include #include #include #include @@ -62,7 +28,6 @@ #include #include #include -#include #include namespace cucumber_cpp::library::runtime @@ -134,8 +99,8 @@ namespace cucumber_cpp::library::runtime std::vector Worker::RunAfterAllHooks() { std::vector results; - const auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::afterAll); - for (const auto& id : ids) + auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::afterAll); + for (const auto& id : ids | std::views::reverse) results.emplace_back(std::move(RunTestHook(id, programContext))); if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index 0189b908..e230c73c 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -5,6 +5,7 @@ target_sources(cucumber_cpp.library.support PRIVATE Duration.cpp Join.cpp Join.hpp + SupportCodeLibrary.cpp SupportCodeLibrary.hpp Timestamp.cpp Timestamp.hpp diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp new file mode 100644 index 00000000..dd04ac71 --- /dev/null +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -0,0 +1,112 @@ +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber/gherkin/id_generator.hpp" +#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/engine/StepType.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace std +{ + std::strong_ordering operator<=>(const std::source_location& left, const std::source_location& right) + { + return std::forward_as_tuple(left.file_name(), left.line()) <=> std::forward_as_tuple(right.file_name(), right.line()); + } +} + +namespace cucumber_cpp::library +{ + std::strong_ordering operator<=>(const HookRegistration::Entry& left, const HookRegistration::Entry& right) + { + return left.sourceLocation <=> right.sourceLocation; + } + + std::strong_ordering operator<=>(const StepStringRegistration::Entry& left, const StepStringRegistration::Entry& right) + { + return left.sourceLocation <=> right.sourceLocation; + } +} + +namespace cucumber_cpp::library::support +{ + std::strong_ordering operator<=>(const Entry& left, const Entry& right) + { + constexpr static auto sourceLocationVisitor = [](const auto& entry) + { + return entry.sourceLocation; + }; + + const auto& leftSource = std::visit(sourceLocationVisitor, left); + const auto& rightSource = std::visit(sourceLocationVisitor, right); + + return leftSource <=> rightSource; + } + + DefinitionRegistration& DefinitionRegistration::Instance() + { + static DefinitionRegistration instance; + return instance; + } + + void DefinitionRegistration::LoadIds(cucumber::gherkin::id_generator_ptr idGenerator) + { + static auto assignGenerator = [&idGenerator](auto& entry) + { + entry.id = idGenerator->next_id(); + }; + + for (auto& [key, item] : registry) + std::visit(assignGenerator, item); + } + + std::vector DefinitionRegistration::GetSteps() + { + auto allSteps = registry | std::views::values | std::views::filter([](const Entry& entry) + { + return std::holds_alternative(entry); + }) | + std::views::transform([](const Entry& entry) + { + return std::get(entry); + }); + return { allSteps.begin(), allSteps.end() }; + } + + std::vector DefinitionRegistration::GetHooks() + { + auto allSteps = registry | std::views::values | std::views::filter([](const Entry& entry) + { + return std::holds_alternative(entry); + }) | + std::views::transform([](const Entry& entry) + { + return std::get(entry); + }); + return { allSteps.begin(), allSteps.end() }; + } + + std::size_t DefinitionRegistration::Register(Hook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation) + { + registry.emplace(sourceLocation, HookRegistration::Entry{ hookType, hook.tagExpression, factory, sourceLocation }); + return registry.size(); + } + + std::size_t DefinitionRegistration::Register(GlobalHook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation) + { + registry.emplace(sourceLocation, HookRegistration::Entry{ hookType, "", factory, sourceLocation }); + return registry.size(); + } + + std::size_t DefinitionRegistration::Register(std::string_view matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) + { + registry.emplace(sourceLocation, StepStringRegistration::Entry{ stepType, std::string{ matcher }, factory, sourceLocation }); + return registry.size(); + } +} diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp index ea182a54..ca7e20bd 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.hpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -1,9 +1,25 @@ #ifndef SUPPORT_SUPPORT_CODE_LIBRARY_HPP #define SUPPORT_SUPPORT_CODE_LIBRARY_HPP +#include "cucumber/gherkin/id_generator.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/engine/StepType.hpp" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library +{ + std::strong_ordering operator<=>(const HookRegistration::Entry& left, const HookRegistration::Entry& right); + std::strong_ordering operator<=>(const StepStringRegistration::Entry& left, const StepStringRegistration::Entry& right); +} namespace cucumber_cpp::library::support { @@ -13,6 +29,62 @@ namespace cucumber_cpp::library::support StepRegistry& stepRegistry; cucumber_expression::ParameterRegistry& parameterRegistry; }; + + using Entry = std::variant; + + std::strong_ordering operator<=>(const Entry& left, const Entry& right); + + struct DefinitionRegistration + { + private: + DefinitionRegistration() = default; + + public: + static DefinitionRegistration& Instance(); + + void LoadIds(cucumber::gherkin::id_generator_ptr idGenerator); + + std::vector GetSteps(); + std::vector GetHooks(); + + template + static std::size_t Register(Hook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); + + template + static std::size_t Register(GlobalHook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); + + template + static std::size_t Register(std::string_view matcher, engine::StepType stepType, std::source_location sourceLocation = std::source_location::current()); + + private: + std::size_t Register(Hook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation); + std::size_t Register(GlobalHook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation); + std::size_t Register(std::string_view matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); + + std::map> registry; + }; + + ////////////////////////// + // implementation // + ////////////////////////// + + template + std::size_t DefinitionRegistration::Register(Hook hook, HookType hookType, std::source_location sourceLocation) + { + return Instance().Register(hook, hookType, HookBodyFactory, sourceLocation); + } + + template + std::size_t DefinitionRegistration::Register(GlobalHook hook, HookType hookType, std::source_location sourceLocation) + { + return Instance().Register(hook, hookType, HookBodyFactory, sourceLocation); + } + + template + std::size_t DefinitionRegistration::Register(std::string_view matcher, engine::StepType stepType, std::source_location sourceLocation) + { + return Instance().Register(matcher, stepType, StepBodyFactory, sourceLocation); + } } #endif diff --git a/cucumber_cpp/library/support/Types.hpp b/cucumber_cpp/library/support/Types.hpp index 303dfd50..fdd65ab1 100644 --- a/cucumber_cpp/library/support/Types.hpp +++ b/cucumber_cpp/library/support/Types.hpp @@ -53,7 +53,7 @@ namespace cucumber_cpp::library::support struct RuntimeAdapter { virtual ~RuntimeAdapter() = default; - virtual bool Run(std::span assembledTestSuites) = 0; + virtual bool Run() = 0; }; } From cbb2f0c971a5178d094897fa94980fc713717523 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 17 Dec 2025 08:20:54 +0000 Subject: [PATCH 020/196] Added global-hooks-afterall-error, global-hooks-attachments, global-hooks-beforeall-error, hooks, hooks-attachment and hooks-conditional compatibility tests --- compatibility/compatibility.cpp | 20 +++-- .../global-hooks-afterall-error.cpp | 32 ++++++++ .../global-hooks-attachments.cpp | 17 +++++ .../global-hooks-beforeall-error.cpp | 32 ++++++++ .../hooks-attachment/hooks-attachment.cpp | 28 +++++++ .../hooks-conditional/hooks-conditional.cpp | 27 +++++++ compatibility/hooks/hooks.cpp | 22 ++++++ cucumber_cpp/library/HookRegistry.cpp | 8 +- cucumber_cpp/library/HookRegistry.hpp | 28 +++---- cucumber_cpp/library/StepRegistry.cpp | 73 ++++++++----------- cucumber_cpp/library/StepRegistry.hpp | 11 ++- cucumber_cpp/library/api/RunCucumber.cpp | 25 +++++-- .../library/assemble/AssembleTestSuites.cpp | 4 +- .../library/engine/ExecutionContext.cpp | 38 +++++++--- .../formatter/helper/EventDataCollector.cpp | 7 +- .../library/runtime/SerialRuntimeAdapter.cpp | 34 ++++++--- .../library/runtime/TestCaseRunner.cpp | 16 ++-- .../library/runtime/TestCaseRunner.hpp | 4 +- cucumber_cpp/library/runtime/Worker.cpp | 34 +++------ 19 files changed, 321 insertions(+), 139 deletions(-) create mode 100644 compatibility/global-hooks-afterall-error/global-hooks-afterall-error.cpp create mode 100644 compatibility/global-hooks-attachments/global-hooks-attachments.cpp create mode 100644 compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.cpp create mode 100644 compatibility/hooks-attachment/hooks-attachment.cpp create mode 100644 compatibility/hooks-conditional/hooks-conditional.cpp create mode 100644 compatibility/hooks/hooks.cpp diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index fb58a2f4..6db75327 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -209,7 +209,19 @@ namespace compatibility void CompareEnvelopes() { - ASSERT_THAT(actualEnvelopes.size(), testing::Eq(expectedEnvelopes.size())); + EXPECT_THAT(actualEnvelopes.size(), testing::Eq(expectedEnvelopes.size())); + + for (auto& json : actualEnvelopes) + { + SanitizeActualJson(json); + actualOfs << json.dump() << "\n"; + } + + for (auto& json : expectedEnvelopes) + { + SanitizeExpectedJson(json); + expectedOfs << json.dump() << "\n"; + } while (!actualEnvelopes.empty() && !expectedEnvelopes.empty()) { @@ -219,12 +231,6 @@ namespace compatibility auto expectedJson = expectedEnvelopes.front(); expectedEnvelopes.pop_front(); - SanitizeActualJson(actualJson); - SanitizeExpectedJson(expectedJson); - - expectedOfs << expectedJson.dump() << "\n"; - actualOfs << actualJson.dump() << "\n"; - EXPECT_THAT(actualJson, testing::Eq(expectedJson)); } } diff --git a/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.cpp b/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.cpp new file mode 100644 index 00000000..ea4e948f --- /dev/null +++ b/compatibility/global-hooks-afterall-error/global-hooks-afterall-error.cpp @@ -0,0 +1,32 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_ALL() +{ + // no-op +} + +HOOK_BEFORE_ALL() +{ + // no-op +} + +WHEN(R"(a step passes)") +{ + // no-op +} + +HOOK_AFTER_ALL() +{ + // no-op +} + +HOOK_AFTER_ALL() +{ + ASSERT_THAT(true, false); +} + +HOOK_AFTER_ALL() +{ + // no-op +} diff --git a/compatibility/global-hooks-attachments/global-hooks-attachments.cpp b/compatibility/global-hooks-attachments/global-hooks-attachments.cpp new file mode 100644 index 00000000..e4469a18 --- /dev/null +++ b/compatibility/global-hooks-attachments/global-hooks-attachments.cpp @@ -0,0 +1,17 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_ALL() +{ + Attach("Attachment from BeforeAll hook", "text/plain"); +} + +WHEN(R"(a step passes)") +{ + // no-op +} + +HOOK_AFTER_ALL() +{ + Attach("Attachment from AfterAll hook", "text/plain"); +} diff --git a/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.cpp b/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.cpp new file mode 100644 index 00000000..ca642edd --- /dev/null +++ b/compatibility/global-hooks-beforeall-error/global-hooks-beforeall-error.cpp @@ -0,0 +1,32 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_ALL() +{ + // no-op +} + +HOOK_BEFORE_ALL() +{ + ASSERT_THAT(true, false); +} + +HOOK_BEFORE_ALL() +{ + // no-op +} + +WHEN(R"(a step passes)") +{ + // no-op +} + +HOOK_AFTER_ALL() +{ + // no-op +} + +HOOK_AFTER_ALL() +{ + // no-op +} diff --git a/compatibility/hooks-attachment/hooks-attachment.cpp b/compatibility/hooks-attachment/hooks-attachment.cpp new file mode 100644 index 00000000..e2bca714 --- /dev/null +++ b/compatibility/hooks-attachment/hooks-attachment.cpp @@ -0,0 +1,28 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include +#include +#include +#include +#include + +namespace +{ + const std::filesystem::path currentCompileDir = std::filesystem::path{ std::source_location::current().file_name() }.parent_path(); +} + +HOOK_BEFORE_SCENARIO() +{ + std::ifstream svgFile{ currentCompileDir / "cucumber.svg", std::ios::binary }; + Attach(svgFile, "image/svg+xml"); +} + +WHEN(R"(a step passes)") +{ + // no-op +} + +HOOK_AFTER_SCENARIO() +{ + std::ifstream svgFile{ currentCompileDir / "cucumber.svg", std::ios::binary }; + Attach(svgFile, "image/svg+xml"); +} diff --git a/compatibility/hooks-conditional/hooks-conditional.cpp b/compatibility/hooks-conditional/hooks-conditional.cpp new file mode 100644 index 00000000..bf2430ee --- /dev/null +++ b/compatibility/hooks-conditional/hooks-conditional.cpp @@ -0,0 +1,27 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_SCENARIO("@passing-hook") +{ + // no-op +} + +HOOK_BEFORE_SCENARIO("@fail-before") +{ + ASSERT_THAT(true, false); +} + +WHEN(R"(a step passes)") +{ + // no-op +} + +HOOK_AFTER_SCENARIO("@fail-after") +{ + ASSERT_THAT(true, false); +} + +HOOK_AFTER_SCENARIO("@passing-hook") +{ + // no-op +} diff --git a/compatibility/hooks/hooks.cpp b/compatibility/hooks/hooks.cpp new file mode 100644 index 00000000..07f078b4 --- /dev/null +++ b/compatibility/hooks/hooks.cpp @@ -0,0 +1,22 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_SCENARIO() +{ + // no-op +} + +WHEN(R"(a step passes)") +{ + // no-op +} + +WHEN(R"(a step fails)") +{ + ASSERT_THAT(true, false); +} + +HOOK_AFTER_SCENARIO() +{ + // no-op +} diff --git a/cucumber_cpp/library/HookRegistry.cpp b/cucumber_cpp/library/HookRegistry.cpp index ad53dd70..f640d781 100644 --- a/cucumber_cpp/library/HookRegistry.cpp +++ b/cucumber_cpp/library/HookRegistry.cpp @@ -7,8 +7,10 @@ #include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/tag.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include @@ -61,8 +63,8 @@ namespace cucumber_cpp::library }; } - HookBase::HookBase(Context& context) - : context{ context } + HookBase::HookBase(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted) + : engine::ExecutionContext{ broadCaster, context, stepOrHookStarted } {} HookRegistry::Definition::Definition(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) @@ -89,9 +91,7 @@ namespace cucumber_cpp::library void HookRegistry::LoadHooks() { - for (const auto& matcher : support::DefinitionRegistration::Instance().GetHooks()) - // for (const auto& matcher : HookRegistration::Instance().GetEntries()) Register(matcher.id, matcher.type, matcher.expression, matcher.factory, matcher.sourceLocation); } diff --git a/cucumber_cpp/library/HookRegistry.hpp b/cucumber_cpp/library/HookRegistry.hpp index ddff0ccc..76f54cca 100644 --- a/cucumber_cpp/library/HookRegistry.hpp +++ b/cucumber_cpp/library/HookRegistry.hpp @@ -3,20 +3,17 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/hook.hpp" -#include "cucumber/messages/hook_type.hpp" -#include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle_tag.hpp" -#include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/tag.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/tag_expression/Model.hpp" -#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include #include -#include #include #include #include @@ -27,12 +24,6 @@ namespace cucumber_cpp::library { - template - std::unique_ptr HookBodyFactory(Context& context) - { - return std::make_unique(context); - } - enum struct HookType { beforeAll, @@ -45,9 +36,9 @@ namespace cucumber_cpp::library afterStep, }; - struct HookBase + struct HookBase : engine::ExecutionContext { - explicit HookBase(Context& context); + HookBase(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted); virtual ~HookBase() = default; @@ -60,12 +51,15 @@ namespace cucumber_cpp::library { /* nothing to do */ } - - protected: - Context& context; }; - using HookFactory = std::unique_ptr (&)(Context& context); + using HookFactory = std::unique_ptr (&)(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted); + + template + std::unique_ptr HookBodyFactory(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted) + { + return std::make_unique(broadCaster, context, stepOrHookStarted); + } struct HookMatch { diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index cc34683d..4b3c474c 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -9,6 +9,8 @@ #include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include +#include +#include #include #include #include @@ -29,7 +31,6 @@ namespace cucumber_cpp::library void StepRegistry::LoadSteps() { for (const auto& matcher : support::DefinitionRegistration::Instance().GetSteps()) - // for (const auto& matcher : StepStringRegistration::Instance().GetEntries()) Register(matcher.id, matcher.regex, matcher.type, matcher.factory, matcher.sourceLocation); } @@ -37,13 +38,13 @@ namespace cucumber_cpp::library { std::vector matches; - for (auto& [id, entry] : registry) + for (auto& [id, iter] : idToDefinitionMap) { - auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, entry.regex); + auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, iter->regex); if (match) { - matches.emplace_back(entry.factory, *match, std::visit(cucumber_expression::PatternVisitor{}, entry.regex)); - ++entry.used; + matches.emplace_back(iter->factory, *match, std::visit(cucumber_expression::PatternVisitor{}, iter->regex)); + ++iter->used; } } @@ -60,13 +61,11 @@ namespace cucumber_cpp::library { std::pair, std::vector> result; - for (auto& [id, entry] : registry) + for (auto& [id, iter] : idToDefinitionMap) { - const auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, entry.regex); + const auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, iter->regex); if (match) - { result.first.push_back(id); - } } return result; @@ -83,7 +82,7 @@ namespace cucumber_cpp::library list.reserve(registry.size()); - for (const auto& [id, entry] : registry) + for (const auto& entry : registry) list.emplace_back(entry.regex, entry.used); return list; @@ -91,54 +90,46 @@ namespace cucumber_cpp::library StepFactory StepRegistry::GetFactoryById(std::string id) const { - return registry.at(id).factory; + return idToDefinitionMap.at(id)->factory; } StepRegistry::Definition StepRegistry::GetDefinitionById(std::string id) const { - return registry.at(id); + return *idToDefinitionMap.at(id); } - const std::map& StepRegistry::StepDefinitions() const + const std::list& StepRegistry::StepDefinitions() const { return registry; } void StepRegistry::Register(std::string id, const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) { - if (matcher.starts_with('^') || matcher.ends_with('$')) - { - registry.emplace(id, Definition{ - factory, - id, - sourceLocation.line(), - sourceLocation.file_name(), - stepType, - matcher, - cucumber_expression::Matcher{ + auto cucumberMatcher = (matcher.starts_with('^') || matcher.ends_with('$')) + ? cucumber_expression::Matcher{ std::in_place_type, matcher, - }, - cucumber::messages::step_definition_pattern_type::REGULAR_EXPRESSION, - }); - } - else - { - registry.emplace(id, Definition{ - factory, - id, - sourceLocation.line(), - sourceLocation.file_name(), - stepType, - matcher, - cucumber_expression::Matcher{ + } + : cucumber_expression::Matcher{ std::in_place_type, matcher, parameterRegistry, - }, - cucumber::messages::step_definition_pattern_type::CUCUMBER_EXPRESSION, - }); - } + + }; + auto cucumberMatcherType = std::holds_alternative(cucumberMatcher) + ? cucumber::messages::step_definition_pattern_type::REGULAR_EXPRESSION + : cucumber::messages::step_definition_pattern_type::CUCUMBER_EXPRESSION; + + registry.emplace_back(factory, + id, + sourceLocation.line(), + sourceLocation.file_name(), + stepType, + matcher, + cucumberMatcher, + cucumberMatcherType); + + idToDefinitionMap[id] = std::prev(registry.end()); } StepStringRegistration& StepStringRegistration::Instance() diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index fbacf4ae..cd680d7a 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -68,16 +69,13 @@ namespace cucumber_cpp::library std::vector matches; }; - struct StepDefinition + struct Definition { StepFactory factory; std::string id; std::size_t line; std::filesystem::path uri; - }; - struct Definition : StepDefinition - { engine::StepType type; std::string pattern; cucumber_expression::Matcher regex; @@ -111,7 +109,7 @@ namespace cucumber_cpp::library StepFactory GetFactoryById(std::string id) const; Definition GetDefinitionById(std::string id) const; - const std::map& StepDefinitions() const; + const std::list& StepDefinitions() const; private: void Register(std::string id, const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); @@ -119,7 +117,8 @@ namespace cucumber_cpp::library cucumber_expression::ParameterRegistry& parameterRegistry; cucumber::gherkin::id_generator_ptr idGenerator; - std::map registry; + std::list registry; + std::map::iterator> idToDefinitionMap; }; struct StepStringRegistration diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 2156777d..5e74941e 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -44,9 +44,7 @@ namespace cucumber_cpp::library::api void EmitStepDefinitions(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { - supportCodeLibrary.stepRegistry.LoadSteps(); - - for (const auto& [name, stepDefinition] : supportCodeLibrary.stepRegistry.StepDefinitions()) + for (const auto& stepDefinition : supportCodeLibrary.stepRegistry.StepDefinitions()) { broadcaster.BroadcastEvent({ .step_definition = cucumber::messages::step_definition{ .id = stepDefinition.id, @@ -64,10 +62,21 @@ namespace cucumber_cpp::library::api } } - void EmitTestRunHooks(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster) + void EmitTestCaseHooks(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster) { - supportCodeLibrary.hookRegistry.LoadHooks(); + const auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::before); + + for (const auto& hook : beforeAllHooks) + broadcaster.BroadcastEvent({ .hook = std::move(hook) }); + + const auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::after); + + for (const auto& hook : afterAllHooks) + broadcaster.BroadcastEvent({ .hook = std::move(hook) }); + } + void EmitTestRunHooks(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster) + { const auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::beforeAll); for (const auto& hook : beforeAllHooks) @@ -85,8 +94,12 @@ namespace cucumber_cpp::library::api EmitParameters(supportCodeLibrary, broadcaster, idGenerator); // undefined parameters + + supportCodeLibrary.stepRegistry.LoadSteps(); EmitStepDefinitions(supportCodeLibrary, broadcaster, idGenerator); - // test case hooks + + supportCodeLibrary.hookRegistry.LoadHooks(); + EmitTestCaseHooks(supportCodeLibrary, broadcaster); EmitTestRunHooks(supportCodeLibrary, broadcaster); } } diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 641877c2..b8029a95 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -52,13 +52,13 @@ namespace cucumber_cpp::library::assemble std::vector stepDefinitionIds; std::vector stepMatchArgumentsLists; - for (const auto& [id, definition] : stepDefinitions) + for (const auto& definition : stepDefinitions) { auto optionalStepMatchArgumentsList = std::visit(cucumber_expression::MatchArgumentsVisitor{ step.text }, definition.regex); if (optionalStepMatchArgumentsList) { - stepDefinitionIds.push_back(id); + stepDefinitionIds.push_back(definition.id); stepMatchArgumentsLists.push_back(*optionalStepMatchArgumentsList); } } diff --git a/cucumber_cpp/library/engine/ExecutionContext.cpp b/cucumber_cpp/library/engine/ExecutionContext.cpp index fcf06792..3fda1b7d 100644 --- a/cucumber_cpp/library/engine/ExecutionContext.cpp +++ b/cucumber_cpp/library/engine/ExecutionContext.cpp @@ -14,6 +14,28 @@ #include #include +/* + +std::optional read_file( std::istream & is ) { + if( not is.seekg( 0, std::ios_base::seek_dir::end ) ) { + return std::nullopt; + } + auto const sz = is.tellg( ); + if( not is.seekg( 0 ) ) { + return std::nullopt; + } + auto result = std::string( '\0', static_cast( sz ) ); + if( not is.read( result.data( ), sz ) ) { + return std::nullopt; + } + if( sz != is.gcount( ) ) { + return std::nullopt; + } + return result; +} + +*/ + namespace cucumber_cpp::library::engine { namespace @@ -31,20 +53,15 @@ namespace cucumber_cpp::library::engine }; } - return {}; + return { std::nullopt, std::nullopt }; } - std::pair, std::optional> ReadTestRunHookStartedIds(StepOrHookStarted stepOrHookStarted) + std::optional ReadTestRunHookStartedIds(StepOrHookStarted stepOrHookStarted) { if (std::holds_alternative(stepOrHookStarted)) - { - return { - std::get(stepOrHookStarted).test_run_started_id, - std::get(stepOrHookStarted).id, - }; - } + return std::get(stepOrHookStarted).id; - return {}; + return std::nullopt; } } @@ -88,7 +105,7 @@ namespace cucumber_cpp::library::engine : std::get(mediaType); auto [test_case_started_id, test_step_id] = ReadTestStepStartedIds(stepOrHookStarted); - auto [test_run_started_id, test_run_hook_started_id] = ReadTestRunHookStartedIds(stepOrHookStarted); + auto test_run_hook_started_id = ReadTestRunHookStartedIds(stepOrHookStarted); broadCaster.BroadcastEvent({ .attachment = cucumber::messages::attachment{ @@ -98,7 +115,6 @@ namespace cucumber_cpp::library::engine .media_type = std::move(options.mediaType), .test_case_started_id = std::move(test_case_started_id), .test_step_id = std::move(test_step_id), - .test_run_started_id = std::move(test_run_started_id), .test_run_hook_started_id = std::move(test_run_hook_started_id), .timestamp = support::TimestampNow(), }, diff --git a/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp b/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp index a0455340..f2459bec 100644 --- a/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp +++ b/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp @@ -98,8 +98,11 @@ namespace cucumber_cpp::library::formatter::helper void EventDataCollector::StoreAttachment(const cucumber::messages::attachment& attachment) { - auto& testCaseAttemptData = testCaseAttemptDataMap.at(*attachment.test_case_started_id); - testCaseAttemptData.stepAttachments[*attachment.test_step_id].emplace_back(attachment); + if (attachment.test_case_started_id && attachment.test_step_id) + { + auto& testCaseAttemptData = testCaseAttemptDataMap.at(*attachment.test_case_started_id); + testCaseAttemptData.stepAttachments[*attachment.test_step_id].emplace_back(attachment); + } } void EventDataCollector::StoreTestStepResult(const cucumber::messages::test_step_finished& testStepFinished) diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp index 218bf7c1..3db99ad8 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp" #include "cucumber/gherkin/id_generator.hpp" +#include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" @@ -7,7 +8,9 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" #include +#include #include namespace cucumber_cpp::library::runtime @@ -33,25 +36,34 @@ namespace cucumber_cpp::library::runtime bool failing = false; runtime::Worker worker{ testRunStartedId, broadcaster, idGenerator, options, supportCodeLibrary, programContext }; - worker.RunBeforeAllHooks(); + const auto beforeHookResults = worker.RunBeforeAllHooks(); - auto assembledTestSuites = assemble::AssembleTestSuites(supportCodeLibrary, testRunStartedId, broadcaster, sourcedPickles, idGenerator); + if (util::GetWorstTestStepResult(beforeHookResults).status != cucumber::messages::test_step_result_status::PASSED) + failing = true; - for (const auto& assembledTestSuite : assembledTestSuites) + if (!failing) { - try + auto assembledTestSuites = assemble::AssembleTestSuites(supportCodeLibrary, testRunStartedId, broadcaster, sourcedPickles, idGenerator); + + for (const auto& assembledTestSuite : assembledTestSuites) { - const auto success = worker.RunTestSuite(assembledTestSuite, failing); - if (!success) + try + { + const auto success = worker.RunTestSuite(assembledTestSuite, failing); + if (!success) + failing = true; + } + catch (...) + { failing = true; - } - catch (...) - { - failing = true; + } } } - worker.RunAfterAllHooks(); + const auto afterHookResults = worker.RunAfterAllHooks(); + + if (util::GetWorstTestStepResult(afterHookResults).status != cucumber::messages::test_step_result_status::PASSED) + failing = true; return !failing; } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index 855e3247..ebabad24 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -144,7 +144,7 @@ namespace cucumber_cpp::library::runtime if (testStep.hook_id) { - testStepResult = RunHook(supportCodeLibrary.hookRegistry.GetDefinitionById(testStep.hook_id.value()), !seenSteps, testCaseContext); + testStepResult = RunHook(supportCodeLibrary.hookRegistry.GetDefinitionById(testStep.hook_id.value()), !seenSteps, testCaseContext, testStepStarted); } else { @@ -173,7 +173,7 @@ namespace cucumber_cpp::library::runtime return willRetry; } - cucumber::messages::test_step_result TestCaseRunner::RunHook(const HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext) + cucumber::messages::test_step_result TestCaseRunner::RunHook(const HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted) { if (ShouldSkipHook(isBeforeHook)) return { @@ -181,10 +181,10 @@ namespace cucumber_cpp::library::runtime .status = cucumber::messages::test_step_result_status::SKIPPED, }; - return InvokeStep(hookDefinition.factory(testCaseContext)); + return InvokeStep(hookDefinition.factory(broadcaster, testCaseContext, testStepStarted)); } - std::vector TestCaseRunner::RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext) + std::vector TestCaseRunner::RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted) { auto ids = supportCodeLibrary.hookRegistry.FindIds(hookType, pickle.tags); std::vector results; @@ -193,7 +193,7 @@ namespace cucumber_cpp::library::runtime for (const auto& id : ids) { const auto& definition = supportCodeLibrary.hookRegistry.GetDefinitionById(id); - results.emplace_back(InvokeStep(definition.factory(testCaseContext))); + results.emplace_back(InvokeStep(definition.factory(broadcaster, testCaseContext, testStepStarted))); } return results; @@ -203,7 +203,7 @@ namespace cucumber_cpp::library::runtime { auto stepDefinitions = (*testStep.step_definition_ids) | std::views::transform([&](const std::string& id) { - return supportCodeLibrary.stepRegistry.StepDefinitions().at(id); + return supportCodeLibrary.stepRegistry.GetDefinitionById(id); }); if (const auto count = testStep.step_definition_ids->size(); count == 0) @@ -235,7 +235,7 @@ namespace cucumber_cpp::library::runtime }; } - auto stepResults = RunStepHooks(pickleStep, HookType::beforeStep, testCaseContext); + auto stepResults = RunStepHooks(pickleStep, HookType::beforeStep, testCaseContext, testStepStarted); if (util::GetWorstTestStepResult(stepResults).status != cucumber::messages::test_step_result_status::FAILED) { @@ -262,7 +262,7 @@ namespace cucumber_cpp::library::runtime stepResults.push_back(result); } - const auto afterStepHookResults = RunStepHooks(pickleStep, HookType::afterStep, testCaseContext); + const auto afterStepHookResults = RunStepHooks(pickleStep, HookType::afterStep, testCaseContext, testStepStarted); stepResults.reserve(stepResults.size() + afterStepHookResults.size()); stepResults.insert(stepResults.end(), afterStepHookResults.begin(), afterStepHookResults.end()); diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.hpp b/cucumber_cpp/library/runtime/TestCaseRunner.hpp index e16a637a..f6c15405 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.hpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.hpp @@ -37,9 +37,9 @@ namespace cucumber_cpp::library::runtime bool RunAttempt(std::size_t attempt, bool moreAttemptsAvailable); - cucumber::messages::test_step_result RunHook(const HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext); + cucumber::messages::test_step_result RunHook(const HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); - std::vector RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext); + std::vector RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); cucumber::messages::test_step_result RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index dc14f823..da55f06d 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -17,6 +17,7 @@ #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" #include #include #include @@ -55,13 +56,6 @@ namespace cucumber_cpp::library::runtime return to_underlying(a.status) < to_underlying(b.status); }; - cucumber::messages::test_step_result GetWorstTestStepResult(std::span testStepResults) - { - if (testStepResults.empty()) - return { .status = cucumber::messages::test_step_result_status::PASSED }; - return *std::ranges::max_element(testStepResults, compare); - } - inline std::set failingStatuses{ cucumber::messages::test_step_result_status::AMBIGUOUS, cucumber::messages::test_step_result_status::FAILED, @@ -90,9 +84,6 @@ namespace cucumber_cpp::library::runtime for (const auto& id : ids) results.emplace_back(std::move(RunTestHook(id, programContext))); - if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) - throw std::runtime_error("Failed before all hook"); - return results; } @@ -103,9 +94,6 @@ namespace cucumber_cpp::library::runtime for (const auto& id : ids | std::views::reverse) results.emplace_back(std::move(RunTestHook(id, programContext))); - if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) - throw std::runtime_error("Failed after all hook"); - return results; } @@ -150,7 +138,7 @@ namespace cucumber_cpp::library::runtime for (const auto& id : ids) results.emplace_back(std::move(RunTestHook(id, context))); - if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) + if (util::GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) throw std::runtime_error("Failed before feature hook"); return results; @@ -163,7 +151,7 @@ namespace cucumber_cpp::library::runtime for (const auto& id : ids) results.emplace_back(std::move(RunTestHook(id, context))); - if (GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) + if (util::GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) throw std::runtime_error("Failed after feature hook"); return results; @@ -174,14 +162,16 @@ namespace cucumber_cpp::library::runtime const auto& definition = supportCodeLibrary.hookRegistry.GetDefinitionById(id); const auto testRunHookStartedid = idGenerator->next_id(); - broadcaster.BroadcastEvent({ .test_run_hook_started = cucumber::messages::test_run_hook_started{ - .id = testRunHookStartedid, - .test_run_started_id = std::string{ testRunStartedId }, - .hook_id = definition.hook.id, - .timestamp = support::TimestampNow(), - } }); + const auto testRunHookStarted = cucumber::messages::test_run_hook_started{ + .id = testRunHookStartedid, + .test_run_started_id = std::string{ testRunStartedId }, + .hook_id = definition.hook.id, + .timestamp = support::TimestampNow(), + }; + + broadcaster.BroadcastEvent({ .test_run_hook_started = testRunHookStarted }); - auto result = definition.factory(context)->ExecuteAndCatchExceptions(); + auto result = definition.factory(broadcaster, context, testRunHookStarted)->ExecuteAndCatchExceptions(); broadcaster.BroadcastEvent({ .test_run_hook_finished = cucumber::messages::test_run_hook_finished{ .test_run_hook_started_id = testRunHookStartedid, From dfe71574b7a23aaa2f7057be22aad84678504448 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 18 Dec 2025 08:49:52 +0000 Subject: [PATCH 021/196] add all compatibility checks by default regardless if a cpp implementation exists --- compatibility/CMakeLists.txt | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/compatibility/CMakeLists.txt b/compatibility/CMakeLists.txt index 132dea89..b5daed10 100644 --- a/compatibility/CMakeLists.txt +++ b/compatibility/CMakeLists.txt @@ -17,9 +17,14 @@ function(add_compatibility_kit name) target_sources(${libname} PRIVATE compatibility.cpp - ${name}/${name}.cpp ) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${name}/${name}.cpp) + target_sources(${libname} PRIVATE + ${name}/${name}.cpp + ) + endif() + string(REPLACE "-" "_" KITNAME ${name}) target_compile_definitions(${libname} PRIVATE @@ -32,9 +37,7 @@ endfunction() file(GLOB kits RELATIVE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/*) foreach(kit ${kits}) if (IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/${kit}) - if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${kit}/${kit}.cpp) - message(STATUS "Adding compatibility kit: ${kit}") - add_compatibility_kit(${kit}) - endif() + message(STATUS "Adding compatibility kit: ${kit}") + add_compatibility_kit(${kit}) endif() endforeach() From de2aaebc7031ad8cd88f0fda147eb35cd2e60780 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 18 Dec 2025 14:16:34 +0000 Subject: [PATCH 022/196] add hooks-named, hooks-skipped and hooks-undefined compatibility tests --- compatibility/compatibility.cpp | 3 + compatibility/hooks-named/hooks-named.cpp | 17 ++++ compatibility/hooks-skipped/hooks-skipped.cpp | 42 +++++++++ .../hooks-undefined/hooks-undefined.cpp | 12 +++ cucumber_cpp/library/Body.cpp | 13 +++ cucumber_cpp/library/HookRegistry.cpp | 29 ++++--- cucumber_cpp/library/HookRegistry.hpp | 86 +------------------ cucumber_cpp/library/Hooks.hpp | 48 +++++------ .../library/assemble/AssembleTestSuites.cpp | 2 +- .../library/engine/ExecutionContext.cpp | 11 +++ .../library/engine/ExecutionContext.hpp | 30 +++++++ cucumber_cpp/library/engine/Step.cpp | 5 -- cucumber_cpp/library/engine/Step.hpp | 14 --- .../library/support/SupportCodeLibrary.cpp | 18 ++-- .../library/support/SupportCodeLibrary.hpp | 57 ++++++++++-- 15 files changed, 229 insertions(+), 158 deletions(-) create mode 100644 compatibility/hooks-named/hooks-named.cpp create mode 100644 compatibility/hooks-skipped/hooks-skipped.cpp create mode 100644 compatibility/hooks-undefined/hooks-undefined.cpp diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 6db75327..b0de1c30 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -329,6 +329,9 @@ namespace compatibility TEST(CompatibilityTest, KIT_NAME) { + if (std::string{ KIT_FOLDER }.ends_with("markdown")) + GTEST_SKIP(); + compatibility::StopwatchIncremental stopwatch; compatibility::TimestampGeneratorIncremental timestampGenerator; compatibility::RunDevkit(compatibility::LoadDevkit()); diff --git a/compatibility/hooks-named/hooks-named.cpp b/compatibility/hooks-named/hooks-named.cpp new file mode 100644 index 00000000..2e385f9b --- /dev/null +++ b/compatibility/hooks-named/hooks-named.cpp @@ -0,0 +1,17 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_SCENARIO(.name = "A named before hook") +{ + // no-op +} + +WHEN(R"(a step passes)") +{ + // no-op +} + +HOOK_AFTER_SCENARIO(.name = "A named after hook") +{ + // no-op +} diff --git a/compatibility/hooks-skipped/hooks-skipped.cpp b/compatibility/hooks-skipped/hooks-skipped.cpp new file mode 100644 index 00000000..38b435a4 --- /dev/null +++ b/compatibility/hooks-skipped/hooks-skipped.cpp @@ -0,0 +1,42 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_SCENARIO() +{ + // no-op +} + +HOOK_BEFORE_SCENARIO("@skip-before") +{ + Skipped(); +} + +HOOK_BEFORE_SCENARIO() +{ + // no-op +} + +WHEN(R"(a normal step)") +{ + // no-op +} + +WHEN(R"(a step that skips)") +{ + Skipped(); +} + +HOOK_AFTER_SCENARIO() +{ + // no-op +} + +HOOK_AFTER_SCENARIO("@skip-after") +{ + Skipped(); +} + +HOOK_AFTER_SCENARIO() +{ + // no-op +} diff --git a/compatibility/hooks-undefined/hooks-undefined.cpp b/compatibility/hooks-undefined/hooks-undefined.cpp new file mode 100644 index 00000000..5d96d8b1 --- /dev/null +++ b/compatibility/hooks-undefined/hooks-undefined.cpp @@ -0,0 +1,12 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +HOOK_BEFORE_SCENARIO() +{ + // no-op +} + +HOOK_AFTER_SCENARIO() +{ + // no-op +} diff --git a/cucumber_cpp/library/Body.cpp b/cucumber_cpp/library/Body.cpp index 09a5d97a..614eb8e1 100644 --- a/cucumber_cpp/library/Body.cpp +++ b/cucumber_cpp/library/Body.cpp @@ -2,6 +2,7 @@ #include "cucumber/messages/exception.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/support/Duration.hpp" #include "gtest/gtest.h" #include @@ -57,6 +58,18 @@ namespace cucumber_cpp::library support::Stopwatch::Instance().Start(); Execute(args); } + catch (const engine::StepSkipped& e) + { + testStepResult.status = cucumber::messages::test_step_result_status::SKIPPED; + if (!e.message.empty()) + testStepResult.message = e.message; + } + catch (const engine::StepPending& e) + { + testStepResult.status = cucumber::messages::test_step_result_status::PENDING; + if (!e.message.empty()) + testStepResult.message = e.message; + } catch (const FatalError& error) { testStepResult.status = cucumber::messages::test_step_result_status::FAILED; diff --git a/cucumber_cpp/library/HookRegistry.cpp b/cucumber_cpp/library/HookRegistry.cpp index f640d781..dae116da 100644 --- a/cucumber_cpp/library/HookRegistry.cpp +++ b/cucumber_cpp/library/HookRegistry.cpp @@ -67,19 +67,20 @@ namespace cucumber_cpp::library : engine::ExecutionContext{ broadCaster, context, stepOrHookStarted } {} - HookRegistry::Definition::Definition(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) + HookRegistry::Definition::Definition(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation) : type{ type } - , tagExpression{ tag_expression::Parse(expression) } + , tagExpression{ tag_expression::Parse(expression.value_or("")) } , factory{ factory } , hook{ .id = id, + .name = name.has_value() ? std::make_optional(name.value()) : std::nullopt, .source_reference = cucumber::messages::source_reference{ .uri = sourceLocation.file_name(), .location = cucumber::messages::location{ .line = sourceLocation.line(), }, }, - .tag_expression = !expression.empty() ? std::make_optional(std::string{ expression }) : std::nullopt, + .tag_expression = expression.has_value() ? std::make_optional(expression.value()) : std::nullopt, .type = HookTypeMap.contains(type) ? std::make_optional(HookTypeMap.at(type)) : std::nullopt, } {} @@ -92,7 +93,7 @@ namespace cucumber_cpp::library void HookRegistry::LoadHooks() { for (const auto& matcher : support::DefinitionRegistration::Instance().GetHooks()) - Register(matcher.id, matcher.type, matcher.expression, matcher.factory, matcher.sourceLocation); + Register(matcher.id, matcher.type, matcher.expression, matcher.name, matcher.factory, matcher.sourceLocation); } std::vector HookRegistry::FindIds(HookType hookType, std::span tags) const @@ -127,18 +128,18 @@ namespace cucumber_cpp::library return registry.at(id); } - void HookRegistry::Register(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) + void HookRegistry::Register(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation) { - registry.emplace(id, Definition{ id, type, expression, factory, sourceLocation }); + registry.emplace(id, Definition{ id, type, expression, name, factory, sourceLocation }); } - std::span HookRegistration::GetEntries() - { - return registry; - } + // std::span HookRegistration::GetEntries() + // { + // return registry; + // } - std::span HookRegistration::GetEntries() const - { - return registry; - } + // std::span HookRegistration::GetEntries() const + // { + // return registry; + // } } diff --git a/cucumber_cpp/library/HookRegistry.hpp b/cucumber_cpp/library/HookRegistry.hpp index 76f54cca..346f1799 100644 --- a/cucumber_cpp/library/HookRegistry.hpp +++ b/cucumber_cpp/library/HookRegistry.hpp @@ -14,6 +14,7 @@ #include #include #include +#include #include #include #include @@ -23,7 +24,6 @@ namespace cucumber_cpp::library { - enum struct HookType { beforeAll, @@ -74,7 +74,7 @@ namespace cucumber_cpp::library { struct Definition { - Definition(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation); + Definition(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation); HookType type; std::unique_ptr tagExpression; @@ -112,91 +112,11 @@ namespace cucumber_cpp::library const Definition& GetDefinitionById(std::string id) const; private: - void Register(std::string id, HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation); + void Register(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation); cucumber::gherkin::id_generator_ptr idGenerator; std::map registry; }; - - struct GlobalHook - { - std::string_view name{ "anonymous" }; - std::int32_t order{ 0 }; - }; - - struct Hook - { - std::string_view tagExpression{ "" }; - std::string_view name{ "anonymous" }; - std::int32_t order{ 0 }; - }; - - struct HookRegistration - { - private: - HookRegistration() = default; - - public: - static inline HookRegistration& Instance() - { - static HookRegistration instance; - return instance; - } - - struct Entry - { - Entry(HookType type, std::string_view expression, HookFactory factory, std::source_location sourceLocation) - : type(type) - , expression{ expression } - , factory(factory) - , sourceLocation{ sourceLocation } - {} - - HookType type; - std::string_view expression; - HookFactory factory; - std::source_location sourceLocation; - std::string id{ "unassigned" }; - }; - - template - static std::size_t Register(std::string_view tagExpression, HookType hookType, std::source_location sourceLocation = std::source_location::current()); - template - static std::size_t Register(Hook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); - template - static std::size_t Register(GlobalHook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); - - std::span GetEntries(); - [[nodiscard]] std::span GetEntries() const; - - private: - std::vector registry; - }; - - ////////////////////////// - // implementation // - ////////////////////////// - - template - std::size_t HookRegistration::Register(std::string_view tagExpression, HookType hookType, std::source_location sourceLocation) - { - Instance().registry.emplace_back(hookType, tagExpression, HookBodyFactory, sourceLocation); - return Instance().registry.size(); - } - - template - std::size_t HookRegistration::Register(Hook hook, HookType hookType, std::source_location sourceLocation) - { - Instance().registry.emplace_back(hookType, hook.tagExpression, HookBodyFactory, sourceLocation); - return Instance().registry.size(); - } - - template - std::size_t HookRegistration::Register(GlobalHook hook, HookType hookType, std::source_location sourceLocation) - { - Instance().registry.emplace_back(hookType, "", HookBodyFactory, sourceLocation); - return Instance().registry.size(); - } } #endif diff --git a/cucumber_cpp/library/Hooks.hpp b/cucumber_cpp/library/Hooks.hpp index e894a68f..b29531c2 100644 --- a/cucumber_cpp/library/Hooks.hpp +++ b/cucumber_cpp/library/Hooks.hpp @@ -11,44 +11,44 @@ // #define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::HookRegistration::Register, cucumber_cpp::library::HookBase) #define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::support::DefinitionRegistration::Register, cucumber_cpp::library::HookBase) -#define HOOK_BEFORE_ALL(...) \ - HOOK_( \ - (cucumber_cpp::library::GlobalHook{ __VA_ARGS__ }), \ +#define HOOK_BEFORE_ALL(...) \ + HOOK_( \ + (cucumber_cpp::library::support::GlobalHook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::beforeAll) -#define HOOK_AFTER_ALL(...) \ - HOOK_( \ - (cucumber_cpp::library::GlobalHook{ __VA_ARGS__ }), \ +#define HOOK_AFTER_ALL(...) \ + HOOK_( \ + (cucumber_cpp::library::support::GlobalHook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::afterAll) -#define HOOK_BEFORE_FEATURE(...) \ - HOOK_( \ - (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ +#define HOOK_BEFORE_FEATURE(...) \ + HOOK_( \ + (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::beforeFeature) -#define HOOK_AFTER_FEATURE(...) \ - HOOK_( \ - (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ +#define HOOK_AFTER_FEATURE(...) \ + HOOK_( \ + (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::afterFeature) -#define HOOK_BEFORE_SCENARIO(...) \ - HOOK_( \ - (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ +#define HOOK_BEFORE_SCENARIO(...) \ + HOOK_( \ + (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::before) -#define HOOK_AFTER_SCENARIO(...) \ - HOOK_( \ - (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ +#define HOOK_AFTER_SCENARIO(...) \ + HOOK_( \ + (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::after) -#define HOOK_BEFORE_STEP(...) \ - HOOK_( \ - (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ +#define HOOK_BEFORE_STEP(...) \ + HOOK_( \ + (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::beforeStep) -#define HOOK_AFTER_STEP(...) \ - HOOK_( \ - (cucumber_cpp::library::Hook{ __VA_ARGS__ }), \ +#define HOOK_AFTER_STEP(...) \ + HOOK_( \ + (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ cucumber_cpp::library::HookType::afterStep) #endif diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index b8029a95..2332349d 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -71,7 +71,7 @@ namespace cucumber_cpp::library::assemble stepMatchArgumentsLists); } - for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags)) + for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags) | std::views::reverse) testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); broadcaster.BroadcastEvent(cucumber::messages::envelope{ .test_case = testCase }); diff --git a/cucumber_cpp/library/engine/ExecutionContext.cpp b/cucumber_cpp/library/engine/ExecutionContext.cpp index 3fda1b7d..8f2201cb 100644 --- a/cucumber_cpp/library/engine/ExecutionContext.cpp +++ b/cucumber_cpp/library/engine/ExecutionContext.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -98,6 +99,16 @@ namespace cucumber_cpp::library::engine }); } + void ExecutionContext::Skipped(const std::string& message, std::source_location current) noexcept(false) + { + throw StepSkipped{ message, current }; + } + + void ExecutionContext::Pending(const std::string& message, std::source_location current) noexcept(false) + { + throw StepPending{ message, current }; + } + void ExecutionContext::Attach(std::string data, cucumber::messages::attachment_content_encoding encoding, OptionsOrMediaType mediaType) { const auto options = std::holds_alternative(mediaType) diff --git a/cucumber_cpp/library/engine/ExecutionContext.hpp b/cucumber_cpp/library/engine/ExecutionContext.hpp index b839f906..bc332f9a 100644 --- a/cucumber_cpp/library/engine/ExecutionContext.hpp +++ b/cucumber_cpp/library/engine/ExecutionContext.hpp @@ -6,13 +6,40 @@ #include "cucumber/messages/test_step_started.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include #include +#include #include +#include #include namespace cucumber_cpp::library::engine { + struct StepSkipped : std::exception + { + StepSkipped(std::string message, std::source_location sourceLocation) + : message{ std::move(message) } + , sourceLocation{ sourceLocation } + { + } + + std::string message; + std::source_location sourceLocation; + }; + + struct StepPending : std::exception + { + StepPending(std::string message, std::source_location sourceLocation) + : message{ std::move(message) } + , sourceLocation{ sourceLocation } + { + } + + std::string message; + std::source_location sourceLocation; + }; + struct AttachOptions { std::string mediaType; @@ -32,6 +59,9 @@ namespace cucumber_cpp::library::engine void Log(std::string text); void Link(std::string url, std::optional title = std::nullopt); + [[noreturn]] static void Skipped(const std::string& message = "", std::source_location current = std::source_location::current()) noexcept(false); + [[noreturn]] static void Pending(const std::string& message = "", std::source_location current = std::source_location::current()) noexcept(false); + Context& context; private: diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index f8daa557..c313f816 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -32,9 +32,4 @@ namespace cucumber_cpp::library::engine { // CucumberTestServer::Instance()->RunStep(step, cucumber::messages::pickle_step_type::OUTCOME); } - - void Step::Pending(const std::string& message, std::source_location current) noexcept(false) - { - throw StepPending{ message, current }; - } } diff --git a/cucumber_cpp/library/engine/Step.hpp b/cucumber_cpp/library/engine/Step.hpp index b57d4b84..678bbd7b 100644 --- a/cucumber_cpp/library/engine/Step.hpp +++ b/cucumber_cpp/library/engine/Step.hpp @@ -20,18 +20,6 @@ namespace cucumber_cpp::library::engine { struct Step : ExecutionContext { - struct StepPending : std::exception - { - StepPending(std::string message, std::source_location sourceLocation) - : message{ std::move(message) } - , sourceLocation{ sourceLocation } - { - } - - std::string message; - std::source_location sourceLocation; - }; - Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, std::optional> table, const std::optional& docString); virtual ~Step() = default; @@ -50,8 +38,6 @@ namespace cucumber_cpp::library::engine void When(const std::string& step) const; void Then(const std::string& step) const; - [[noreturn]] static void Pending(const std::string& message, std::source_location current = std::source_location::current()) noexcept(false); - std::optional> table; const std::optional& docString; }; diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index dd04ac71..c1f3e6c2 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -21,9 +21,10 @@ namespace std } } -namespace cucumber_cpp::library +namespace cucumber_cpp::library::support { - std::strong_ordering operator<=>(const HookRegistration::Entry& left, const HookRegistration::Entry& right) + + std::strong_ordering operator<=>(const HookEntry& left, const HookEntry& right) { return left.sourceLocation <=> right.sourceLocation; } @@ -32,10 +33,7 @@ namespace cucumber_cpp::library { return left.sourceLocation <=> right.sourceLocation; } -} -namespace cucumber_cpp::library::support -{ std::strong_ordering operator<=>(const Entry& left, const Entry& right) { constexpr static auto sourceLocationVisitor = [](const auto& entry) @@ -79,28 +77,28 @@ namespace cucumber_cpp::library::support return { allSteps.begin(), allSteps.end() }; } - std::vector DefinitionRegistration::GetHooks() + std::vector DefinitionRegistration::GetHooks() { auto allSteps = registry | std::views::values | std::views::filter([](const Entry& entry) { - return std::holds_alternative(entry); + return std::holds_alternative(entry); }) | std::views::transform([](const Entry& entry) { - return std::get(entry); + return std::get(entry); }); return { allSteps.begin(), allSteps.end() }; } std::size_t DefinitionRegistration::Register(Hook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation) { - registry.emplace(sourceLocation, HookRegistration::Entry{ hookType, hook.tagExpression, factory, sourceLocation }); + registry.emplace(sourceLocation, HookEntry{ hookType, hook, factory, sourceLocation }); return registry.size(); } std::size_t DefinitionRegistration::Register(GlobalHook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation) { - registry.emplace(sourceLocation, HookRegistration::Entry{ hookType, "", factory, sourceLocation }); + registry.emplace(sourceLocation, HookEntry{ hookType, hook, factory, sourceLocation }); return registry.size(); } diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp index ca7e20bd..caa2a069 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.hpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -8,21 +8,64 @@ #include "cucumber_cpp/library/engine/StepType.hpp" #include #include +#include #include #include +#include #include +#include #include #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::support { - std::strong_ordering operator<=>(const HookRegistration::Entry& left, const HookRegistration::Entry& right); + struct GlobalHook + + { + std::string_view name{}; + std::int32_t order{ 0 }; + }; + + struct Hook + { + std::string_view expression{ "" }; + std::string_view name{}; + std::int32_t order{ 0 }; + }; + + struct HookEntry + { + HookEntry(HookType type, GlobalHook hook, HookFactory factory, std::source_location sourceLocation) + : type{ type } + , expression{ std::nullopt } + , name{ hook.name.empty() ? std::nullopt : std::make_optional(hook.name) } + , order{ hook.order } + , factory{ factory } + , sourceLocation{ sourceLocation } + {} + + HookEntry(HookType type, Hook hook, HookFactory factory, std::source_location sourceLocation) + : type{ type } + , expression{ hook.expression.empty() ? std::nullopt : std::make_optional(hook.expression) } + , name{ hook.name.empty() ? std::nullopt : std::make_optional(hook.name) } + , order{ hook.order } + , factory{ factory } + , sourceLocation{ sourceLocation } + {} + + HookType type; + std::optional expression; + std::optional name; + std::int32_t order; + HookFactory factory; + std::source_location sourceLocation; + std::string id{ "unassigned" }; + }; + + std::strong_ordering operator<=>(const HookEntry& left, const HookEntry& right); std::strong_ordering operator<=>(const StepStringRegistration::Entry& left, const StepStringRegistration::Entry& right); -} -namespace cucumber_cpp::library::support -{ struct SupportCodeLibrary { HookRegistry& hookRegistry; @@ -30,7 +73,7 @@ namespace cucumber_cpp::library::support cucumber_expression::ParameterRegistry& parameterRegistry; }; - using Entry = std::variant; + using Entry = std::variant; std::strong_ordering operator<=>(const Entry& left, const Entry& right); @@ -45,7 +88,7 @@ namespace cucumber_cpp::library::support void LoadIds(cucumber::gherkin::id_generator_ptr idGenerator); std::vector GetSteps(); - std::vector GetHooks(); + std::vector GetHooks(); template static std::size_t Register(Hook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); From d1a3e524b3a889bfe2d3daf32e433dd325f3c54b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 18 Dec 2025 23:40:30 +0000 Subject: [PATCH 023/196] add multiple-features and multiple-features-reverses compatibility tests --- compatibility/CMakeLists.txt | 5 ++++ compatibility/compatibility.cpp | 25 +++++++++++-------- .../multiple-features-reversed.cpp | 7 ++++++ .../multiple-features/multiple-features.cpp | 7 ++++++ cucumber_cpp/library/Application.cpp | 15 ++++------- cucumber_cpp/library/api/Gherkin.cpp | 6 ++--- cucumber_cpp/library/api/Gherkin.hpp | 4 +-- cucumber_cpp/library/api/RunCucumber.cpp | 18 ++++++++++--- .../library/assemble/AssembleTestSuites.cpp | 12 ++++++--- .../library/assemble/AssembleTestSuites.hpp | 4 +-- .../library/assemble/AssembledTestSuite.hpp | 4 +-- cucumber_cpp/library/runtime/MakeRuntime.cpp | 6 ++--- cucumber_cpp/library/runtime/MakeRuntime.hpp | 4 +-- .../library/runtime/SerialRuntimeAdapter.cpp | 5 ++-- .../library/runtime/SerialRuntimeAdapter.hpp | 7 +++--- cucumber_cpp/library/support/Types.hpp | 12 +++++++-- 16 files changed, 90 insertions(+), 51 deletions(-) create mode 100644 compatibility/multiple-features-reversed/multiple-features-reversed.cpp create mode 100644 compatibility/multiple-features/multiple-features.cpp diff --git a/compatibility/CMakeLists.txt b/compatibility/CMakeLists.txt index b5daed10..ff923e22 100644 --- a/compatibility/CMakeLists.txt +++ b/compatibility/CMakeLists.txt @@ -19,18 +19,23 @@ function(add_compatibility_kit name) compatibility.cpp ) + if (EXISTS ${CMAKE_CURRENT_LIST_DIR}/${name}/${name}.cpp) target_sources(${libname} PRIVATE ${name}/${name}.cpp ) + else() + set(SKIP_TEST On) endif() string(REPLACE "-" "_" KITNAME ${name}) target_compile_definitions(${libname} PRIVATE KIT_NAME=${KITNAME} + KIT_STRING="${name}" KIT_FOLDER="${CMAKE_CURRENT_SOURCE_DIR}/${name}" KIT_NDJSON_FILE="${CMAKE_CURRENT_SOURCE_DIR}/${name}/${name}.ndjson" + $<$:SKIP_TEST> ) endfunction() diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index b0de1c30..50a13822 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -10,11 +10,9 @@ #include "nlohmann/json_fwd.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include #include #include #include -#include #include #include #include @@ -24,10 +22,9 @@ #include #include #include +#include #include -#include #include -#include #ifndef KIT_FOLDER #error KIT_FOLDER is not defined @@ -45,7 +42,7 @@ namespace compatibility { struct Devkit { - std::vector paths; + std::set paths; std::string tagExpression; std::size_t retry; std::filesystem::path ndjsonFile; @@ -255,16 +252,19 @@ namespace compatibility return std::filesystem::is_regular_file(entry) && entry.path().has_extension() && entry.path().extension() == ".feature"; } - std::vector GetFeatureFiles(std::vector paths) + std::set GetFeatureFiles(std::set paths) { - std::vector files; + std::set files; for (const auto feature : paths) if (std::filesystem::is_directory(feature)) for (const auto& entry : std::filesystem::directory_iterator{ feature } | std::views::filter(IsFeatureFile)) - files.emplace_back(entry.path()); + { + std::cout << " found feature file: " << entry.path() << "\n"; + files.insert(entry.path()); + } else - files.emplace_back(feature); + files.insert(feature); return files; } @@ -300,11 +300,13 @@ namespace compatibility void RunDevkit(Devkit devkit) { devkit.paths = GetFeatureFiles(devkit.paths); + const auto isReversed = std::string{ KIT_STRING }.ends_with("-reversed"); cucumber_cpp::library::support::RunOptions runOptions{ .sources = { .paths = devkit.paths, .tagExpression = devkit.tagExpression, + .ordering = isReversed ? cucumber_cpp::library::support::RunOptions::Ordering::reverse : cucumber_cpp::library::support::RunOptions::Ordering::defined, }, .runtime = { .retry = devkit.retry, @@ -329,8 +331,9 @@ namespace compatibility TEST(CompatibilityTest, KIT_NAME) { - if (std::string{ KIT_FOLDER }.ends_with("markdown")) - GTEST_SKIP(); +#ifdef SKIP_TEST + GTEST_SKIP(); +#endif compatibility::StopwatchIncremental stopwatch; compatibility::TimestampGeneratorIncremental timestampGenerator; diff --git a/compatibility/multiple-features-reversed/multiple-features-reversed.cpp b/compatibility/multiple-features-reversed/multiple-features-reversed.cpp new file mode 100644 index 00000000..08abbd51 --- /dev/null +++ b/compatibility/multiple-features-reversed/multiple-features-reversed.cpp @@ -0,0 +1,7 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +STEP(R"(an order for {string})", ([[maybe_unused]] const std::string& order)) +{ + // no-op +} diff --git a/compatibility/multiple-features/multiple-features.cpp b/compatibility/multiple-features/multiple-features.cpp new file mode 100644 index 00000000..08abbd51 --- /dev/null +++ b/compatibility/multiple-features/multiple-features.cpp @@ -0,0 +1,7 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +STEP(R"(an order for {string})", ([[maybe_unused]] const std::string& order)) +{ + // no-op +} diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 4205b547..688605a0 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -1,17 +1,12 @@ #include "cucumber_cpp/library/Application.hpp" -#include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/Errors.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/support/Types.hpp" -#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include @@ -28,10 +23,10 @@ #include #include #include +#include #include #include #include -#include namespace cucumber_cpp::library { @@ -59,16 +54,16 @@ namespace cucumber_cpp::library return std::filesystem::is_regular_file(entry) && entry.path().has_extension() && entry.path().extension() == ".feature"; } - std::vector GetFeatureFiles(Application::Options& options) + std::set GetFeatureFiles(Application::Options& options) { - std::vector files; + std::set files; for (const auto feature : options.features | std::views::transform(ToFileSystemPath)) if (std::filesystem::is_directory(feature)) for (const auto& entry : std::filesystem::directory_iterator{ feature } | std::views::filter(IsFeatureFile)) - files.emplace_back(entry.path()); + files.emplace(entry.path()); else - files.emplace_back(feature); + files.emplace(feature); return files; } diff --git a/cucumber_cpp/library/api/Gherkin.cpp b/cucumber_cpp/library/api/Gherkin.cpp index 66330fdb..b3f4f629 100644 --- a/cucumber_cpp/library/api/Gherkin.cpp +++ b/cucumber_cpp/library/api/Gherkin.cpp @@ -11,14 +11,14 @@ #include "cucumber/messages/source_reference.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include -#include namespace cucumber_cpp::library::api { - std::vector CollectPickles(const support::RunOptions::Sources& sources, cucumber::gherkin::id_generator_ptr idGenerator, util::Broadcaster& broadcaster) + std::list CollectPickles(const support::RunOptions::Sources& sources, cucumber::gherkin::id_generator_ptr idGenerator, util::Broadcaster& broadcaster) { - std::vector pickleSources; + std::list pickleSources; cucumber::gherkin::parser<> parser{ idGenerator }; diff --git a/cucumber_cpp/library/api/Gherkin.hpp b/cucumber_cpp/library/api/Gherkin.hpp index cb08b994..3bbc7b63 100644 --- a/cucumber_cpp/library/api/Gherkin.hpp +++ b/cucumber_cpp/library/api/Gherkin.hpp @@ -4,11 +4,11 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include +#include namespace cucumber_cpp::library::api { - std::vector CollectPickles(const support::RunOptions::Sources& sources, cucumber::gherkin::id_generator_ptr idGenerator, util::Broadcaster& broadcaster); + std::list CollectPickles(const support::RunOptions::Sources& sources, cucumber::gherkin::id_generator_ptr idGenerator, util::Broadcaster& broadcaster); } #endif diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 5e74941e..b60b2601 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -18,10 +18,10 @@ #include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include +#include #include #include #include -#include namespace cucumber_cpp::library::api { @@ -125,11 +125,23 @@ namespace cucumber_cpp::library::api return tagExpression->Evaluate(pickle.pickle->tags); }; auto filteredPicklesView = pickleSources | std::views::filter(pickleFilter); - std::vector filteredPickles(filteredPicklesView.begin(), filteredPicklesView.end()); + + const auto createOrderedPickleList = [](auto ordered) -> std::list + { + return { ordered.begin(), ordered.end() }; + }; + const auto orderPickles = [&](auto pickles) -> std::list + { + if (options.sources.ordering == support::RunOptions::Ordering::defined) + return createOrderedPickleList(pickles); + else + return createOrderedPickleList(pickles | std::views::reverse); + }; + std::list orderedPickles = orderPickles(filteredPicklesView); EmitSupportCodeMessages(supportCodeLibrary, broadcaster, idGenerator); - auto runtime = runtime::MakeRuntime(options.runtime, broadcaster, filteredPickles, supportCodeLibrary, idGenerator, programContext); + auto runtime = runtime::MakeRuntime(options.runtime, broadcaster, orderedPickles, supportCodeLibrary, idGenerator, programContext); auto& listeners = testing::UnitTest::GetInstance()->listeners(); auto* defaultEventListener = listeners.Release(listeners.default_result_printer()); diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 2332349d..3e640690 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -3,13 +3,13 @@ #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_case.hpp" -#include "cucumber/messages/test_step.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include #include #include @@ -25,9 +25,10 @@ namespace cucumber_cpp::library::assemble std::vector AssembleTestSuites(support::SupportCodeLibrary supportCodeLibrary, std::string_view testRunStartedId, util::Broadcaster& broadcaster, - std::span sourcedPickles, + const std::list& sourcedPickles, cucumber::gherkin::id_generator_ptr idGenerator) { + std::vector testUris; std::map assembledTestSuiteMap; for (const auto& pickleSource : sourcedPickles) @@ -77,7 +78,10 @@ namespace cucumber_cpp::library::assemble broadcaster.BroadcastEvent(cucumber::messages::envelope{ .test_case = testCase }); if (!assembledTestSuiteMap.contains(pickleSource.gherkinDocument->uri.value())) + { + testUris.emplace_back(pickleSource.gherkinDocument->uri.value()); assembledTestSuiteMap.emplace(pickleSource.gherkinDocument->uri.value(), *pickleSource.gherkinDocument); + } assembledTestSuiteMap.at(pickleSource.gherkinDocument->uri.value()).testCases.emplace_back(*pickleSource.pickle, testCase); } @@ -85,8 +89,8 @@ namespace cucumber_cpp::library::assemble std::vector assembledTestSuites; assembledTestSuites.reserve(assembledTestSuiteMap.size()); - for (auto assembledTestSuiteValues : assembledTestSuiteMap | std::views::values) - assembledTestSuites.emplace_back(std::move(assembledTestSuiteValues)); + for (auto uri : testUris) + assembledTestSuites.emplace_back(std::move(assembledTestSuiteMap.at(uri))); return assembledTestSuites; } diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.hpp b/cucumber_cpp/library/assemble/AssembleTestSuites.hpp index e488ac1e..10b0988b 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.hpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.hpp @@ -6,7 +6,7 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include +#include #include #include @@ -16,7 +16,7 @@ namespace cucumber_cpp::library::assemble support::SupportCodeLibrary supportCodeLibrary, std::string_view testRunStartedId, util::Broadcaster& broadcaster, - std::span sourcedPickles, + const std::list& sourcedPickles, cucumber::gherkin::id_generator_ptr idGenerator); } diff --git a/cucumber_cpp/library/assemble/AssembledTestSuite.hpp b/cucumber_cpp/library/assemble/AssembledTestSuite.hpp index 69af6793..814fcc3e 100644 --- a/cucumber_cpp/library/assemble/AssembledTestSuite.hpp +++ b/cucumber_cpp/library/assemble/AssembledTestSuite.hpp @@ -3,14 +3,14 @@ #include "cucumber/messages/gherkin_document.hpp" #include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" -#include +#include namespace cucumber_cpp::library::assemble { struct AssembledTestSuite { const cucumber::messages::gherkin_document& gherkinDocument; - std::vector testCases; + std::list testCases; }; } diff --git a/cucumber_cpp/library/runtime/MakeRuntime.cpp b/cucumber_cpp/library/runtime/MakeRuntime.cpp index 0deb5c62..b713ae2c 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.cpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.cpp @@ -6,13 +6,13 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include -#include #include namespace cucumber_cpp::library::runtime { - std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, std::span sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) + std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) { return std::make_unique( testRunStartedId, @@ -24,7 +24,7 @@ namespace cucumber_cpp::library::runtime programContext); } - std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, std::span sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) + std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) { const auto testRunStartedId{ idGenerator->next_id() }; return std::make_unique( diff --git a/cucumber_cpp/library/runtime/MakeRuntime.hpp b/cucumber_cpp/library/runtime/MakeRuntime.hpp index 43d52bb4..7c79896a 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.hpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.hpp @@ -6,15 +6,15 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include -#include #include namespace cucumber_cpp::library::runtime { std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); - std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, std::span sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); + std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); } #endif diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp index 3db99ad8..a8334f74 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp @@ -3,14 +3,13 @@ #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" #include "cucumber_cpp/library/runtime/Worker.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" +#include #include -#include #include namespace cucumber_cpp::library::runtime @@ -18,7 +17,7 @@ namespace cucumber_cpp::library::runtime SerialRuntimeAdapter::SerialRuntimeAdapter(std::string testRunStartedId, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, - std::span sourcedPickles, + const std::list& sourcedPickles, const support::RunOptions::Runtime& options, support::SupportCodeLibrary supportCodeLibrary, Context& programContext) diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp index 96590af9..e221439f 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp @@ -3,11 +3,10 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber_cpp/CucumberCpp.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include +#include #include namespace cucumber_cpp::library::runtime @@ -17,7 +16,7 @@ namespace cucumber_cpp::library::runtime SerialRuntimeAdapter(std::string testRunStartedId, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, - std::span sourcedPickles, + const std::list& sourcedPickles, const support::RunOptions::Runtime& options, support::SupportCodeLibrary supportCodeLibrary, Context& programContext); @@ -28,7 +27,7 @@ namespace cucumber_cpp::library::runtime std::string testRunStartedId; util::Broadcaster& broadcaster; cucumber::gherkin::id_generator_ptr idGenerator; - std::span sourcedPickles; + const std::list& sourcedPickles; const support::RunOptions::Runtime& options; support::SupportCodeLibrary supportCodeLibrary; Context& programContext; diff --git a/cucumber_cpp/library/support/Types.hpp b/cucumber_cpp/library/support/Types.hpp index fdd65ab1..44b4ee91 100644 --- a/cucumber_cpp/library/support/Types.hpp +++ b/cucumber_cpp/library/support/Types.hpp @@ -4,11 +4,11 @@ #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle.hpp" -#include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" #include #include #include #include +#include #include #include @@ -16,10 +16,18 @@ namespace cucumber_cpp::library::support { struct RunOptions { + enum class Ordering + { + defined, + reverse, + }; + struct Sources { - std::span paths; + std::set paths; std::string_view tagExpression; + Ordering ordering; + } sources; struct Runtime From 83c6e5a3bfeae8cbc5015836c014bd5ba597c510 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 19 Dec 2025 09:46:45 +0000 Subject: [PATCH 024/196] fix clang-msvc build --- CMakeLists.txt | 15 ++++++++------- cucumber_cpp/library/api/Gherkin.cpp | 2 +- cucumber_cpp/library/api/RunCucumber.cpp | 2 +- .../formatter/helper/TestCaseAttemptFormatter.cpp | 2 +- .../formatter/helper/TestCaseAttemptParser.cpp | 4 ++-- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b4a82feb..fca37d0a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,13 +11,9 @@ endif() if (CCR_STANDALONE) set(CCR_DEFAULTOPT On) -else() - set(CCR_DEFAULTOPT Off) -endif() - -if (CCR_STANDALONE) set(CCR_EXCLUDE_FROM_ALL "") else() + set(CCR_DEFAULTOPT Off) set(CCR_EXCLUDE_FROM_ALL "EXCLUDE_FROM_ALL") endif() @@ -27,8 +23,13 @@ option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements set(BUILD_SHARED_LIBS Off CACHE STRING "") -add_compile_options(-fsanitize=address) -add_link_options(-fsanitize=address) +message(STATUS "CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") +message(STATUS "CXX_SIMULATE_ID: ${CMAKE_CXX_SIMULATE_ID}") + +if (NOT CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) +endif() if (CCR_BUILD_TESTS) ccr_enable_testing() diff --git a/cucumber_cpp/library/api/Gherkin.cpp b/cucumber_cpp/library/api/Gherkin.cpp index b3f4f629..e8733425 100644 --- a/cucumber_cpp/library/api/Gherkin.cpp +++ b/cucumber_cpp/library/api/Gherkin.cpp @@ -28,7 +28,7 @@ namespace cucumber_cpp::library::api { envelope.source = { .uri = path.string(), - .data = cucumber::gherkin::slurp(path), + .data = cucumber::gherkin::slurp(path.string()), }; broadcaster.BroadcastEvent(envelope); diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index b60b2601..12e6423f 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -53,7 +53,7 @@ namespace cucumber_cpp::library::api .type = stepDefinition.patternType, }, .source_reference = { - .uri = stepDefinition.uri, + .uri = stepDefinition.uri.string(), .location = cucumber::messages::location{ .line = stepDefinition.line, }, diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp index 39d6cef2..edff687f 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp @@ -16,7 +16,7 @@ namespace cucumber_cpp::library::formatter::helper namespace { // to be moved tyo LocationHelpers.hpp - std::string FormatLocation(LineAndUri obj, std::optional cwd = std::nullopt) + std::string FormatLocation(LineAndUri obj, std::optional cwd = std::nullopt) { std::string uri = obj.uri; if (cwd) diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp index 9c2ad2d7..24198875 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp @@ -54,7 +54,7 @@ namespace cucumber_cpp::library::formatter::helper { const auto& definition = supportCode.stepRegistry.GetDefinitionById(testStep.step_definition_ids->front()); parsedTestStep.actionLocation = { - .uri = definition.uri, + .uri = definition.uri.string(), .line = definition.line, }; } @@ -62,7 +62,7 @@ namespace cucumber_cpp::library::formatter::helper if (testStep.pickle_step_id) { parsedTestStep.location = { - .uri = pickleUri, + .uri = pickleUri.string(), .line = gherkinStepMap.at(pickleStep.ast_node_ids.front()).location.line }; parsedTestStep.text = pickleStep.text; From 13484a5f0db6c206cd0fe2400592aafa2f3d89d8 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 19 Dec 2025 10:27:35 +0000 Subject: [PATCH 025/196] fix clang-msvc build --- external/jupyter-xeus/cpp-terminal/CMakeLists.txt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt index b9fe8d3a..d20fdd9c 100644 --- a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt +++ b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt @@ -6,10 +6,12 @@ FetchContent_Declare( set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) -add_compile_options( - -Wno-unused-variable - -Wno-unused-but-set-variable -) +if (NOT CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") + add_compile_options( + -Wno-unused-variable + -Wno-unused-but-set-variable + ) +endif() set(CPPTERMINAL_BUILD_EXAMPLES Off CACHE STRING "") set(CPPTERMINAL_ENABLE_INSTALL Off CACHE STRING "") From c7ee029ddf0fa73e4d80c781839142647d4f93ef Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 19 Dec 2025 10:45:29 +0000 Subject: [PATCH 026/196] fix MSVC build --- CMakeLists.txt | 2 +- external/jupyter-xeus/cpp-terminal/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fca37d0a..78ae12cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ set(BUILD_SHARED_LIBS Off CACHE STRING "") message(STATUS "CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") message(STATUS "CXX_SIMULATE_ID: ${CMAKE_CXX_SIMULATE_ID}") -if (NOT CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") +if (NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) endif() diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt index d20fdd9c..f0573672 100644 --- a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt +++ b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt @@ -6,7 +6,7 @@ FetchContent_Declare( set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) -if (NOT CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") +if (NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") add_compile_options( -Wno-unused-variable -Wno-unused-but-set-variable From 4a9afab9f4336a92306cdc276ce1956f02fe82d8 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 19 Dec 2025 11:19:17 +0000 Subject: [PATCH 027/196] fix MSVC build --- CMakeLists.txt | 2 +- external/jupyter-xeus/cpp-terminal/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 78ae12cd..ce79df2c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,7 +26,7 @@ set(BUILD_SHARED_LIBS Off CACHE STRING "") message(STATUS "CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") message(STATUS "CXX_SIMULATE_ID: ${CMAKE_CXX_SIMULATE_ID}") -if (NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") +if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) endif() diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt index f0573672..5855aa30 100644 --- a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt +++ b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt @@ -6,7 +6,7 @@ FetchContent_Declare( set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) -if (NOT CMAKE_CXX_COMPILER_ID MATCHES "MSVC" AND NOT CMAKE_CXX_SIMULATE_ID MATCHES "MSVC") +if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) add_compile_options( -Wno-unused-variable -Wno-unused-but-set-variable From d11d0d097ee826cbd0e24e082dc56fad001cd469 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 22 Dec 2025 13:11:11 +0000 Subject: [PATCH 028/196] add parameter-types compatibility tests --- .../parameter-types/parameter-types.cpp | 21 + cucumber_cpp/CucumberCpp.hpp | 2 + cucumber_cpp/library/Application.cpp | 2 +- cucumber_cpp/library/Application.hpp | 2 +- cucumber_cpp/library/Body.cpp | 3 +- cucumber_cpp/library/Body.hpp | 5 +- cucumber_cpp/library/BodyMacro.hpp | 82 ++- cucumber_cpp/library/Parameter.hpp | 92 ++++ cucumber_cpp/library/StepRegistry.cpp | 35 +- cucumber_cpp/library/StepRegistry.hpp | 5 +- cucumber_cpp/library/api/RunCucumber.cpp | 23 +- .../library/assemble/AssembleTestSuites.cpp | 11 +- .../library/cucumber_expression/Argument.cpp | 41 ++ .../library/cucumber_expression/Argument.hpp | 34 ++ .../cucumber_expression/CMakeLists.txt | 5 + .../cucumber_expression/Expression.cpp | 62 +-- .../cucumber_expression/Expression.hpp | 13 +- .../cucumber_expression/MatchRange.hpp | 3 + .../library/cucumber_expression/Matcher.hpp | 18 +- .../cucumber_expression/ParameterRegistry.cpp | 85 ++- .../cucumber_expression/ParameterRegistry.hpp | 74 ++- .../cucumber_expression/RegularExpression.hpp | 47 +- .../cucumber_expression/TreeRegexp.cpp | 164 ++++++ .../cucumber_expression/TreeRegexp.hpp | 51 ++ .../cucumber_expression/test/CMakeLists.txt | 1 + .../test/TestExpression.cpp | 490 +++++++++--------- .../test/TestTreeRegexp.cpp | 163 ++++++ .../library/runtime/TestCaseRunner.cpp | 36 +- .../library/runtime/TestCaseRunner.hpp | 3 +- 29 files changed, 1024 insertions(+), 549 deletions(-) create mode 100644 compatibility/parameter-types/parameter-types.cpp create mode 100644 cucumber_cpp/library/Parameter.hpp create mode 100644 cucumber_cpp/library/cucumber_expression/Argument.cpp create mode 100644 cucumber_cpp/library/cucumber_expression/Argument.hpp create mode 100644 cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp create mode 100644 cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp create mode 100644 cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp diff --git a/compatibility/parameter-types/parameter-types.cpp b/compatibility/parameter-types/parameter-types.cpp new file mode 100644 index 00000000..460d587e --- /dev/null +++ b/compatibility/parameter-types/parameter-types.cpp @@ -0,0 +1,21 @@ +#include "cucumber/messages/group.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" +#include "gmock/gmock.h" +#include + +struct Flight +{ + std::string from; + std::string to; +}; + +PARAMETER(Flight, "flight", "([A-Z]{3})-([A-Z]{3})", true) +{ + return { group.children[0].value.value(), group.children[1].value.value() }; +} + +STEP(R"({flight} has been delayed)", (const Flight& flight)) +{ + EXPECT_THAT(flight.from, testing::StrEq("LHR")); + EXPECT_THAT(flight.to, testing::StrEq("CDG")); +} diff --git a/cucumber_cpp/CucumberCpp.hpp b/cucumber_cpp/CucumberCpp.hpp index 378a7ab0..eb103edf 100644 --- a/cucumber_cpp/CucumberCpp.hpp +++ b/cucumber_cpp/CucumberCpp.hpp @@ -4,8 +4,10 @@ #include "cucumber_cpp/library/Application.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/Hooks.hpp" +#include "cucumber_cpp/library/Parameter.hpp" #include "cucumber_cpp/library/Steps.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" +#include "library/cucumber_expression/MatchRange.hpp" namespace cucumber_cpp { diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 688605a0..29d6b498 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -156,7 +156,7 @@ namespace cucumber_cpp::library return programContextRef; } - cucumber_expression::ParameterRegistration& Application::ParameterRegistration() + cucumber_expression::ParameterRegistry& Application::ParameterRegistration() { return parameterRegistry; } diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 95152ceb..79d8768b 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -45,7 +45,7 @@ namespace cucumber_cpp::library CLI::App& CliParser(); Context& ProgramContext(); - cucumber_expression::ParameterRegistration& ParameterRegistration(); + cucumber_expression::ParameterRegistry& ParameterRegistration(); // void AddReportHandler(const std::string& name, std::unique_ptr&& reporter); diff --git a/cucumber_cpp/library/Body.cpp b/cucumber_cpp/library/Body.cpp index 614eb8e1..279417bd 100644 --- a/cucumber_cpp/library/Body.cpp +++ b/cucumber_cpp/library/Body.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/Body.hpp" #include "cucumber/messages/exception.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" @@ -48,7 +49,7 @@ namespace cucumber_cpp::library cucumber::messages::test_step_result& testStepResult; }; - cucumber::messages::test_step_result Body::ExecuteAndCatchExceptions(const ExecuteArgs& args) + cucumber::messages::test_step_result Body::ExecuteAndCatchExceptions(const cucumber::messages::step_match_arguments_list& args) { cucumber::messages::test_step_result testStepResult{ .status = cucumber::messages::test_step_result_status::PASSED }; CucumberResultReporter reportListener{ testStepResult }; diff --git a/cucumber_cpp/library/Body.hpp b/cucumber_cpp/library/Body.hpp index 15832aae..a5d2b836 100644 --- a/cucumber_cpp/library/Body.hpp +++ b/cucumber_cpp/library/Body.hpp @@ -2,6 +2,7 @@ #define CUCUMBER_CPP_BODY_HPP #include "cucumber/messages/exception.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include @@ -31,10 +32,10 @@ namespace cucumber_cpp::library { virtual ~Body() = default; - cucumber::messages::test_step_result ExecuteAndCatchExceptions(const ExecuteArgs& args = {}); + cucumber::messages::test_step_result ExecuteAndCatchExceptions(const cucumber::messages::step_match_arguments_list& args = {}); protected: - virtual void Execute(const ExecuteArgs& args) = 0; + virtual void Execute(const cucumber::messages::step_match_arguments_list& args) = 0; }; template diff --git a/cucumber_cpp/library/BodyMacro.hpp b/cucumber_cpp/library/BodyMacro.hpp index 4d46e3bb..bf105eea 100644 --- a/cucumber_cpp/library/BodyMacro.hpp +++ b/cucumber_cpp/library/BodyMacro.hpp @@ -2,6 +2,7 @@ #define CUCUMBER_CPP_BODYMACRO_HPP #include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" #include #include @@ -14,51 +15,42 @@ #define BODY_STRUCT CONCAT(BodyImpl, __LINE__) -#define BODY(matcher, type, targs, registration, base) \ - namespace \ - { \ - struct BODY_STRUCT : cucumber_cpp::library::Body \ - , base \ - { \ - /* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \ - /* Then the result would be Foo::Bar::Foo::Bar which is invalid */ \ - using myBase = base; \ - using myBase::myBase; \ - \ - void Execute(const std::variant, std::vector>& args) override \ - { \ - cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ - /* ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); */ \ - ExecuteWithArgs(args, static_cast(nullptr)); \ - } \ - \ - template \ - void ExecuteWithArgs(const std::variant, std::vector>& args, void (* /* unused */)(TArgs...)) \ - { \ - if (std::holds_alternative>(args)) \ - ExecuteWithArgs(std::get>(args), std::make_index_sequence{}); \ - else \ - ExecuteWithArgs(std::get>(args), std::make_index_sequence{}); \ - } \ - \ - template \ - void ExecuteWithArgs(const std::vector& args, std::index_sequence /*unused*/) \ - { \ - ExecuteWithArgs(cucumber_cpp::library::engine::StringTo>(args[I])...); \ - } \ - \ - template \ - void ExecuteWithArgs(const std::vector& args, std::index_sequence /*unused*/) \ - { \ - ExecuteWithArgs(std::any_cast>(args[I])...); \ - } \ - \ - private: \ - void ExecuteWithArgs targs; \ - static const std::size_t ID; \ - }; \ - } \ - const std::size_t BODY_STRUCT::ID = registration(matcher, type); \ +#define BODY(matcher, type, targs, registration, base) \ + namespace \ + { \ + struct BODY_STRUCT : cucumber_cpp::library::Body \ + , base \ + { \ + /* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \ + /* Then the result would be Foo::Bar::Foo::Bar which is invalid */ \ + using myBase = base; \ + using myBase::myBase; \ + \ + void Execute(const cucumber::messages::step_match_arguments_list& args) override \ + { \ + cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ + /* ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); */ \ + ExecuteWithArgs(args, static_cast(nullptr)); \ + } \ + \ + template \ + void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, void (* /* unused */)(TArgs...)) \ + { \ + ExecuteWithArgs(args, std::make_index_sequence{}); \ + } \ + \ + template \ + void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, std::index_sequence /*unused*/) \ + { \ + ExecuteWithArgs(cucumber_cpp::library::cucumber_expression::ConverterTypeMap>::Instance().at(args.step_match_arguments[I].parameter_type_name.value())(args.step_match_arguments[I].group)...); \ + } \ + \ + private: \ + void ExecuteWithArgs targs; \ + static const std::size_t ID; \ + }; \ + } \ + const std::size_t BODY_STRUCT::ID = registration(matcher, type); \ void BODY_STRUCT::ExecuteWithArgs targs #endif diff --git a/cucumber_cpp/library/Parameter.hpp b/cucumber_cpp/library/Parameter.hpp new file mode 100644 index 00000000..edf71b8f --- /dev/null +++ b/cucumber_cpp/library/Parameter.hpp @@ -0,0 +1,92 @@ +#ifndef LIBRARY_PARAMETER_HPP +#define LIBRARY_PARAMETER_HPP + +// IWYU pragma: private, include "cucumber_cpp/CucumberCpp.hpp" +// IWYU pragma: friend cucumber_cpp/.* + +#include "cucumber/messages/group.hpp" +#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library +{ + using ToStringFn = std::function; + using ToAnyFn = std::function; + + struct ParameterEntryParams + { + std::string name; + std::string regex; + bool useForSnippets; + }; + + struct ParameterEntry + { + ParameterEntryParams params; + + std::size_t localId; + + std::source_location location; + + auto operator<=>(const ParameterEntry& other) const + { + return std::tie(params.name, params.regex) <=> std::tie(other.params.name, other.params.regex); + } + }; + + struct ParameterRegistration + { + private: + ParameterRegistration() = default; + + public: + static ParameterRegistration& Instance() + { + static ParameterRegistration instance; + return instance; + } + + template + std::size_t Register(ParameterEntryParams params, std::source_location location = std::source_location::current()) + { + customParameters.emplace(params, customParameters.size() + 1, location); + + cucumber_expression::ConverterTypeMap::Instance()[params.name] = Transformer::Transform; + + return customParameters.size(); + } + + const std::set& GetRegisteredParameters() const + { + return customParameters; + } + + private: + std::set customParameters; + }; +} + +#define PARAMETER_STRUCT CONCAT(ParameterImpl, __LINE__) + +#define PARAMETER(Type, ...) \ + namespace \ + { \ + struct PARAMETER_STRUCT \ + { \ + static Type Transform(const cucumber::messages::group& group); \ + static const std::size_t ID; \ + }; \ + } \ + const std::size_t PARAMETER_STRUCT::ID = cucumber_cpp::library::ParameterRegistration::Instance().Register({ __VA_ARGS__ }); \ + Type PARAMETER_STRUCT::Transform(const cucumber::messages::group& group) + +#endif diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index 4b3c474c..e8925982 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -2,6 +2,7 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" @@ -34,38 +35,20 @@ namespace cucumber_cpp::library Register(matcher.id, matcher.regex, matcher.type, matcher.factory, matcher.sourceLocation); } - StepMatch StepRegistry::Query(const std::string& expression) + [[nodiscard]] std::pair, std::vector>> StepRegistry::FindDefinitions(const std::string& expression) { - std::vector matches; - - for (auto& [id, iter] : idToDefinitionMap) - { - auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, iter->regex); - if (match) - { - matches.emplace_back(iter->factory, *match, std::visit(cucumber_expression::PatternVisitor{}, iter->regex)); - ++iter->used; - } - } - - if (matches.empty()) - throw StepNotFoundError{}; - - if (matches.size() > 1) - throw AmbiguousStepError{ std::move(matches) }; - - return std::move(matches.front()); - } - - [[nodiscard]] std::pair, std::vector> StepRegistry::FindDefinitions(const std::string& expression) - { - std::pair, std::vector> result; + std::pair, std::vector>> result; + result.first.reserve(idToDefinitionMap.size()); + result.second.reserve(idToDefinitionMap.size()); for (auto& [id, iter] : idToDefinitionMap) { const auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, iter->regex); if (match) + { result.first.push_back(id); + result.second.push_back(match.value()); + } } return result; @@ -109,12 +92,12 @@ namespace cucumber_cpp::library ? cucumber_expression::Matcher{ std::in_place_type, matcher, + parameterRegistry, } : cucumber_expression::Matcher{ std::in_place_type, matcher, parameterRegistry, - }; auto cucumberMatcherType = std::holds_alternative(cucumberMatcher) ? cucumber::messages::step_definition_pattern_type::REGULAR_EXPRESSION diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index cd680d7a..e5d25af3 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -5,9 +5,9 @@ #include "cucumber/messages/pickle_doc_string.hpp" #include "cucumber/messages/pickle_table_row.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" -#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" @@ -99,8 +99,7 @@ namespace cucumber_cpp::library void LoadSteps(); - [[nodiscard]] StepMatch Query(const std::string& expression); - [[nodiscard]] std::pair, std::vector> FindDefinitions(const std::string& expression); + [[nodiscard]] std::pair, std::vector>> FindDefinitions(const std::string& expression); [[nodiscard]] std::size_t Size() const; diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 12e6423f..f239b588 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -34,11 +34,20 @@ namespace cucumber_cpp::library::api if (parameter.isBuiltin) continue; - broadcaster.BroadcastEvent({ .parameter_type = cucumber::messages::parameter_type{ - .name = parameter.name, - .regular_expressions = parameter.regex, - .id = idGenerator->next_id(), - } }); + broadcaster.BroadcastEvent({ + .parameter_type = cucumber::messages::parameter_type{ + .name = parameter.name, + .regular_expressions = parameter.regex, + .use_for_snippets = parameter.useForSnippets, + .id = idGenerator->next_id(), + .source_reference = cucumber::messages::source_reference{ + .uri = parameter.location.file_name(), + .location = cucumber::messages::location{ + .line = parameter.location.line(), + }, + }, + }, + }); } } @@ -90,11 +99,11 @@ namespace cucumber_cpp::library::api void EmitSupportCodeMessages(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { - support::DefinitionRegistration::Instance().LoadIds(idGenerator); - EmitParameters(supportCodeLibrary, broadcaster, idGenerator); // undefined parameters + support::DefinitionRegistration::Instance().LoadIds(idGenerator); + supportCodeLibrary.stepRegistry.LoadSteps(); EmitStepDefinitions(supportCodeLibrary, broadcaster, idGenerator); diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 3e640690..2cf09687 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -47,7 +47,7 @@ namespace cucumber_cpp::library::assemble for (const auto& step : pickleSource.pickle->steps) { - auto definitions = supportCodeLibrary.stepRegistry.FindDefinitions(step.text); + // auto definitions = supportCodeLibrary.stepRegistry.FindDefinitions(step.text); const auto& stepDefinitions = supportCodeLibrary.stepRegistry.StepDefinitions(); std::vector stepDefinitionIds; @@ -55,12 +55,13 @@ namespace cucumber_cpp::library::assemble for (const auto& definition : stepDefinitions) { - auto optionalStepMatchArgumentsList = std::visit(cucumber_expression::MatchArgumentsVisitor{ step.text }, definition.regex); - - if (optionalStepMatchArgumentsList) + const auto match = std::visit(cucumber_expression::MatchVisitor{ step.text }, definition.regex); + if (match) { stepDefinitionIds.push_back(definition.id); - stepMatchArgumentsLists.push_back(*optionalStepMatchArgumentsList); + auto& argumentList = stepMatchArgumentsLists.emplace_back(); + for (const auto& result : *match) + argumentList.step_match_arguments.emplace_back(result.Group(), result.Name()); } } diff --git a/cucumber_cpp/library/cucumber_expression/Argument.cpp b/cucumber_cpp/library/cucumber_expression/Argument.cpp new file mode 100644 index 00000000..9ddbcc6c --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/Argument.cpp @@ -0,0 +1,41 @@ +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" +#include "cucumber/messages/group.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + Argument::Argument(cucumber::messages::group group, const Parameter& parameter) + : group{ group } + , parameter{ parameter } + {} + + std::vector Argument::BuildArguments(const cucumber::messages::group& group, std::span parameters) + { + if (group.children.size() != parameters.size()) + throw std::runtime_error("Mismatch between number of groups and parameters"); + + std::size_t index{ 0 }; + auto converted = parameters | std::views::transform([&group, &index](const Parameter& parameter) -> Argument + { + return { group.children[index++], parameter }; + }); + + return { converted.begin(), converted.end() }; + } + + cucumber::messages::group Argument::Group() const + { + return group; + } + + std::string Argument::Name() const + { + return parameter.name; + } + +} diff --git a/cucumber_cpp/library/cucumber_expression/Argument.hpp b/cucumber_cpp/library/cucumber_expression/Argument.hpp new file mode 100644 index 00000000..98c112f7 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/Argument.hpp @@ -0,0 +1,34 @@ +#ifndef CUCUMBER_EXPRESSION_ARGUMENT_HPP +#define CUCUMBER_EXPRESSION_ARGUMENT_HPP + +#include "cucumber/messages/group.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + struct Argument + { + private: + Argument(cucumber::messages::group group, const Parameter& parameter); + + public: + static std::vector BuildArguments(const cucumber::messages::group& group, std::span parameters); + + template + T GetValue() + { + return ConverterTypeMap::Instance().at(parameter.name)(group); + } + + cucumber::messages::group Group() const; + std::string Name() const; + + private: + cucumber::messages::group group; + const Parameter& parameter; + }; +} + +#endif diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index c0ae28ad..283bfe28 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(cucumber_cpp.library.cucumber_expression STATIC ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.cucumber_expression PRIVATE + Argument.cpp + Argument.hpp Ast.cpp Ast.hpp Errors.cpp @@ -15,6 +17,9 @@ target_sources(cucumber_cpp.library.cucumber_expression PRIVATE MatchRange.hpp ParameterRegistry.cpp ParameterRegistry.hpp + RegularExpression.hpp + TreeRegexp.cpp + TreeRegexp.hpp ) target_include_directories(cucumber_cpp.library.cucumber_expression PUBLIC diff --git a/cucumber_cpp/library/cucumber_expression/Expression.cpp b/cucumber_cpp/library/cucumber_expression/Expression.cpp index de4e03b6..79dfdfaa 100644 --- a/cucumber_cpp/library/cucumber_expression/Expression.cpp +++ b/cucumber_cpp/library/cucumber_expression/Expression.cpp @@ -1,15 +1,12 @@ #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber/messages/step_match_arguments_list.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Ast.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/ExpressionParser.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include -#include #include -#include -#include #include #include #include @@ -25,8 +22,9 @@ namespace cucumber_cpp::library::cucumber_expression : expression{ std::move(expression) } , parameterRegistry{ parameterRegistry } , pattern{ RewriteToRegex(ExpressionParser{}.Parse(this->expression)) } - , regex{ pattern } - {} + , treeRegexp{ pattern } + { + } std::string_view Expression::Source() const { @@ -38,53 +36,13 @@ namespace cucumber_cpp::library::cucumber_expression return pattern; } - std::optional> Expression::Match(const std::string& text) const + std::optional> Expression::MatchToArguments(const std::string& text) const { - std::smatch smatch; - if (!std::regex_search(text, smatch, regex)) + auto group = treeRegexp.MatchToGroup(text); + if (!group.has_value()) return std::nullopt; - std::vector result; - result.reserve(converters.size()); - - auto converterIter = converters.begin(); - auto matchIter = smatch.begin() + 1; - - while (matchIter != smatch.end() && converterIter != converters.end()) - { - const auto stringArgs = converterIter->converter.toStrings({ matchIter, matchIter + converterIter->matches }); - result.emplace_back(converterIter->converter.toAny(stringArgs)); - matchIter = std::next(matchIter, converterIter->matches); - converterIter = std::next(converterIter); - } - - return result; - } - - std::optional Expression::MatchArguments(const std::string& text) const - { - std::smatch smatch; - if (!std::regex_search(text, smatch, regex)) - return std::nullopt; - - cucumber::messages::step_match_arguments_list result; - result.step_match_arguments.reserve(converters.size()); - - auto converterIter = converters.begin(); - auto matchIter = smatch.begin() + 1; - - while (matchIter != smatch.end() && converterIter != converters.end()) - { - auto groups = converterIter->converter.toStrings({ matchIter, matchIter + converterIter->matches }); - result.step_match_arguments.emplace_back( - groups, - converterIter->name); - - matchIter = std::next(matchIter, converterIter->matches); - converterIter = std::next(converterIter); - } - - return result; + return Argument::BuildArguments(group.value(), parameters); } std::string Expression::RewriteToRegex(const Node& node) @@ -181,7 +139,7 @@ namespace cucumber_cpp::library::cucumber_expression if (parameter.regex.empty()) throw UndefinedParameterTypeError(node, expression, node.Text()); - auto& converter = converters.emplace_back(0u, parameter.converter, parameter.name); + parameters.push_back(parameter); std::string partialRegex{}; if (parameter.regex.size() == 1) @@ -193,8 +151,6 @@ namespace cucumber_cpp::library::cucumber_expression partialRegex += R"()|(?:)" + parameterRegex; partialRegex = std::format(R"(((?:{})))", partialRegex); } - - converter.matches += std::regex{ partialRegex }.mark_count(); return partialRegex; } catch (const std::out_of_range&) diff --git a/cucumber_cpp/library/cucumber_expression/Expression.hpp b/cucumber_cpp/library/cucumber_expression/Expression.hpp index 8c7c0a1d..44d5e6d3 100644 --- a/cucumber_cpp/library/cucumber_expression/Expression.hpp +++ b/cucumber_cpp/library/cucumber_expression/Expression.hpp @@ -1,15 +1,14 @@ #ifndef CUCUMBER_EXPRESSION_EXPRESSION_HPP #define CUCUMBER_EXPRESSION_EXPRESSION_HPP -#include "cucumber/messages/step_match_arguments_list.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Ast.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include +#include "cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp" #include #include #include #include -#include #include #include #include @@ -22,9 +21,8 @@ namespace cucumber_cpp::library::cucumber_expression std::string_view Source() const; std::string_view Pattern() const; - std::optional> Match(const std::string& text) const; - std::optional MatchArguments(const std::string& text) const; + std::optional> MatchToArguments(const std::string& text) const; private: std::string @@ -54,9 +52,10 @@ namespace cucumber_cpp::library::cucumber_expression std::string expression; ParameterRegistry& parameterRegistry; - std::vector converters; + std::vector parameters; std::string pattern; - std::regex regex; + + TreeRegexp treeRegexp; }; } diff --git a/cucumber_cpp/library/cucumber_expression/MatchRange.hpp b/cucumber_cpp/library/cucumber_expression/MatchRange.hpp index 239a1650..c8936a85 100644 --- a/cucumber_cpp/library/cucumber_expression/MatchRange.hpp +++ b/cucumber_cpp/library/cucumber_expression/MatchRange.hpp @@ -1,6 +1,9 @@ #ifndef CUCUMBER_EXPRESSION_MATCH_RANGE_HPP #define CUCUMBER_EXPRESSION_MATCH_RANGE_HPP +// IWYU pragma: private, include "cucumber_cpp/CucumberCpp.hpp" +// IWYU pragma: friend cucumber_cpp/.* + #include #include diff --git a/cucumber_cpp/library/cucumber_expression/Matcher.hpp b/cucumber_cpp/library/cucumber_expression/Matcher.hpp index f190c81e..2a98c6ba 100644 --- a/cucumber_cpp/library/cucumber_expression/Matcher.hpp +++ b/cucumber_cpp/library/cucumber_expression/Matcher.hpp @@ -1,14 +1,12 @@ #ifndef CUCUMBER_EXPRESSION_MATCHER_HPP #define CUCUMBER_EXPRESSION_MATCHER_HPP -#include "cucumber/messages/step_match_arguments_list.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/RegularExpression.hpp" -#include #include #include #include -#include #include #include @@ -34,19 +32,9 @@ namespace cucumber_cpp::library::cucumber_expression struct MatchVisitor { - std::optional, std::vector>> operator()(const auto& expression) const + std::optional> operator()(const auto& expression) const { - return expression.Match(text); - } - - const std::string& text; - }; - - struct MatchArgumentsVisitor - { - std::optional operator()(const auto& expression) const - { - return expression.MatchArguments(text); + return expression.MatchToArguments(text); } const std::string& text; diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index ff632f68..f6f2b60e 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -1,18 +1,17 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber/messages/group.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" -#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" -#include #include -#include #include #include #include -#include +#include #include #include #include +#include #include #include #include @@ -22,50 +21,30 @@ namespace cucumber_cpp::library::cucumber_expression namespace { template - ParameterConversion CreateStreamConverter() + std::function CreateStreamConverter() { - return { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group - { - return { .value = matches.begin()->str() }; - }, - .toAny = [](const cucumber::messages::group& matches) -> std::any - { - return StringTo(matches.value.value()); - } }; - } + return [](const cucumber::messages::group& matches) + { + return StringTo(matches.value.value()); + }; + }; + } - ParameterConversion CreateStringConverter() + std::function CreateStringConverter() + { + return [](const cucumber::messages::group& matches) { - return { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group - { - return { - .children = { - matches[1].matched ? cucumber::messages::group{ .value = matches[1].str() } : cucumber::messages::group{}, - matches[3].matched ? cucumber::messages::group{ .value = matches[3].str() } : cucumber::messages::group{}, - }, - .value = matches[0].str(), - }; - }, - .toAny = [](const cucumber::messages::group& matches) -> std::any - { - std::string str = matches.children.front().value.has_value() - ? matches.children.front().value.value() - : matches.children.back().value.value(); + std::string str = matches.children.front().value.has_value() + ? matches.children.front().value.value() + : matches.children.back().value.value(); - str = std::regex_replace(str, std::regex(R"__(\\")__"), "\""); - str = std::regex_replace(str, std::regex(R"__(\\')__"), "'"); + str = std::regex_replace(str, std::regex(R"__(\\")__"), "\""); + str = std::regex_replace(str, std::regex(R"__(\\')__"), "'"); - return str; - } }; - } + return str; + }; } - Converter::Converter(std::size_t matches, ParameterConversion converter, std::string name) - : matches{ matches } - , converter{ std::move(converter) } - , name{ std::move(name) } - {} - ParameterRegistry::ParameterRegistry() { const static std::string integerNegativeRegex{ R"__(-?\d+)__" }; @@ -89,6 +68,10 @@ namespace cucumber_cpp::library::cucumber_expression // extension AddBuiltinParameter("bool", { wordRegex }, CreateStreamConverter()); + + const auto& parameterRegistration = cucumber_cpp::library::ParameterRegistration::Instance(); + for (const auto& parameter : parameterRegistration.GetRegisteredParameters()) + AddParameter(Parameter{ parameter.params.name, { std::string(parameter.params.regex) }, false, parameter.params.useForSnippets, parameter.location }); } const std::map& ParameterRegistry::GetParameters() const @@ -101,19 +84,8 @@ namespace cucumber_cpp::library::cucumber_expression return parametersByName.at(name); } - void ParameterRegistry::AddParameter(std::string name, std::vector regex, ParameterConversion converter) - { - AddParameter(name, regex, converter, false); - } - - void ParameterRegistry::AddBuiltinParameter(std::string name, std::vector regex, ParameterConversion converter) - { - AddParameter(name, regex, converter, true); - } - - void ParameterRegistry::AddParameter(std::string name, std::vector regex, ParameterConversion converter, bool isBuiltin) + void ParameterRegistry::AssertParameterIsUnique(const std::string& name) const { - if (parametersByName.contains(name)) { if (name.empty()) @@ -121,7 +93,12 @@ namespace cucumber_cpp::library::cucumber_expression else throw CucumberExpressionError{ std::format("There is already a parameter with name {}", name) }; } + } + + void ParameterRegistry::AddParameter(Parameter parameter) + { + AssertParameterIsUnique(parameter.name); - parametersByName.emplace(name, Parameter{ name, regex, converter, isBuiltin }); + parametersByName.emplace(parameter.name, parameter); } } diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp index a5f45451..23528939 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp @@ -2,6 +2,7 @@ #define CUCUMBER_EXPRESSION_PARAMETERREGISTRY_HPP #include "cucumber/messages/group.hpp" +#include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" #include #include @@ -12,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -94,39 +97,32 @@ namespace cucumber_cpp::library::cucumber_expression return iequals(s, "true") || iequals(s, "1") || iequals(s, "yes") || iequals(s, "on") || iequals(s, "enabled") || iequals(s, "active"); } - struct ParameterConversion + template + using TypeMap = std::map>; + + template + struct ConverterTypeMap { - std::function toStrings; - std::function toAny; + static std::map>& Instance(); }; - struct Converter + template + std::map>& ConverterTypeMap::Instance() { - Converter(std::size_t matches, ParameterConversion converter, std::string name); - - std::string name; - std::size_t matches; - ParameterConversion converter; - }; + static std::map> typeMap; + return typeMap; + } struct Parameter { std::string name; std::vector regex; - ParameterConversion converter; bool isBuiltin{ false }; + bool useForSnippets{ false }; + std::source_location location; }; - struct ParameterRegistration - { - protected: - ~ParameterRegistration() = default; - - public: - virtual void AddParameter(std::string name, std::vector regex, ParameterConversion converter) = 0; - }; - - struct ParameterRegistry : ParameterRegistration + struct ParameterRegistry { ParameterRegistry(); @@ -136,14 +132,44 @@ namespace cucumber_cpp::library::cucumber_expression const Parameter& Lookup(const std::string& name) const; - void AddParameter(std::string name, std::vector regex, ParameterConversion converter) override; + template + void AddParameter(std::string name, std::vector regex, std::function converter, std::source_location location = std::source_location::current()); + + void AssertParameterIsUnique(const std::string& name) const; private: - void AddBuiltinParameter(std::string name, std::vector regex, ParameterConversion converter); - void AddParameter(std::string name, std::vector regex, ParameterConversion converter, bool isBuiltin); + void AddParameter(Parameter parameter); + + template + void AddBuiltinParameter(std::string name, std::vector regex, std::function converter, std::source_location location = std::source_location::current()); + + template + void AddParameter(Parameter parameter, std::function converter); std::map parametersByName; }; + + template + void ParameterRegistry::AddParameter(std::string name, std::vector regex, std::function converter, std::source_location location) + { + AddParameter(Parameter{ name, regex, false, false, location }, converter); + } + + template + void ParameterRegistry::AddBuiltinParameter(std::string name, std::vector regex, std::function converter, std::source_location location) + { + AddParameter(Parameter{ name, regex, true, false, location }, converter); + } + + template + void ParameterRegistry::AddParameter(Parameter parameter, std::function converter) + { + AssertParameterIsUnique(parameter.name); + + AddParameter(parameter); + + ConverterTypeMap::Instance().emplace(parameter.name, converter); + } } #endif diff --git a/cucumber_cpp/library/cucumber_expression/RegularExpression.hpp b/cucumber_cpp/library/cucumber_expression/RegularExpression.hpp index 011a9f12..ee69854d 100644 --- a/cucumber_cpp/library/cucumber_expression/RegularExpression.hpp +++ b/cucumber_cpp/library/cucumber_expression/RegularExpression.hpp @@ -3,6 +3,9 @@ #include "cucumber/messages/group.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp" #include #include #include @@ -15,10 +18,18 @@ namespace cucumber_cpp::library::cucumber_expression { struct RegularExpression { - explicit RegularExpression(std::string expression) + explicit RegularExpression(std::string expression, ParameterRegistry& parameterRegistry) : expression{ std::move(expression) } , regex{ this->expression } - {} + , treeRegexp{ this->expression } + { + auto parameterIters = treeRegexp.RootBuilder().Children() | std::views::transform([¶meterRegistry](const GroupBuilder& groupBuilder) -> Parameter + { + return parameterRegistry.Lookup(""); + }); + + parameters = { parameterIters.begin(), parameterIters.end() }; + } std::string_view Source() const { @@ -30,39 +41,21 @@ namespace cucumber_cpp::library::cucumber_expression return expression; } - std::optional> Match(const std::string& text) const + std::optional> MatchToArguments(const std::string& text) const { - std::smatch smatch; - if (!std::regex_search(text, smatch, regex)) + auto group = treeRegexp.MatchToGroup(text); + if (!group.has_value()) return std::nullopt; - std::vector result{}; - result.reserve(smatch.size() - 1); - - for (const auto& match : smatch | std::views::drop(1)) - result.emplace_back(match.str()); - - return result; - } - - std::optional MatchArguments(const std::string& text) const - { - std::smatch smatch; - if (!std::regex_search(text, smatch, regex)) - return {}; - - cucumber::messages::step_match_arguments_list result{}; - result.step_match_arguments.reserve(smatch.size() - 1); - - for (const auto& match : smatch | std::views::drop(1)) - result.step_match_arguments.emplace_back(cucumber::messages::group{ .value = match.str() }); - - return result; + return Argument::BuildArguments(group.value(), parameters); } private: std::string expression; std::regex regex; + + TreeRegexp treeRegexp; + std::vector parameters; }; } diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp new file mode 100644 index 00000000..f54db45a --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp @@ -0,0 +1,164 @@ +#include "cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp" +#include "cucumber/messages/group.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + namespace + { + bool IsNonCapturing(std::string_view pattern, std::size_t pos) + { + if (pattern[pos + 1] != '?') + return false; + if (pattern[pos + 2] != '<') + return true; + + return pattern[pos + 3] == '=' || pattern[pos + 3] == '!'; + } + } + + void GroupBuilder::Add(GroupBuilder groupBuilder) + { + children.push_back(std::move(groupBuilder)); + } + + void GroupBuilder::SetNonCapturing() + { + capturing = false; + } + + bool GroupBuilder::IsCapturing() const + { + return capturing; + } + + void GroupBuilder::SetPattern(std::string_view pattern) + { + this->pattern = pattern; + } + + void GroupBuilder::MoveChildrenTo(GroupBuilder& target) + { + for (auto& child : children) + target.Add(std::move(child)); + children.clear(); + } + + const std::list& GroupBuilder::Children() const + { + return children; + } + + std::string_view GroupBuilder::Pattern() const + { + return pattern; + } + + cucumber::messages::group GroupBuilder::Build(const std::smatch& match, std::size_t& index) const + { + const auto groupIndex = index++; + const auto children = this->children | std::views::transform([&match, &index](const auto& child) + { + return child.Build(match, index); + }); + const auto value = match[groupIndex].matched ? std::make_optional(match[groupIndex].str()) : std::nullopt; + + return { + .children = std::vector(children.begin(), children.end()), + // .start = value.first - match.prefix().first, + .value = value, + }; + } + + ///////////////////////////////////////////////////////// + + TreeRegexp::TreeRegexp(std::string_view pattern) + : rootGroupBuilder{ CreateGroupBuilder(pattern) } + , regex{ std::string(pattern) } + { + } + + const GroupBuilder& TreeRegexp::RootBuilder() const + { + return rootGroupBuilder; + } + + std::optional TreeRegexp::MatchToGroup(const std::string& text) const + { + std::smatch match; + if (!std::regex_search(text, match, regex)) + return std::nullopt; + + std::size_t index = 0; + return rootGroupBuilder.Build(match, index); + } + + GroupBuilder TreeRegexp::CreateGroupBuilder(std::string_view pattern) + { + std::deque stack; + std::deque groupStartStack; + + stack.emplace_back(); + + bool escaping{ false }; + bool charClass{ false }; + + for (std::size_t i = 0; i < pattern.size(); ++i) + { + const char c = pattern[i]; + + if (c == '[' && !escaping) + charClass = true; + else if (c == ']' && !escaping) + charClass = false; + else if (c == '(' && !escaping && !charClass) + { + groupStartStack.emplace_back(i); + auto& groupBuilder = stack.emplace_back(); + if (IsNonCapturing(pattern, i)) + groupBuilder.SetNonCapturing(); + } + else if (c == ')' && !escaping && !charClass) + { + if (stack.empty()) + throw std::runtime_error("Empty stack"); + + auto groupBuilder = stack.back(); + stack.pop_back(); + + auto groupStart = groupStartStack.empty() ? 0 : groupStartStack.back(); + groupStart += 1; + + if (!groupStartStack.empty()) + groupStartStack.pop_back(); + + if (groupBuilder.IsCapturing()) + { + groupBuilder.SetPattern(pattern.substr(groupStart, i - groupStart)); + stack.back().Add(groupBuilder); + } + else + { + groupBuilder.MoveChildrenTo(stack.back()); + } + } + + escaping = c == '\\' && !escaping; + } + + if (stack.empty()) + throw std::runtime_error("Empty stack"); + + return stack.back(); + } +} diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp new file mode 100644 index 00000000..ae4305ef --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp @@ -0,0 +1,51 @@ +#ifndef CUCUMBER_EXPRESSION_TREE_REGEXP_HPP +#define CUCUMBER_EXPRESSION_TREE_REGEXP_HPP + +#include "cucumber/messages/group.hpp" +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + struct GroupBuilder + { + void Add(GroupBuilder groupBuilder); + + void SetNonCapturing(); + bool IsCapturing() const; + + void SetPattern(std::string_view pattern); + + void MoveChildrenTo(GroupBuilder& target); + + const std::list& Children() const; + std::string_view Pattern() const; + + cucumber::messages::group Build(const std::smatch& match, std::size_t& index) const; + + private: + std::string_view pattern; + bool capturing{ true }; + std::list children; + }; + + struct TreeRegexp + { + TreeRegexp(std::string_view pattern); + + const GroupBuilder& RootBuilder() const; + + std::optional MatchToGroup(const std::string& text) const; + + private: + GroupBuilder CreateGroupBuilder(std::string_view pattern); + GroupBuilder rootGroupBuilder; + std::regex regex; + }; +} + +#endif diff --git a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt index 94ffce35..ed8f7629 100644 --- a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt @@ -13,6 +13,7 @@ target_sources(cucumber_cpp.library.cucumber_expression.test PRIVATE TestExpressionParser.cpp TestExpressionTokenizer.cpp TestTransformation.cpp + TestTreeRegexp.cpp ) add_custom_command( diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index b26e4858..da890afb 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -1,273 +1,275 @@ -#include "cucumber/messages/group.hpp" -#include "cucumber_cpp/library/cucumber_expression/Errors.hpp" -#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "yaml-cpp/node/node.h" -#include "yaml-cpp/node/parse.h" -#include "yaml-cpp/yaml.h" -#include "gmock/gmock.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +// #include "cucumber/messages/group.hpp" +// #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" +// #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +// #include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" +// #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +// #include "yaml-cpp/node/node.h" +// #include "yaml-cpp/node/parse.h" +// #include "yaml-cpp/yaml.h" +// #include "gmock/gmock.h" +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include +// #include -namespace cucumber_cpp::library::cucumber_expression -{ - namespace - { - std::vector> GetTestData(const std::filesystem::path& path) - { - std::vector> testdata; +// namespace cucumber_cpp::library::cucumber_expression +// { +// namespace +// { +// std::vector> GetTestData(const std::filesystem::path& path) +// { +// std::vector> testdata; - for (const auto& file : std::filesystem::directory_iterator(path)) - if (file.is_regular_file() && file.path().extension() == ".yaml") - testdata.emplace_back(file.path().string(), YAML::LoadFile(file.path().string())); +// for (const auto& file : std::filesystem::directory_iterator(path)) +// if (file.is_regular_file() && file.path().extension() == ".yaml") +// testdata.emplace_back(file.path().string(), YAML::LoadFile(file.path().string())); - return testdata; - } +// return testdata; +// } - std::string FormatMessage(const YAML::Node& node, const Expression& expression) - { - return std::format("failed to match {}\n" - "regex {}\n" - "against {}", - node["expression"].as(), expression.Pattern(), node["text"].as()); - } - } +// std::string FormatMessage(const YAML::Node& node, const Expression& expression) +// { +// return std::format("failed to match {}\n" +// "regex {}\n" +// "against {}", +// node["expression"].as(), expression.Pattern(), node["text"].as()); +// } +// } - struct TestExpression : testing::Test - { - ParameterRegistry parameterRegistry{}; +// struct TestExpression : testing::Test +// { +// ParameterRegistry parameterRegistry{}; - std::optional> Match(std::string expr, std::string text) - { - Expression expression{ std::move(expr), parameterRegistry }; - return expression.Match(std::move(text)); - } - }; +// std::optional> Match(std::string expr, std::string text) +// { +// Expression expression{ std::move(expr), parameterRegistry }; +// return expression.Match(std::move(text)); +// } +// }; - TEST_F(TestExpression, TestFromFiles) - { - std::filesystem::path testdataPath = "testdata/cucumber-expression/matching"; +// TEST_F(TestExpression, TestFromFiles) +// { +// std::filesystem::path testdataPath = "testdata/cucumber-expression/matching"; - for (const auto& [file, testdata] : GetTestData(testdataPath)) - { - if (testdata["exception"]) - ASSERT_ANY_THROW(Match(testdata["expression"].as(), testdata["text"].as())) - << std::format("Test failed for file: {}", file); - else - { - if (testdata["expected_args"].IsNull()) - ASSERT_THAT(Match(testdata["expression"].as(), testdata["text"].as()), testing::IsFalse()); - else - { - const auto expression = Expression{ testdata["expression"].as(), parameterRegistry }; - const auto matchOpt = expression.Match(testdata["text"].as()); +// for (const auto& [file, testdata] : GetTestData(testdataPath)) +// { +// if (testdata["exception"]) +// ASSERT_ANY_THROW(Match(testdata["expression"].as(), testdata["text"].as())) +// << std::format("Test failed for file: {}", file); +// else +// { +// if (testdata["expected_args"].IsNull()) +// ASSERT_THAT(Match(testdata["expression"].as(), testdata["text"].as()), testing::IsFalse()); +// else +// { +// const auto expression = Expression{ testdata["expression"].as(), parameterRegistry }; +// const auto matchOpt = expression.Match(testdata["text"].as()); - ASSERT_THAT(matchOpt, testing::IsTrue()) << FormatMessage(testdata, expression); +// const auto arguments = expression.MatchToArguments(testdata["text"].as()); - const auto& match = *matchOpt; - for (std::size_t i = 0; i < testdata["expected_args"].size(); ++i) - { - if (match[i].type() == typeid(std::string)) - ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - else if (match[i].type() == typeid(std::int32_t)) - ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - else if (match[i].type() == typeid(std::int64_t)) - ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - else if (match[i].type() == typeid(float)) - ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - else if (match[i].type() == typeid(double)) - ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - else - FAIL() << "Unknown type: " << match[i].type().name() << " for:\n" - << FormatMessage(testdata, expression); - } - } - } - } - } +// ASSERT_THAT(matchOpt, testing::IsTrue()) << FormatMessage(testdata, expression); - TEST_F(TestExpression, MatchFloat) - { - EXPECT_THAT(Match(R"__({float})__", R"__()__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(.)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(,)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(-)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(E)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(1,)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(,1)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(1.)__"), testing::IsFalse()); +// const auto& match = *matchOpt; +// for (std::size_t i = 0; i < testdata["expected_args"].size(); ++i) +// { +// if (match[i].type() == typeid(std::string)) +// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); +// else if (match[i].type() == typeid(std::int32_t)) +// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); +// else if (match[i].type() == typeid(std::int64_t)) +// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); +// else if (match[i].type() == typeid(float)) +// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); +// else if (match[i].type() == typeid(double)) +// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); +// else +// FAIL() << "Unknown type: " << match[i].type().name() << " for:\n" +// << FormatMessage(testdata, expression); +// } +// } +// } +// } +// } - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1)__"))[0]), testing::FloatNear(1.0f, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-1)__"))[0]), testing::FloatNear(-1.0f, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1.1)__"))[0]), testing::FloatNear(1.1f, std::numeric_limits::epsilon())); - EXPECT_THAT(Match(R"__({float})__", R"__(1,000)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(1,000,0)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(1,000.1)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(1,000,10)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(1,0.1)__"), testing::IsFalse()); - EXPECT_THAT(Match(R"__({float})__", R"__(1,000,000.1)__"), testing::IsFalse()); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-1.1)__"))[0]), testing::FloatNear(-1.1f, std::numeric_limits::epsilon())); +// TEST_F(TestExpression, MatchFloat) +// { +// EXPECT_THAT(Match(R"__({float})__", R"__()__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(.)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(,)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(-)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(E)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(1,)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(,1)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(1.)__"), testing::IsFalse()); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(.1)__"))[0]), testing::FloatNear(0.1f, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1)__"))[0]), testing::FloatNear(-0.1f, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1000001)__"))[0]), testing::FloatNear(-0.1000001f, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1E1)__"))[0]), testing::FloatNear(10.0, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(.1E1)__"))[0]), testing::FloatNear(1, std::numeric_limits::epsilon())); - EXPECT_THAT(Match(R"__({float})__", R"__(1,E1)__"), testing::IsFalse()); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.01)__"))[0]), testing::FloatNear(-0.01, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E-1)__"))[0]), testing::FloatNear(-0.01, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E-2)__"))[0]), testing::FloatNear(-0.001, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E+1)__"))[0]), testing::FloatNear(-1, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E+2)__"))[0]), testing::FloatNear(-10, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E1)__"))[0]), testing::FloatNear(-1, std::numeric_limits::epsilon())); - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E2)__"))[0]), testing::FloatNear(-10, std::numeric_limits::epsilon())); - } +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1)__"))[0]), testing::FloatNear(1.0f, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-1)__"))[0]), testing::FloatNear(-1.0f, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1.1)__"))[0]), testing::FloatNear(1.1f, std::numeric_limits::epsilon())); +// EXPECT_THAT(Match(R"__({float})__", R"__(1,000)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(1,000,0)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(1,000.1)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(1,000,10)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(1,0.1)__"), testing::IsFalse()); +// EXPECT_THAT(Match(R"__({float})__", R"__(1,000,000.1)__"), testing::IsFalse()); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-1.1)__"))[0]), testing::FloatNear(-1.1f, std::numeric_limits::epsilon())); - TEST_F(TestExpression, FloatWithZero) - { - EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(0)__"))[0]), testing::FloatNear(0.0f, std::numeric_limits::epsilon())); - } +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(.1)__"))[0]), testing::FloatNear(0.1f, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1)__"))[0]), testing::FloatNear(-0.1f, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1000001)__"))[0]), testing::FloatNear(-0.1000001f, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1E1)__"))[0]), testing::FloatNear(10.0, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(.1E1)__"))[0]), testing::FloatNear(1, std::numeric_limits::epsilon())); +// EXPECT_THAT(Match(R"__({float})__", R"__(1,E1)__"), testing::IsFalse()); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.01)__"))[0]), testing::FloatNear(-0.01, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E-1)__"))[0]), testing::FloatNear(-0.01, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E-2)__"))[0]), testing::FloatNear(-0.001, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E+1)__"))[0]), testing::FloatNear(-1, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E+2)__"))[0]), testing::FloatNear(-10, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E1)__"))[0]), testing::FloatNear(-1, std::numeric_limits::epsilon())); +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E2)__"))[0]), testing::FloatNear(-10, std::numeric_limits::epsilon())); +// } - TEST_F(TestExpression, MatchAnonymous) - { - EXPECT_THAT(std::any_cast((*Match(R"__({})__", R"__(0.22)__"))[0]), testing::StrEq("0.22")); - } +// TEST_F(TestExpression, FloatWithZero) +// { +// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(0)__"))[0]), testing::FloatNear(0.0f, std::numeric_limits::epsilon())); +// } - TEST_F(TestExpression, MatchCustom) - { - struct CustomType - { - std::optional text; - std::optional number; - }; +// TEST_F(TestExpression, MatchAnonymous) +// { +// EXPECT_THAT(std::any_cast((*Match(R"__({})__", R"__(0.22)__"))[0]), testing::StrEq("0.22")); +// } - parameterRegistry.AddParameter("textAndOrNumber", { R"(([A-Z]+)?(?: )?([0-9]+)?)" }, - { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group - { - std::optional text{ matches[1].matched ? matches[1].str() : std::optional{ std::nullopt } }; - std::optional number{ matches[2].matched ? matches[2].str() : std::optional{ std::nullopt } }; +// TEST_F(TestExpression, MatchCustom) +// { +// struct CustomType +// { +// std::optional text; +// std::optional number; +// }; - return { .children = { cucumber::messages::group{ .value = text }, cucumber::messages::group{ .value = number } } }; - }, - .toAny = [](const cucumber::messages::group& matches) -> std::any - { - std::optional text{ matches.children[0].value }; - std::optional number{ matches.children[1].value ? StringTo(matches.children[1].value.value()) : std::optional{ std::nullopt } }; - return CustomType{ text, number }; - } }); +// parameterRegistry.AddParameter("textAndOrNumber", { R"(([A-Z]+)?(?: )?([0-9]+)?)" }, +// { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group +// { +// std::optional text{ matches[1].matched ? matches[1].str() : std::optional{ std::nullopt } }; +// std::optional number{ matches[2].matched ? matches[2].str() : std::optional{ std::nullopt } }; - auto matchString{ Match(R"__({textAndOrNumber})__", R"__(ABC)__") }; - EXPECT_THAT(matchString, testing::IsTrue()); - EXPECT_THAT(std::any_cast((*matchString)[0]).text.value(), testing::StrEq("ABC")); - EXPECT_THAT(std::any_cast((*matchString)[0]).number, testing::IsFalse()); +// return { .children = { cucumber::messages::group{ .value = text }, cucumber::messages::group{ .value = number } } }; +// }, +// .toAny = [](const cucumber::messages::group& matches) -> std::any +// { +// std::optional text{ matches.children[0].value }; +// std::optional number{ matches.children[1].value ? StringTo(matches.children[1].value.value()) : std::optional{ std::nullopt } }; +// return CustomType{ text, number }; +// } }); - auto matchInt{ Match(R"__({textAndOrNumber})__", R"__(123)__") }; - EXPECT_THAT(matchInt, testing::IsTrue()); - EXPECT_THAT(std::any_cast((*matchInt)[0]).text, testing::IsFalse()); - EXPECT_THAT(std::any_cast((*matchInt)[0]).number.value(), testing::Eq(123)); +// auto matchString{ Match(R"__({textAndOrNumber})__", R"__(ABC)__") }; +// EXPECT_THAT(matchString, testing::IsTrue()); +// EXPECT_THAT(std::any_cast((*matchString)[0]).text.value(), testing::StrEq("ABC")); +// EXPECT_THAT(std::any_cast((*matchString)[0]).number, testing::IsFalse()); - auto matchStringAndInt{ Match(R"__({textAndOrNumber})__", R"__(ABC 123)__") }; - EXPECT_THAT(matchStringAndInt, testing::IsTrue()); - EXPECT_THAT(std::any_cast((*matchStringAndInt)[0]).text.value(), testing::StrEq("ABC")); - EXPECT_THAT(std::any_cast((*matchStringAndInt)[0]).number.value(), testing::Eq(123)); - } +// auto matchInt{ Match(R"__({textAndOrNumber})__", R"__(123)__") }; +// EXPECT_THAT(matchInt, testing::IsTrue()); +// EXPECT_THAT(std::any_cast((*matchInt)[0]).text, testing::IsFalse()); +// EXPECT_THAT(std::any_cast((*matchInt)[0]).number.value(), testing::Eq(123)); - TEST_F(TestExpression, ExposeSource) - { - auto expr = "I have {int} cuke(s)"; - Expression expression{ expr, parameterRegistry }; - EXPECT_THAT(expr, testing::StrEq(expression.Source())); - } +// auto matchStringAndInt{ Match(R"__({textAndOrNumber})__", R"__(ABC 123)__") }; +// EXPECT_THAT(matchStringAndInt, testing::IsTrue()); +// EXPECT_THAT(std::any_cast((*matchStringAndInt)[0]).text.value(), testing::StrEq("ABC")); +// EXPECT_THAT(std::any_cast((*matchStringAndInt)[0]).number.value(), testing::Eq(123)); +// } - TEST_F(TestExpression, MatchBoolean) - { - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(true)__"))[0]), testing::IsTrue()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(1)__"))[0]), testing::IsTrue()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(yes)__"))[0]), testing::IsTrue()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(on)__"))[0]), testing::IsTrue()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(enabled)__"))[0]), testing::IsTrue()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(active)__"))[0]), testing::IsTrue()); +// TEST_F(TestExpression, ExposeSource) +// { +// auto expr = "I have {int} cuke(s)"; +// Expression expression{ expr, parameterRegistry }; +// EXPECT_THAT(expr, testing::StrEq(expression.Source())); +// } - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(false)__"))[0]), testing::IsFalse()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(0)__"))[0]), testing::IsFalse()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(2)__"))[0]), testing::IsFalse()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(off)__"))[0]), testing::IsFalse()); - EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(foo)__"))[0]), testing::IsFalse()); - } +// TEST_F(TestExpression, MatchBoolean) +// { +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(true)__"))[0]), testing::IsTrue()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(1)__"))[0]), testing::IsTrue()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(yes)__"))[0]), testing::IsTrue()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(on)__"))[0]), testing::IsTrue()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(enabled)__"))[0]), testing::IsTrue()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(active)__"))[0]), testing::IsTrue()); - TEST_F(TestExpression, ThrowUnknownParameterType) - { - auto expr = "I have {doesnotexist} cuke(s)"; +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(false)__"))[0]), testing::IsFalse()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(0)__"))[0]), testing::IsFalse()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(2)__"))[0]), testing::IsFalse()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(off)__"))[0]), testing::IsFalse()); +// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(foo)__"))[0]), testing::IsFalse()); +// } - try - { - Expression expression{ expr, parameterRegistry }; - FAIL() << "Expected UndefinedParameterTypeError to be thrown"; - } - catch (UndefinedParameterTypeError e) - { - EXPECT_THAT(e.what(), testing::StrEq("This Cucumber Expression has a problem at column 8:\n" - "\n" - "I have {doesnotexist} cuke(s)\n" - " ^------------^\n" - "Undefined parameter type 'doesnotexist'\n" - "Please register a ParameterType for 'doesnotexist'\n")); - } - } +// TEST_F(TestExpression, ThrowUnknownParameterType) +// { +// auto expr = "I have {doesnotexist} cuke(s)"; - TEST_F(TestExpression, ThrowDuplicateAnonymousParameterError) - { - try - { - parameterRegistry.AddParameter("", { ".*" }, - { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group - { - return { .value = matches.begin()->str() }; - }, - .toAny = [](const cucumber::messages::group& matches) -> std::any - { - return matches.value.value(); - } }); - FAIL() << "Expected CucumberExpressionError to be thrown"; - } - catch (const CucumberExpressionError& e) - { - EXPECT_THAT(e.what(), testing::StrEq("The anonymous parameter type has already been defined")); - } - } +// try +// { +// Expression expression{ expr, parameterRegistry }; +// FAIL() << "Expected UndefinedParameterTypeError to be thrown"; +// } +// catch (UndefinedParameterTypeError e) +// { +// EXPECT_THAT(e.what(), testing::StrEq("This Cucumber Expression has a problem at column 8:\n" +// "\n" +// "I have {doesnotexist} cuke(s)\n" +// " ^------------^\n" +// "Undefined parameter type 'doesnotexist'\n" +// "Please register a ParameterType for 'doesnotexist'\n")); +// } +// } - TEST_F(TestExpression, ThrowDuplicateParameterError) - { - try - { - parameterRegistry.AddParameter("word", { ".*" }, - { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group - { - return { .value = matches.begin()->str() }; - }, - .toAny = [](const cucumber::messages::group& matches) -> std::any - { - return matches.value.value(); - } }); - FAIL() << "Expected CucumberExpressionError to be thrown"; - } - catch (const CucumberExpressionError& e) - { - EXPECT_THAT(e.what(), testing::StrEq("There is already a parameter with name word")); - } - } -} +// TEST_F(TestExpression, ThrowDuplicateAnonymousParameterError) +// { +// try +// { +// parameterRegistry.AddParameter("", { ".*" }, +// { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group +// { +// return { .value = matches.begin()->str() }; +// }, +// .toAny = [](const cucumber::messages::group& matches) -> std::any +// { +// return matches.value.value(); +// } }); +// FAIL() << "Expected CucumberExpressionError to be thrown"; +// } +// catch (const CucumberExpressionError& e) +// { +// EXPECT_THAT(e.what(), testing::StrEq("The anonymous parameter type has already been defined")); +// } +// } + +// TEST_F(TestExpression, ThrowDuplicateParameterError) +// { +// try +// { +// parameterRegistry.AddParameter("word", { ".*" }, +// { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group +// { +// return { .value = matches.begin()->str() }; +// }, +// .toAny = [](const cucumber::messages::group& matches) -> std::any +// { +// return matches.value.value(); +// } }); +// FAIL() << "Expected CucumberExpressionError to be thrown"; +// } +// catch (const CucumberExpressionError& e) +// { +// EXPECT_THAT(e.what(), testing::StrEq("There is already a parameter with name word")); +// } +// } +// } diff --git a/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp new file mode 100644 index 00000000..fb395c50 --- /dev/null +++ b/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp @@ -0,0 +1,163 @@ + +#include "cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp" +#include "gmock/gmock.h" +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + using namespace std::string_view_literals; + + namespace + { + std::string_view BuilderPattern(const GroupBuilder& builder) + { + return builder.Pattern(); + } + } + + TEST(TestTreeRegexp, exposes_group_source) + { + TreeRegexp treeRegexp{ R"__((a(?:b)?)(c))__" }; + EXPECT_THAT(treeRegexp.RootBuilder().Children().begin()->Pattern(), testing::StrEq("a(?:b)?"sv)); + EXPECT_THAT(std::next(treeRegexp.RootBuilder().Children().begin())->Pattern(), testing::StrEq("c"sv)); + } + + TEST(TestTreeRegexp, builds_tree) + { + TreeRegexp treeRegexp{ R"__((a(?:b)?)(c))__" }; + const auto group = *treeRegexp.MatchToGroup("ac"); + EXPECT_THAT(group.value.value(), testing::StrEq("ac")); + EXPECT_THAT(group.children[0].value.value(), testing::StrEq("a")); + EXPECT_THAT(group.children[0].children.empty(), testing::IsTrue()); + EXPECT_THAT(group.children[1].value.value(), testing::StrEq("c")); + } + + TEST(TestTreeRegexp, ignores_non_capture_group_as_a_non_capturing_group) + { + TreeRegexp treeRegexp{ R"__(a(?:b)(c))__" }; + const auto group = *treeRegexp.MatchToGroup("abc"); + EXPECT_THAT(group.value.value(), testing::StrEq("abc")); + EXPECT_THAT(group.children.size(), testing::Eq(1)); + } + + TEST(TestTreeRegexp, ignores_negative_lookahead_as_a_non_capturing_group) + { + TreeRegexp treeRegexp{ R"__(a(?!b)(.+))__" }; + const auto group = *treeRegexp.MatchToGroup("aBc"); + EXPECT_THAT(group.value.value(), testing::StrEq("aBc")); + EXPECT_THAT(group.children.size(), testing::Eq(1)); + } + + TEST(TestTreeRegexp, ignores_positive_lookahead_as_a_non_capturing_group) + { + TreeRegexp treeRegexp{ R"__(a(?=[b])(.+))__" }; + const auto group = *treeRegexp.MatchToGroup("ac"); + } + + TEST(TestTreeRegexp, DISABLED_ignores_positive_lookbehind_as_a_non_capturing_group) + { + TreeRegexp treeRegexp{ R"__(a(.+)(?<=c)$)__" }; + } + + TEST(TestTreeRegexp, DISABLED_ignores_negative_lookbehind_as_a_non_capturing_group) + { + TreeRegexp treeRegexp{ R"__(a(.+?)(?b)c)__" }; + } + + TEST(TestTreeRegexp, matches_optional_group) + { + TreeRegexp treeRegexp{ R"__(^Something( with an optional argument)?)__" }; + const auto group = *treeRegexp.MatchToGroup("Something"); + EXPECT_THAT(group.children[0].value, testing::IsFalse()); + } + + TEST(TestTreeRegexp, matches_nested_groups) + { + TreeRegexp treeRegexp{ R"__(^A (\d+) thick line from ((\d+),\s*(\d+),\s*(\d+)) to ((\d+),\s*(\d+),\s*(\d+)))__" }; + const auto group = *treeRegexp.MatchToGroup("A 5 thick line from 10,20,30 to 40,50,60"); + EXPECT_THAT(group.children[0].value.value(), testing::StrEq("5")); + EXPECT_THAT(group.children[1].value.value(), testing::StrEq("10,20,30")); + EXPECT_THAT(group.children[1].children[0].value.value(), testing::StrEq("10")); + EXPECT_THAT(group.children[1].children[1].value.value(), testing::StrEq("20")); + EXPECT_THAT(group.children[1].children[2].value.value(), testing::StrEq("30")); + EXPECT_THAT(group.children[2].value.value(), testing::StrEq("40,50,60")); + EXPECT_THAT(group.children[2].children[0].value.value(), testing::StrEq("40")); + EXPECT_THAT(group.children[2].children[1].value.value(), testing::StrEq("50")); + EXPECT_THAT(group.children[2].children[2].value.value(), testing::StrEq("60")); + } + + TEST(TestTreeRegexp, detects_multiple_non_capturing_groups) + { + TreeRegexp treeRegexp{ R"__((?:a)(:b)(\?c)(d))__" }; + const auto group = *treeRegexp.MatchToGroup("a:b?cd"); + EXPECT_THAT(group.children.size(), testing::Eq(3)); + } + + TEST(TestTreeRegexp, works_with_escaped_backslash) + { + TreeRegexp treeRegexp{ R"__(foo\\(bar|baz))__" }; + const auto group = *treeRegexp.MatchToGroup("foo\\bar"); + EXPECT_THAT(group.children.size(), testing::Eq(1)); + } + + TEST(TestTreeRegexp, works_with_escaped_slash) + { + TreeRegexp treeRegexp{ R"__(^I go to '\/(.+)'$)__" }; + const auto group = *treeRegexp.MatchToGroup("I go to '/hello'"); + EXPECT_THAT(group.children.size(), testing::Eq(1)); + } + + TEST(TestTreeRegexp, works_with_digit_and_word) + { + TreeRegexp treeRegexp{ R"__(^(\d) (\w+)$)__" }; + const auto group = *treeRegexp.MatchToGroup("2 you"); + EXPECT_THAT(group.children.size(), testing::Eq(2)); + } + + TEST(TestTreeRegexp, captures_non_capturing_groups_with_capturing_groups_inside) + { + TreeRegexp treeRegexp{ R"__(the stdout(?: from "(.*?)")?)__" }; + const auto group = *treeRegexp.MatchToGroup("the stdout"); + + EXPECT_THAT(group.value.value(), testing::StrEq("the stdout")); + EXPECT_THAT(group.children[0].value, testing::IsFalse()); + EXPECT_THAT(group.children.size(), testing::Eq(1)); + } + + TEST(TestTreeRegexp, DISABLED_works_with_case_insensitive_flag) + { + TreeRegexp treeRegexp{ R"__(HELLO/)__" }; + const auto group = *treeRegexp.MatchToGroup("hello"); + EXPECT_THAT(group.value.value(), testing::StrEq("hello")); + } + + TEST(TestTreeRegexp, empty_capturing_group) + { + TreeRegexp treeRegexp{ R"__(())__" }; + const auto group = *treeRegexp.MatchToGroup(""); + EXPECT_THAT(group.value.value(), testing::StrEq("")); + EXPECT_THAT(group.children.size(), testing::Eq(1)); + } + + TEST(TestTreeRegexp, DISABLED_empty_look_ahead) + { + TreeRegexp treeRegexp{ R"__((?<=))__" }; + const auto group = *treeRegexp.MatchToGroup(""); + } + + TEST(TestTreeRegexp, does_not_consider_parenthesis_in_character_class_as_group) + { + TreeRegexp treeRegexp{ R"__(^drawings: ([A-Z, ()]+)$)__" }; + const auto group = *treeRegexp.MatchToGroup("drawings: ONE(TWO)"); + EXPECT_THAT(group.value.value(), testing::StrEq("drawings: ONE(TWO)")); + EXPECT_THAT(group.children.size(), testing::Eq(1)); + EXPECT_THAT(group.children[0].value.value(), testing::StrEq("ONE(TWO)")); + } +} diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index ebabad24..5fb27146 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -7,6 +7,7 @@ #include "cucumber/messages/pickle_step.hpp" #include "cucumber/messages/pickle_table_row.hpp" #include "cucumber/messages/step_match_argument.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/suggestion.hpp" #include "cucumber/messages/test_case.hpp" #include "cucumber/messages/test_case_finished.hpp" @@ -55,27 +56,6 @@ namespace cucumber_cpp::library::runtime // return { seconds, nanos }; // } - std::vector BuildExpressionParameters(std::span arguments, cucumber_expression::ParameterRegistry& parameterRegistry) - { - std::vector parameters; - - for (const auto& argument : arguments) - { - const auto parameter = parameterRegistry.Lookup(*argument.parameter_type_name); - parameters.push_back(parameter.converter.toAny(argument.group)); - } - - return parameters; - } - - std::vector BuildRegularParameters(std::span arguments) - { - const auto transformedArguments = arguments | std::views::transform([](const auto& argument) - { - return argument.group.value.value(); - }); - return { transformedArguments.begin(), transformedArguments.end() }; - } } TestCaseRunner::TestCaseRunner(util::Broadcaster& broadcaster, @@ -241,16 +221,6 @@ namespace cucumber_cpp::library::runtime { const auto& definition = stepDefinitions.front(); - std::variant, std::vector> parameters{}; - - if (!testStep.step_match_arguments_lists->empty()) - { - if (std::holds_alternative(definition.regex)) - parameters = BuildExpressionParameters(testStep.step_match_arguments_lists->front().step_match_arguments, supportCodeLibrary.parameterRegistry); - else - parameters = BuildRegularParameters(testStep.step_match_arguments_lists->front().step_match_arguments); - } - const auto toOptionalTable = [](const cucumber::messages::pickle_step& pickleStep) -> std::optional> { if (pickleStep.argument && pickleStep.argument->data_table) @@ -258,7 +228,7 @@ namespace cucumber_cpp::library::runtime return std::nullopt; }; - const auto result = InvokeStep(definition.factory(broadcaster, testCaseContext, testStepStarted, toOptionalTable(pickleStep), pickleStep.argument ? pickleStep.argument->doc_string : std::nullopt), parameters); + const auto result = InvokeStep(definition.factory(broadcaster, testCaseContext, testStepStarted, toOptionalTable(pickleStep), pickleStep.argument ? pickleStep.argument->doc_string : std::nullopt), testStep.step_match_arguments_lists->front()); stepResults.push_back(result); } @@ -276,7 +246,7 @@ namespace cucumber_cpp::library::runtime return finalStepResult; } - cucumber::messages::test_step_result TestCaseRunner::InvokeStep(std::unique_ptr body, const ExecuteArgs& args) + cucumber::messages::test_step_result TestCaseRunner::InvokeStep(std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args) { return body->ExecuteAndCatchExceptions(args); } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.hpp b/cucumber_cpp/library/runtime/TestCaseRunner.hpp index f6c15405..1104d3a1 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.hpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.hpp @@ -5,6 +5,7 @@ #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_case.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_result.hpp" @@ -43,7 +44,7 @@ namespace cucumber_cpp::library::runtime cucumber::messages::test_step_result RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); - cucumber::messages::test_step_result InvokeStep(std::unique_ptr body, const ExecuteArgs& args = {}); + cucumber::messages::test_step_result InvokeStep(std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args = {}); cucumber::messages::test_step_result GetWorstStepResult() const; bool ShouldSkipHook(bool isBeforeHook); From 78f70e1a5187414d6f547a165c63bd169ff1d1ef Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 22 Dec 2025 15:29:29 +0000 Subject: [PATCH 029/196] add pending compatibility tests --- compatibility/compatibility.cpp | 1 + compatibility/pending/pending.cpp | 16 + .../regular-expression/regular-expression.cpp | 7 + cucumber_cpp/library/BodyMacro.hpp | 80 +-- .../library/cucumber_expression/Argument.hpp | 2 +- .../cucumber_expression/ParameterRegistry.cpp | 36 +- .../test/TestExpression.cpp | 580 +++++++++--------- 7 files changed, 395 insertions(+), 327 deletions(-) create mode 100644 compatibility/pending/pending.cpp create mode 100644 compatibility/regular-expression/regular-expression.cpp diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 50a13822..b4e047b3 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -310,6 +310,7 @@ namespace compatibility }, .runtime = { .retry = devkit.retry, + .strict = true, }, }; diff --git a/compatibility/pending/pending.cpp b/compatibility/pending/pending.cpp new file mode 100644 index 00000000..8908bdbc --- /dev/null +++ b/compatibility/pending/pending.cpp @@ -0,0 +1,16 @@ +#include "cucumber_cpp/CucumberCpp.hpp" + +GIVEN(R"(an implemented non-pending step)") +{ + // no-op +} + +GIVEN(R"(an implemented step that is skipped)") +{ + // no-op +} + +GIVEN(R"(an unimplemented pending step)") +{ + Pending(); +} diff --git a/compatibility/regular-expression/regular-expression.cpp b/compatibility/regular-expression/regular-expression.cpp new file mode 100644 index 00000000..d42e34ad --- /dev/null +++ b/compatibility/regular-expression/regular-expression.cpp @@ -0,0 +1,7 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +STEP(R"(^a (.*?)(?: and a (.*?))?(?: and a (.*?))?$)", (const std::string& vegtable1, const std::string& vegtable2, const std::string& vegtable3)) +{ + // no-op +} diff --git a/cucumber_cpp/library/BodyMacro.hpp b/cucumber_cpp/library/BodyMacro.hpp index bf105eea..07b1eb5e 100644 --- a/cucumber_cpp/library/BodyMacro.hpp +++ b/cucumber_cpp/library/BodyMacro.hpp @@ -1,12 +1,19 @@ #ifndef CUCUMBER_CPP_BODYMACRO_HPP #define CUCUMBER_CPP_BODYMACRO_HPP +#include "cucumber/messages/step_match_argument.hpp" #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" #include #include +template +T TransformArg(const cucumber::messages::step_match_argument& match) +{ + return cucumber_cpp::library::cucumber_expression::ConverterTypeMap::Instance().at(match.parameter_type_name.value())(match.group); +} + #define BODY_MATCHER(matcher, ...) matcher #define BODY_ARGS(matcher, args, ...) args @@ -15,42 +22,43 @@ #define BODY_STRUCT CONCAT(BodyImpl, __LINE__) -#define BODY(matcher, type, targs, registration, base) \ - namespace \ - { \ - struct BODY_STRUCT : cucumber_cpp::library::Body \ - , base \ - { \ - /* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \ - /* Then the result would be Foo::Bar::Foo::Bar which is invalid */ \ - using myBase = base; \ - using myBase::myBase; \ - \ - void Execute(const cucumber::messages::step_match_arguments_list& args) override \ - { \ - cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ - /* ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); */ \ - ExecuteWithArgs(args, static_cast(nullptr)); \ - } \ - \ - template \ - void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, void (* /* unused */)(TArgs...)) \ - { \ - ExecuteWithArgs(args, std::make_index_sequence{}); \ - } \ - \ - template \ - void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, std::index_sequence /*unused*/) \ - { \ - ExecuteWithArgs(cucumber_cpp::library::cucumber_expression::ConverterTypeMap>::Instance().at(args.step_match_arguments[I].parameter_type_name.value())(args.step_match_arguments[I].group)...); \ - } \ - \ - private: \ - void ExecuteWithArgs targs; \ - static const std::size_t ID; \ - }; \ - } \ - const std::size_t BODY_STRUCT::ID = registration(matcher, type); \ +#define BODY(matcher, type, targs, registration, base) \ + namespace \ + { \ + struct BODY_STRUCT : cucumber_cpp::library::Body \ + , base \ + { \ + /* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \ + /* Then the result would be Foo::Bar::Foo::Bar which is invalid */ \ + using myBase = base; \ + using myBase::myBase; \ + \ + void Execute(const cucumber::messages::step_match_arguments_list& args) override \ + { \ + cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ + /* ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); */ \ + ExecuteWithArgs(args, static_cast(nullptr)); \ + } \ + \ + template \ + void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, void (* /* unused */)(TArgs...)) \ + { \ + ExecuteWithArgs(args, std::make_index_sequence{}); \ + } \ + \ + template \ + void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, std::index_sequence /*unused*/) \ + { \ + ExecuteWithArgs(TransformArg>(args.step_match_arguments[I])...); \ + /*ExecuteWithArgs(cucumber_cpp::library::cucumber_expression::ConverterTypeMap>::Instance().at(args.step_match_arguments[I].parameter_type_name.value())(args.step_match_arguments[I].group)...); */ \ + } \ + \ + private: \ + void ExecuteWithArgs targs; \ + static const std::size_t ID; \ + }; \ + } \ + const std::size_t BODY_STRUCT::ID = registration(matcher, type); \ void BODY_STRUCT::ExecuteWithArgs targs #endif diff --git a/cucumber_cpp/library/cucumber_expression/Argument.hpp b/cucumber_cpp/library/cucumber_expression/Argument.hpp index 98c112f7..a4bd55a5 100644 --- a/cucumber_cpp/library/cucumber_expression/Argument.hpp +++ b/cucumber_cpp/library/cucumber_expression/Argument.hpp @@ -17,7 +17,7 @@ namespace cucumber_cpp::library::cucumber_expression static std::vector BuildArguments(const cucumber::messages::group& group, std::span parameters); template - T GetValue() + T GetValue() const { return ConverterTypeMap::Instance().at(parameter.name)(group); } diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index f6f2b60e..dbd7588b 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -23,26 +23,28 @@ namespace cucumber_cpp::library::cucumber_expression template std::function CreateStreamConverter() { - return [](const cucumber::messages::group& matches) + return [](const cucumber::messages::group& matches) -> T { - return StringTo(matches.value.value()); + if (matches.value.has_value()) + return StringTo(matches.value.value()); + return {}; }; }; - } - std::function CreateStringConverter() - { - return [](const cucumber::messages::group& matches) + std::function CreateStringConverter() { - std::string str = matches.children.front().value.has_value() - ? matches.children.front().value.value() - : matches.children.back().value.value(); + return [](const cucumber::messages::group& matches) + { + std::string str = matches.children.front().value.has_value() + ? matches.children.front().value.value() + : matches.children.back().value.value(); - str = std::regex_replace(str, std::regex(R"__(\\")__"), "\""); - str = std::regex_replace(str, std::regex(R"__(\\')__"), "'"); + str = std::regex_replace(str, std::regex(R"__(\\")__"), "\""); + str = std::regex_replace(str, std::regex(R"__(\\')__"), "'"); - return str; - }; + return str; + }; + } } ParameterRegistry::ParameterRegistry() @@ -50,14 +52,16 @@ namespace cucumber_cpp::library::cucumber_expression const static std::string integerNegativeRegex{ R"__(-?\d+)__" }; const static std::string integerPositiveRegex{ R"__(\d+)__" }; const static std::string floatRegex{ R"__((?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][+-]?\d+)?)__" }; - const static std::string stringDoubleRegex{ R"__("([^\"\\]*(\\.[^\"\\]*)*)")__" }; - const static std::string stringSingleRegex{ R"__('([^'\\]*(\\.[^'\\]*)*)')__" }; + // const static std::string stringDoubleRegex{ R"__("([^\"\\]*(\\.[^\"\\]*)*)")__" }; + // const static std::string stringSingleRegex{ R"__('([^'\\]*(\\.[^'\\]*)*)')__" }; + const static std::string stringRegex{ R"__("([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)')__" }; const static std::string wordRegex{ R"__([^\s]+)__" }; AddBuiltinParameter("int", { integerNegativeRegex, integerPositiveRegex }, CreateStreamConverter()); AddBuiltinParameter("float", { floatRegex }, CreateStreamConverter()); AddBuiltinParameter("word", { wordRegex }, CreateStreamConverter()); - AddBuiltinParameter("string", { stringDoubleRegex, stringSingleRegex }, CreateStringConverter()); + // AddBuiltinParameter("string", { stringDoubleRegex, stringSingleRegex }, CreateStringConverter()); + AddBuiltinParameter("string", { stringRegex }, CreateStringConverter()); AddBuiltinParameter("", { ".*" }, CreateStreamConverter()); AddBuiltinParameter("bigdecimal", { floatRegex }, CreateStreamConverter()); AddBuiltinParameter("biginteger", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index da890afb..072cb3f7 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -1,275 +1,307 @@ -// #include "cucumber/messages/group.hpp" -// #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" -// #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -// #include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" -// #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -// #include "yaml-cpp/node/node.h" -// #include "yaml-cpp/node/parse.h" -// #include "yaml-cpp/yaml.h" -// #include "gmock/gmock.h" -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include - -// namespace cucumber_cpp::library::cucumber_expression -// { -// namespace -// { -// std::vector> GetTestData(const std::filesystem::path& path) -// { -// std::vector> testdata; - -// for (const auto& file : std::filesystem::directory_iterator(path)) -// if (file.is_regular_file() && file.path().extension() == ".yaml") -// testdata.emplace_back(file.path().string(), YAML::LoadFile(file.path().string())); - -// return testdata; -// } - -// std::string FormatMessage(const YAML::Node& node, const Expression& expression) -// { -// return std::format("failed to match {}\n" -// "regex {}\n" -// "against {}", -// node["expression"].as(), expression.Pattern(), node["text"].as()); -// } -// } - -// struct TestExpression : testing::Test -// { -// ParameterRegistry parameterRegistry{}; - -// std::optional> Match(std::string expr, std::string text) -// { -// Expression expression{ std::move(expr), parameterRegistry }; -// return expression.Match(std::move(text)); -// } -// }; - -// TEST_F(TestExpression, TestFromFiles) -// { -// std::filesystem::path testdataPath = "testdata/cucumber-expression/matching"; - -// for (const auto& [file, testdata] : GetTestData(testdataPath)) -// { -// if (testdata["exception"]) -// ASSERT_ANY_THROW(Match(testdata["expression"].as(), testdata["text"].as())) -// << std::format("Test failed for file: {}", file); -// else -// { -// if (testdata["expected_args"].IsNull()) -// ASSERT_THAT(Match(testdata["expression"].as(), testdata["text"].as()), testing::IsFalse()); -// else -// { -// const auto expression = Expression{ testdata["expression"].as(), parameterRegistry }; -// const auto matchOpt = expression.Match(testdata["text"].as()); - -// const auto arguments = expression.MatchToArguments(testdata["text"].as()); - -// ASSERT_THAT(matchOpt, testing::IsTrue()) << FormatMessage(testdata, expression); - -// const auto& match = *matchOpt; -// for (std::size_t i = 0; i < testdata["expected_args"].size(); ++i) -// { -// if (match[i].type() == typeid(std::string)) -// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); -// else if (match[i].type() == typeid(std::int32_t)) -// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); -// else if (match[i].type() == typeid(std::int64_t)) -// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); -// else if (match[i].type() == typeid(float)) -// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); -// else if (match[i].type() == typeid(double)) -// ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); -// else -// FAIL() << "Unknown type: " << match[i].type().name() << " for:\n" -// << FormatMessage(testdata, expression); -// } -// } -// } -// } -// } - -// TEST_F(TestExpression, MatchFloat) -// { -// EXPECT_THAT(Match(R"__({float})__", R"__()__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(.)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(,)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(-)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(E)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(1,)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(,1)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(1.)__"), testing::IsFalse()); - -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1)__"))[0]), testing::FloatNear(1.0f, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-1)__"))[0]), testing::FloatNear(-1.0f, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1.1)__"))[0]), testing::FloatNear(1.1f, std::numeric_limits::epsilon())); -// EXPECT_THAT(Match(R"__({float})__", R"__(1,000)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(1,000,0)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(1,000.1)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(1,000,10)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(1,0.1)__"), testing::IsFalse()); -// EXPECT_THAT(Match(R"__({float})__", R"__(1,000,000.1)__"), testing::IsFalse()); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-1.1)__"))[0]), testing::FloatNear(-1.1f, std::numeric_limits::epsilon())); - -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(.1)__"))[0]), testing::FloatNear(0.1f, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1)__"))[0]), testing::FloatNear(-0.1f, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1000001)__"))[0]), testing::FloatNear(-0.1000001f, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(1E1)__"))[0]), testing::FloatNear(10.0, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(.1E1)__"))[0]), testing::FloatNear(1, std::numeric_limits::epsilon())); -// EXPECT_THAT(Match(R"__({float})__", R"__(1,E1)__"), testing::IsFalse()); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.01)__"))[0]), testing::FloatNear(-0.01, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E-1)__"))[0]), testing::FloatNear(-0.01, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E-2)__"))[0]), testing::FloatNear(-0.001, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E+1)__"))[0]), testing::FloatNear(-1, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E+2)__"))[0]), testing::FloatNear(-10, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E1)__"))[0]), testing::FloatNear(-1, std::numeric_limits::epsilon())); -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(-.1E2)__"))[0]), testing::FloatNear(-10, std::numeric_limits::epsilon())); -// } - -// TEST_F(TestExpression, FloatWithZero) -// { -// EXPECT_THAT(std::any_cast((*Match(R"__({float})__", R"__(0)__"))[0]), testing::FloatNear(0.0f, std::numeric_limits::epsilon())); -// } - -// TEST_F(TestExpression, MatchAnonymous) -// { -// EXPECT_THAT(std::any_cast((*Match(R"__({})__", R"__(0.22)__"))[0]), testing::StrEq("0.22")); -// } - -// TEST_F(TestExpression, MatchCustom) -// { -// struct CustomType -// { -// std::optional text; -// std::optional number; -// }; - -// parameterRegistry.AddParameter("textAndOrNumber", { R"(([A-Z]+)?(?: )?([0-9]+)?)" }, -// { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group -// { -// std::optional text{ matches[1].matched ? matches[1].str() : std::optional{ std::nullopt } }; -// std::optional number{ matches[2].matched ? matches[2].str() : std::optional{ std::nullopt } }; - -// return { .children = { cucumber::messages::group{ .value = text }, cucumber::messages::group{ .value = number } } }; -// }, -// .toAny = [](const cucumber::messages::group& matches) -> std::any -// { -// std::optional text{ matches.children[0].value }; -// std::optional number{ matches.children[1].value ? StringTo(matches.children[1].value.value()) : std::optional{ std::nullopt } }; -// return CustomType{ text, number }; -// } }); - -// auto matchString{ Match(R"__({textAndOrNumber})__", R"__(ABC)__") }; -// EXPECT_THAT(matchString, testing::IsTrue()); -// EXPECT_THAT(std::any_cast((*matchString)[0]).text.value(), testing::StrEq("ABC")); -// EXPECT_THAT(std::any_cast((*matchString)[0]).number, testing::IsFalse()); - -// auto matchInt{ Match(R"__({textAndOrNumber})__", R"__(123)__") }; -// EXPECT_THAT(matchInt, testing::IsTrue()); -// EXPECT_THAT(std::any_cast((*matchInt)[0]).text, testing::IsFalse()); -// EXPECT_THAT(std::any_cast((*matchInt)[0]).number.value(), testing::Eq(123)); - -// auto matchStringAndInt{ Match(R"__({textAndOrNumber})__", R"__(ABC 123)__") }; -// EXPECT_THAT(matchStringAndInt, testing::IsTrue()); -// EXPECT_THAT(std::any_cast((*matchStringAndInt)[0]).text.value(), testing::StrEq("ABC")); -// EXPECT_THAT(std::any_cast((*matchStringAndInt)[0]).number.value(), testing::Eq(123)); -// } - -// TEST_F(TestExpression, ExposeSource) -// { -// auto expr = "I have {int} cuke(s)"; -// Expression expression{ expr, parameterRegistry }; -// EXPECT_THAT(expr, testing::StrEq(expression.Source())); -// } - -// TEST_F(TestExpression, MatchBoolean) -// { -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(true)__"))[0]), testing::IsTrue()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(1)__"))[0]), testing::IsTrue()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(yes)__"))[0]), testing::IsTrue()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(on)__"))[0]), testing::IsTrue()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(enabled)__"))[0]), testing::IsTrue()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(active)__"))[0]), testing::IsTrue()); - -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(false)__"))[0]), testing::IsFalse()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(0)__"))[0]), testing::IsFalse()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(2)__"))[0]), testing::IsFalse()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(off)__"))[0]), testing::IsFalse()); -// EXPECT_THAT(std::any_cast((*Match(R"__({bool})__", R"__(foo)__"))[0]), testing::IsFalse()); -// } - -// TEST_F(TestExpression, ThrowUnknownParameterType) -// { -// auto expr = "I have {doesnotexist} cuke(s)"; - -// try -// { -// Expression expression{ expr, parameterRegistry }; -// FAIL() << "Expected UndefinedParameterTypeError to be thrown"; -// } -// catch (UndefinedParameterTypeError e) -// { -// EXPECT_THAT(e.what(), testing::StrEq("This Cucumber Expression has a problem at column 8:\n" -// "\n" -// "I have {doesnotexist} cuke(s)\n" -// " ^------------^\n" -// "Undefined parameter type 'doesnotexist'\n" -// "Please register a ParameterType for 'doesnotexist'\n")); -// } -// } - -// TEST_F(TestExpression, ThrowDuplicateAnonymousParameterError) -// { -// try -// { -// parameterRegistry.AddParameter("", { ".*" }, -// { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group -// { -// return { .value = matches.begin()->str() }; -// }, -// .toAny = [](const cucumber::messages::group& matches) -> std::any -// { -// return matches.value.value(); -// } }); -// FAIL() << "Expected CucumberExpressionError to be thrown"; -// } -// catch (const CucumberExpressionError& e) -// { -// EXPECT_THAT(e.what(), testing::StrEq("The anonymous parameter type has already been defined")); -// } -// } - -// TEST_F(TestExpression, ThrowDuplicateParameterError) -// { -// try -// { -// parameterRegistry.AddParameter("word", { ".*" }, -// { .toStrings = [](const MatchRange& matches) -> cucumber::messages::group -// { -// return { .value = matches.begin()->str() }; -// }, -// .toAny = [](const cucumber::messages::group& matches) -> std::any -// { -// return matches.value.value(); -// } }); -// FAIL() << "Expected CucumberExpressionError to be thrown"; -// } -// catch (const CucumberExpressionError& e) -// { -// EXPECT_THAT(e.what(), testing::StrEq("There is already a parameter with name word")); -// } -// } -// } +#include "cucumber/messages/group.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" +#include "cucumber_cpp/library/cucumber_expression/Errors.hpp" +#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" +#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "yaml-cpp/node/node.h" +#include "yaml-cpp/node/parse.h" +#include "yaml-cpp/yaml.h" +#include "gmock/gmock.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::cucumber_expression +{ + namespace + { + std::vector> GetTestData(const std::filesystem::path& path) + { + std::vector> testdata; + + for (const auto& file : std::filesystem::directory_iterator(path)) + if (file.is_regular_file() && file.path().extension() == ".yaml") + testdata.emplace_back(file.path().string(), YAML::LoadFile(file.path().string())); + + return testdata; + } + + std::string FormatMessage(const std::string& file, const YAML::Node& node, const Expression& expression) + { + return std::format("input: {}\n" + "failed to match {}\n" + "regex {}\n" + "against {}", + file, node["expression"].as(), expression.Pattern(), node["text"].as()); + } + } + + struct TestExpression : testing::Test + { + ParameterRegistry parameterRegistry{}; + + template + std::optional Match(std::string expr, std::string text) + { + Expression expression{ std::move(expr), parameterRegistry }; + auto args = expression.MatchToArguments(std::move(text)); + + if (!args.has_value()) + return std::nullopt; + else + return args.value()[0].GetValue(); + } + }; + + TEST_F(TestExpression, TestFromFiles) + { + std::filesystem::path testdataPath = "testdata/cucumber-expression/matching"; + + for (const auto& [file, testdata] : GetTestData(testdataPath)) + { + if (testdata["exception"]) + ASSERT_ANY_THROW(Match(testdata["expression"].as(), testdata["text"].as())) + << std::format("Test failed for file: {}", file); + else + { + if (testdata["expected_args"].IsNull()) + ASSERT_THAT(Match(testdata["expression"].as(), testdata["text"].as()), testing::IsFalse()); + else + { + const auto expression = Expression{ testdata["expression"].as(), parameterRegistry }; + const auto matchOpt = expression.MatchToArguments(testdata["text"].as()); + + const auto arguments = expression.MatchToArguments(testdata["text"].as()); + + ASSERT_THAT(matchOpt, testing::IsTrue()) << FormatMessage(file, testdata, expression); + + const auto& match = *matchOpt; + for (std::size_t i = 0; i < testdata["expected_args"].size(); ++i) + { + if (file.ends_with("matches-single-quoted-empty-string-as-empty-string.yaml")) + std::cout << "testme"; + + const auto& argument = match[i]; + const auto actualValue = argument.Name() == "string" ? argument.GetValue() : argument.Group().value.value(); + const auto expectedValue = testdata["expected_args"][i].as(); + + if (argument.Name() == "int") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "float") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "word") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "string") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "bigdecimal") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "biginteger") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "byte") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "short") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "long") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + if (argument.Name() == "double") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + // if (match[i].type() == typeid(std::string)) + // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) + // << FormatMessage(testdata, expression); + // else if (match[i].type() == typeid(std::int32_t)) + // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); + // else if (match[i].type() == typeid(std::int64_t)) + // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); + // else if (match[i].type() == typeid(float)) + // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); + // else if (match[i].type() == typeid(double)) + // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); + // else + // FAIL() << "Unknown type: " << match[i].type().name() << " for:\n" + // << FormatMessage(testdata, expression); + } + } + } + } + } + + TEST_F(TestExpression, MatchFloat) + { + EXPECT_THAT(Match(R"__({float})__", R"__()__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(.)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(,)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(-)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(E)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(1,)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(,1)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(1.)__"), testing::IsFalse()); + + EXPECT_THAT(Match(R"__({float})__", R"__(1)__").value(), testing::FloatNear(1.0f, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-1)__").value(), testing::FloatNear(-1.0f, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(1.1)__").value(), testing::FloatNear(1.1f, std::numeric_limits::epsilon())); + + EXPECT_THAT(Match(R"__({float})__", R"__(1,000)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(1,000,0)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(1,000.1)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(1,000,10)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(1,0.1)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(1,000,000.1)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(-1.1)__").value(), testing::FloatNear(-1.1f, std::numeric_limits::epsilon())); + + EXPECT_THAT(Match(R"__({float})__", R"__(.1)__").value(), testing::FloatNear(0.1f, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-.1)__").value(), testing::FloatNear(-0.1f, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-.1000001)__").value(), testing::FloatNear(-0.1000001f, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(1E1)__").value(), testing::FloatNear(10.0, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(.1E1)__").value(), testing::FloatNear(1, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(1,E1)__"), testing::IsFalse()); + EXPECT_THAT(Match(R"__({float})__", R"__(-.01)__").value(), testing::FloatNear(-0.01, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-.1E-1)__").value(), testing::FloatNear(-0.01, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-.1E-2)__").value(), testing::FloatNear(-0.001, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-.1E+1)__").value(), testing::FloatNear(-1, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-.1E+2)__").value(), testing::FloatNear(-10, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-.1E1)__").value(), testing::FloatNear(-1, std::numeric_limits::epsilon())); + EXPECT_THAT(Match(R"__({float})__", R"__(-.1E2)__").value(), testing::FloatNear(-10, std::numeric_limits::epsilon())); + } + + TEST_F(TestExpression, FloatWithZero) + { + EXPECT_THAT(Match(R"__({float})__", R"__(0)__").value(), testing::FloatNear(0.0f, std::numeric_limits::epsilon())); + } + + TEST_F(TestExpression, MatchAnonymous) + { + EXPECT_THAT(Match(R"__({})__", R"__(0.22)__").value(), testing::StrEq("0.22")); + } + + TEST_F(TestExpression, MatchCustom) + { + struct CustomType + { + std::optional text; + std::optional number; + }; + + parameterRegistry.AddParameter("textAndOrNumber", { R"(([A-Z]+)?(?: )?([0-9]+)?)" }, + [](const cucumber::messages::group& matches) -> CustomType + { + std::optional text{ matches.children[0].value }; + std::optional number{ matches.children[1].value ? StringTo(matches.children[1].value.value()) : std::optional{ std::nullopt } }; + return CustomType{ text, number }; + }); + + auto matchString{ Match(R"__({textAndOrNumber})__", R"__(ABC)__") }; + EXPECT_THAT(matchString, testing::IsTrue()); + EXPECT_THAT(matchString.value().text.value(), testing::StrEq("ABC")); + EXPECT_THAT(matchString.value().number, testing::IsFalse()); + + auto matchInt{ Match(R"__({textAndOrNumber})__", R"__(123)__") }; + EXPECT_THAT(matchInt, testing::IsTrue()); + EXPECT_THAT(matchInt.value().text, testing::IsFalse()); + EXPECT_THAT(matchInt.value().number.value(), testing::Eq(123)); + + auto matchStringAndInt{ Match(R"__({textAndOrNumber})__", R"__(ABC 123)__") }; + EXPECT_THAT(matchStringAndInt, testing::IsTrue()); + EXPECT_THAT(matchStringAndInt.value().text.value(), testing::StrEq("ABC")); + EXPECT_THAT(matchStringAndInt.value().number.value(), testing::Eq(123)); + } + + TEST_F(TestExpression, ExposeSource) + { + auto expr = "I have {int} cuke(s)"; + Expression expression{ expr, parameterRegistry }; + EXPECT_THAT(expr, testing::StrEq(expression.Source())); + } + + TEST_F(TestExpression, MatchBoolean) + { + EXPECT_THAT(Match(R"__({bool})__", R"__(true)__").value(), testing::IsTrue()); + EXPECT_THAT(Match(R"__({bool})__", R"__(1)__").value(), testing::IsTrue()); + EXPECT_THAT(Match(R"__({bool})__", R"__(yes)__").value(), testing::IsTrue()); + EXPECT_THAT(Match(R"__({bool})__", R"__(on)__").value(), testing::IsTrue()); + EXPECT_THAT(Match(R"__({bool})__", R"__(enabled)__").value(), testing::IsTrue()); + EXPECT_THAT(Match(R"__({bool})__", R"__(active)__").value(), testing::IsTrue()); + + EXPECT_THAT(Match(R"__({bool})__", R"__(false)__").value(), testing::IsFalse()); + EXPECT_THAT(Match(R"__({bool})__", R"__(0)__").value(), testing::IsFalse()); + EXPECT_THAT(Match(R"__({bool})__", R"__(2)__").value(), testing::IsFalse()); + EXPECT_THAT(Match(R"__({bool})__", R"__(off)__").value(), testing::IsFalse()); + EXPECT_THAT(Match(R"__({bool})__", R"__(foo)__").value(), testing::IsFalse()); + } + + TEST_F(TestExpression, ThrowUnknownParameterType) + { + auto expr = "I have {doesnotexist} cuke(s)"; + + try + { + Expression expression{ expr, parameterRegistry }; + FAIL() << "Expected UndefinedParameterTypeError to be thrown"; + } + catch (UndefinedParameterTypeError e) + { + EXPECT_THAT(e.what(), testing::StrEq("This Cucumber Expression has a problem at column 8:\n" + "\n" + "I have {doesnotexist} cuke(s)\n" + " ^------------^\n" + "Undefined parameter type 'doesnotexist'\n" + "Please register a ParameterType for 'doesnotexist'\n")); + } + } + + TEST_F(TestExpression, ThrowDuplicateAnonymousParameterError) + { + try + { + parameterRegistry.AddParameter("", { ".*" }, + [](const cucumber::messages::group& matches) -> std::string + { + return matches.value.value(); + }); + FAIL() << "Expected CucumberExpressionError to be thrown"; + } + catch (const CucumberExpressionError& e) + { + EXPECT_THAT(e.what(), testing::StrEq("The anonymous parameter type has already been defined")); + } + } + + TEST_F(TestExpression, ThrowDuplicateParameterError) + { + try + { + parameterRegistry.AddParameter("word", { ".*" }, + [](const cucumber::messages::group& matches) -> std::string + { + return matches.value.value(); + }); + FAIL() << "Expected CucumberExpressionError to be thrown"; + } + catch (const CucumberExpressionError& e) + { + EXPECT_THAT(e.what(), testing::StrEq("There is already a parameter with name word")); + } + } +} From c67ebd429589795db55a39a997e57879a7243bd8 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 22 Dec 2025 15:33:01 +0000 Subject: [PATCH 030/196] fixed TestExpression tests --- .../test/TestExpression.cpp | 40 ++++++++----------- 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index 072cb3f7..c6acf48e 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -98,50 +98,42 @@ namespace cucumber_cpp::library::cucumber_expression const auto actualValue = argument.Name() == "string" ? argument.GetValue() : argument.Group().value.value(); const auto expectedValue = testdata["expected_args"][i].as(); - if (argument.Name() == "int") + if (argument.Name() == "") + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + + else if (argument.Name() == "int") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "float") + else if (argument.Name() == "float") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "word") + else if (argument.Name() == "word") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "string") + else if (argument.Name() == "string") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "bigdecimal") + else if (argument.Name() == "bigdecimal") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "biginteger") + else if (argument.Name() == "biginteger") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "byte") + else if (argument.Name() == "byte") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "short") + else if (argument.Name() == "short") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "long") + else if (argument.Name() == "long") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - if (argument.Name() == "double") + else if (argument.Name() == "double") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); - // if (match[i].type() == typeid(std::string)) - // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) - // << FormatMessage(testdata, expression); - // else if (match[i].type() == typeid(std::int32_t)) - // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - // else if (match[i].type() == typeid(std::int64_t)) - // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - // else if (match[i].type() == typeid(float)) - // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - // else if (match[i].type() == typeid(double)) - // ASSERT_THAT(std::any_cast(match[i]), testdata["expected_args"][i].as()) << FormatMessage(testdata, expression); - // else - // FAIL() << "Unknown type: " << match[i].type().name() << " for:\n" - // << FormatMessage(testdata, expression); + else + FAIL() << "Unknown type: " << argument.Name() << " for:\n" + << FormatMessage(file, testdata, expression); } } } From 323b2d486f842212466a958b302485df4ff9d13c Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 22 Dec 2025 15:51:43 +0000 Subject: [PATCH 031/196] fixed TestExpression tests --- .../library/cucumber_expression/test/TestExpression.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index c6acf48e..73b3c4ff 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -91,12 +91,7 @@ namespace cucumber_cpp::library::cucumber_expression const auto& match = *matchOpt; for (std::size_t i = 0; i < testdata["expected_args"].size(); ++i) { - if (file.ends_with("matches-single-quoted-empty-string-as-empty-string.yaml")) - std::cout << "testme"; - const auto& argument = match[i]; - const auto actualValue = argument.Name() == "string" ? argument.GetValue() : argument.Group().value.value(); - const auto expectedValue = testdata["expected_args"][i].as(); if (argument.Name() == "") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); From 3be881a3cb33f01473b706b75c5551b16fa19802 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 22 Dec 2025 16:04:08 +0000 Subject: [PATCH 032/196] add retry, retry-ambiguous, retry-pending and retry-undefined compatibility tests --- compatibility/compatibility.cpp | 2 +- .../retry-ambiguous/retry-ambiguous.cpp | 11 +++++++ compatibility/retry-pending/retry-pending.cpp | 6 ++++ .../retry-undefined/retry-undefined.cpp | 0 compatibility/retry/retry.cpp | 33 +++++++++++++++++++ 5 files changed, 51 insertions(+), 1 deletion(-) create mode 100644 compatibility/retry-ambiguous/retry-ambiguous.cpp create mode 100644 compatibility/retry-pending/retry-pending.cpp create mode 100644 compatibility/retry-undefined/retry-undefined.cpp create mode 100644 compatibility/retry/retry.cpp diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index b4e047b3..bb3f0f8d 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -309,7 +309,7 @@ namespace compatibility .ordering = isReversed ? cucumber_cpp::library::support::RunOptions::Ordering::reverse : cucumber_cpp::library::support::RunOptions::Ordering::defined, }, .runtime = { - .retry = devkit.retry, + .retry = std::string{ KIT_STRING }.starts_with("retry") ? 2u : 0u, .strict = true, }, }; diff --git a/compatibility/retry-ambiguous/retry-ambiguous.cpp b/compatibility/retry-ambiguous/retry-ambiguous.cpp new file mode 100644 index 00000000..71944084 --- /dev/null +++ b/compatibility/retry-ambiguous/retry-ambiguous.cpp @@ -0,0 +1,11 @@ +#include "cucumber_cpp/CucumberCpp.hpp" + +GIVEN(R"(an ambiguous step)") +{ + // no-op +} + +GIVEN(R"(an ambiguous step)") +{ + // no-op +} diff --git a/compatibility/retry-pending/retry-pending.cpp b/compatibility/retry-pending/retry-pending.cpp new file mode 100644 index 00000000..09b3099e --- /dev/null +++ b/compatibility/retry-pending/retry-pending.cpp @@ -0,0 +1,6 @@ +#include "cucumber_cpp/CucumberCpp.hpp" + +GIVEN(R"(a pending step)") +{ + Pending(); +} diff --git a/compatibility/retry-undefined/retry-undefined.cpp b/compatibility/retry-undefined/retry-undefined.cpp new file mode 100644 index 00000000..e69de29b diff --git a/compatibility/retry/retry.cpp b/compatibility/retry/retry.cpp new file mode 100644 index 00000000..94ee443b --- /dev/null +++ b/compatibility/retry/retry.cpp @@ -0,0 +1,33 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +GIVEN(R"(a step that always passes)") +{ + // no-op +} + +namespace +{ + auto secondTimePass = 0; + auto thirdTimePass = 0; +} + +GIVEN(R"(a step that passes the second time)") +{ + secondTimePass++; + EXPECT_THAT(secondTimePass, testing::Ge(2)); +} + +GIVEN(R"(a step that passes the third time)") +{ + // no-op + thirdTimePass++; + EXPECT_THAT(thirdTimePass, testing::Ge(3)); +} + +GIVEN(R"(a step that always fails)") +{ + // no-op + FAIL(); +} From e94534c9574a698a757b9b95d24125298e4731bd Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 22 Dec 2025 16:33:31 +0000 Subject: [PATCH 033/196] add rules, rules-backgrounds and skipped compatibility tests --- .../rules-backgrounds/rules-backgrounds.cpp | 17 ++++++++ compatibility/rules/rules.cpp | 42 +++++++++++++++++++ compatibility/skipped/skipped.cpp | 16 +++++++ 3 files changed, 75 insertions(+) create mode 100644 compatibility/rules-backgrounds/rules-backgrounds.cpp create mode 100644 compatibility/rules/rules.cpp create mode 100644 compatibility/skipped/skipped.cpp diff --git a/compatibility/rules-backgrounds/rules-backgrounds.cpp b/compatibility/rules-backgrounds/rules-backgrounds.cpp new file mode 100644 index 00000000..2cc1e047 --- /dev/null +++ b/compatibility/rules-backgrounds/rules-backgrounds.cpp @@ -0,0 +1,17 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +GIVEN(R"(an order for {string})", (const std::string& order)) +{ + // no-op +} + +WHEN(R"(an action)") +{ + // no-op +} + +THEN(R"(an outcome)") +{ + // no-op +} diff --git a/compatibility/rules/rules.cpp b/compatibility/rules/rules.cpp new file mode 100644 index 00000000..709ae320 --- /dev/null +++ b/compatibility/rules/rules.cpp @@ -0,0 +1,42 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include "gmock/gmock.h" +#include +#include +#include + +using Stock = std::stack; + +GIVEN(R"(the customer has {int} cents)", (std::int32_t money)) +{ + context.InsertAt("money", money); +} + +GIVEN(R"(there are chocolate bars in stock)") +{ + auto stock = context.Emplace(); + stock->push("Mars"); +} + +GIVEN(R"(there are no chocolate bars in stock)") +{ + context.Emplace(); +} + +WHEN(R"(the customer tries to buy a {int} cent chocolate bar)",(std::int32_t price)) +{ + if (context.Get("money") >= price && !context.Get().empty()) + { + context.InsertAt("chocolate", context.Get().top()); + context.Get().pop(); + } +} + +THEN(R"(the sale should not happen)") +{ + EXPECT_THAT(context.Contains("chocolate"), testing::IsFalse()); +} + +THEN(R"(the sale should happen)") +{ + EXPECT_THAT(context.Contains("chocolate"), testing::IsTrue()); +} diff --git a/compatibility/skipped/skipped.cpp b/compatibility/skipped/skipped.cpp new file mode 100644 index 00000000..6e0e9773 --- /dev/null +++ b/compatibility/skipped/skipped.cpp @@ -0,0 +1,16 @@ +#include "cucumber_cpp/CucumberCpp.hpp" + +GIVEN(R"(a step that does not skip)") +{ + // no-op +} + +WHEN(R"(a step that is skipped)") +{ + // no-op +} + +THEN(R"(I skip a step)") +{ + Skipped(); +} From c235f1ae01293a912a9fe1b42944586d47f912b8 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 22 Dec 2025 23:46:38 +0000 Subject: [PATCH 034/196] add undefined, unknown-parameter-type and unused-steps compatibility tests, improved ambiguous test --- compatibility/ambiguous/ambiguous.cpp | 2 +- compatibility/ambiguous/ambiguous.ndjson | 4 +- compatibility/compatibility.cpp | 40 ++-------- compatibility/undefined/undefined.cpp | 11 +++ .../unknown-parameter-type.cpp | 10 +++ compatibility/unused-steps/unused-steps.cpp | 10 +++ cucumber_cpp/library/BodyMacro.hpp | 75 +++++++++---------- cucumber_cpp/library/StepRegistry.cpp | 66 +++++++++------- cucumber_cpp/library/StepRegistry.hpp | 4 +- cucumber_cpp/library/api/RunCucumber.cpp | 21 +++++- .../library/assemble/AssembleTestSuites.cpp | 2 +- .../library/cucumber_expression/Errors.cpp | 5 +- .../library/cucumber_expression/Errors.hpp | 6 +- .../cucumber_expression/ParameterRegistry.cpp | 5 +- .../cucumber_expression/TreeRegexp.cpp | 2 +- .../library/support/SupportCodeLibrary.hpp | 2 + .../library/support/UndefinedParameters.hpp | 15 ++++ 17 files changed, 167 insertions(+), 113 deletions(-) create mode 100644 compatibility/undefined/undefined.cpp create mode 100644 compatibility/unknown-parameter-type/unknown-parameter-type.cpp create mode 100644 compatibility/unused-steps/unused-steps.cpp create mode 100644 cucumber_cpp/library/support/UndefinedParameters.hpp diff --git a/compatibility/ambiguous/ambiguous.cpp b/compatibility/ambiguous/ambiguous.cpp index b6c1430e..f21b5b41 100644 --- a/compatibility/ambiguous/ambiguous.cpp +++ b/compatibility/ambiguous/ambiguous.cpp @@ -6,7 +6,7 @@ STEP(R"(^a (.*?) with (.*?)$)", (const std::string& arg1, const std::string& arg // no-op } -STEP(R"(^a step with (.*)$)", (const std::string& arg1)) +STEP(R"(^a step with (.*?)$)", (const std::string& arg1)) { // no-op } diff --git a/compatibility/ambiguous/ambiguous.ndjson b/compatibility/ambiguous/ambiguous.ndjson index 66be9159..5cbe118b 100644 --- a/compatibility/ambiguous/ambiguous.ndjson +++ b/compatibility/ambiguous/ambiguous.ndjson @@ -3,9 +3,9 @@ {"gherkinDocument":{"feature":{"tags":[],"location":{"line":1,"column":1},"language":"en","keyword":"Feature","name":"Ambiguous steps","description":" Multiple step definitions that match a pickle step result in an AMBIGUOUS status, since Cucumnber cannot determine\n which one to execute.","children":[{"scenario":{"id":"1","tags":[],"location":{"line":5,"column":3},"keyword":"Scenario","name":"Multiple step definitions for a step","description":"","steps":[{"id":"0","location":{"line":6,"column":5},"keyword":"Given ","keywordType":"Context","text":"a step with multiple definitions"}],"examples":[]}}]},"comments":[],"uri":"samples/ambiguous/ambiguous.feature"}} {"pickle":{"id":"3","uri":"samples/ambiguous/ambiguous.feature","location":{"line":5,"column":3},"astNodeIds":["1"],"tags":[],"name":"Multiple step definitions for a step","language":"en","steps":[{"id":"2","text":"a step with multiple definitions","type":"Context","astNodeIds":["0"]}]}} {"stepDefinition":{"id":"4","pattern":{"type":"REGULAR_EXPRESSION","source":"^a (.*?) with (.*?)$"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":3}}}} -{"stepDefinition":{"id":"5","pattern":{"type":"REGULAR_EXPRESSION","source":"^a step with (.*)$"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":7}}}} +{"stepDefinition":{"id":"5","pattern":{"type":"REGULAR_EXPRESSION","source":"^a step with (.*?)$"},"sourceReference":{"uri":"samples/ambiguous/ambiguous.ts","location":{"line":7}}}} {"testRunStarted":{"id":"6","timestamp":{"seconds":0,"nanos":0}}} -{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["4","5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":2,"value":"step","children":[]}},{"group":{"start":12,"value":"multiple definitions","children":[]}}]},{"stepMatchArguments":[{"group":{"start":12,"value":"multiple definitions","children":[]},"parameterTypeName":""}]}]}],"testRunStartedId":"6"}} +{"testCase":{"id":"7","pickleId":"3","testSteps":[{"id":"8","pickleStepId":"2","stepDefinitionIds":["4","5"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"start":2,"value":"step","children":[]}},{"group":{"start":12,"value":"multiple definitions","children":[]}}]},{"stepMatchArguments":[{"group":{"start":12,"value":"multiple definitions","children":[]}}]}]}],"testRunStartedId":"6"}} {"testCaseStarted":{"id":"9","testCaseId":"7","timestamp":{"seconds":0,"nanos":1000000},"attempt":0}} {"testStepStarted":{"testCaseStartedId":"9","testStepId":"8","timestamp":{"seconds":0,"nanos":2000000}}} {"testStepFinished":{"testCaseStartedId":"9","testStepId":"8","testStepResult":{"status":"AMBIGUOUS","duration":{"seconds":0,"nanos":0}},"timestamp":{"seconds":0,"nanos":3000000}}} diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index bb3f0f8d..2e76516f 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -65,25 +65,18 @@ namespace compatibility auto& key = jsonIter.key(); auto& value = jsonIter.value(); - if (key == "parameterTypeName" && value.get().empty()) - jsonIter = json.erase(jsonIter); - else if (key == "exception") + if (key == "exception") jsonIter = json.erase(jsonIter); else if (key == "message") jsonIter = json.erase(jsonIter); else if (key == "line") jsonIter = json.erase(jsonIter); - else if (key == "start") - jsonIter = json.erase(jsonIter); else if (key == "snippets") jsonIter = json.erase(jsonIter); else if (value.is_object()) { SanitizeExpectedJson(value); - if (value.size() == 0) - jsonIter = json.erase(jsonIter); - else - ++jsonIter; + ++jsonIter; } else if (value.is_array()) { @@ -95,16 +88,10 @@ namespace compatibility if (item.is_object()) SanitizeExpectedJson(item); - if (item.size() == 0) - valueIter = value.erase(valueIter); - else - ++valueIter; + ++valueIter; } - if (value.size() == 0) - jsonIter = json.erase(jsonIter); - else - ++jsonIter; + ++jsonIter; } else if (key == "uri") { @@ -124,9 +111,7 @@ namespace compatibility auto& key = jsonIter.key(); auto& value = jsonIter.value(); - if (key == "parameterTypeName" && value.get().empty()) - jsonIter = json.erase(jsonIter); - else if (key == "exception") + if (key == "exception") jsonIter = json.erase(jsonIter); else if (key == "message") jsonIter = json.erase(jsonIter); @@ -137,10 +122,7 @@ namespace compatibility else if (value.is_object()) { SanitizeActualJson(value); - if (value.size() == 0) - jsonIter = json.erase(jsonIter); - else - ++jsonIter; + ++jsonIter; } else if (value.is_array()) { @@ -152,16 +134,10 @@ namespace compatibility if (item.is_object()) SanitizeActualJson(item); - if (item.size() == 0) - valueIter = value.erase(valueIter); - else - ++valueIter; + ++valueIter; } - if (value.size() == 0) - jsonIter = json.erase(jsonIter); - else - ++jsonIter; + ++jsonIter; } else ++jsonIter; diff --git a/compatibility/undefined/undefined.cpp b/compatibility/undefined/undefined.cpp new file mode 100644 index 00000000..292ca85c --- /dev/null +++ b/compatibility/undefined/undefined.cpp @@ -0,0 +1,11 @@ +#include "cucumber_cpp/CucumberCpp.hpp" + +GIVEN(R"(an implemented step)") +{ + // no-op +} + +GIVEN(R"(a step that will be skipped)") +{ + // no-op +} diff --git a/compatibility/unknown-parameter-type/unknown-parameter-type.cpp b/compatibility/unknown-parameter-type/unknown-parameter-type.cpp new file mode 100644 index 00000000..bea9d047 --- /dev/null +++ b/compatibility/unknown-parameter-type/unknown-parameter-type.cpp @@ -0,0 +1,10 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +struct Airport +{}; + +GIVEN(R"({airport} is closed because of a strike)", (const Airport& airport)) +{ + FAIL() << "Should not be called because airport parameter type has not been defined"; +} diff --git a/compatibility/unused-steps/unused-steps.cpp b/compatibility/unused-steps/unused-steps.cpp new file mode 100644 index 00000000..681b2a08 --- /dev/null +++ b/compatibility/unused-steps/unused-steps.cpp @@ -0,0 +1,10 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include + +GIVEN(R"(a step that is used)") +{ +} + +GIVEN(R"(a step that is not used)") +{ +} diff --git a/cucumber_cpp/library/BodyMacro.hpp b/cucumber_cpp/library/BodyMacro.hpp index 07b1eb5e..0b726ff1 100644 --- a/cucumber_cpp/library/BodyMacro.hpp +++ b/cucumber_cpp/library/BodyMacro.hpp @@ -11,7 +11,7 @@ template T TransformArg(const cucumber::messages::step_match_argument& match) { - return cucumber_cpp::library::cucumber_expression::ConverterTypeMap::Instance().at(match.parameter_type_name.value())(match.group); + return cucumber_cpp::library::cucumber_expression::ConverterTypeMap::Instance().at(match.parameter_type_name.value_or(""))(match.group); } #define BODY_MATCHER(matcher, ...) matcher @@ -22,43 +22,42 @@ T TransformArg(const cucumber::messages::step_match_argument& match) #define BODY_STRUCT CONCAT(BodyImpl, __LINE__) -#define BODY(matcher, type, targs, registration, base) \ - namespace \ - { \ - struct BODY_STRUCT : cucumber_cpp::library::Body \ - , base \ - { \ - /* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \ - /* Then the result would be Foo::Bar::Foo::Bar which is invalid */ \ - using myBase = base; \ - using myBase::myBase; \ - \ - void Execute(const cucumber::messages::step_match_arguments_list& args) override \ - { \ - cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ - /* ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); */ \ - ExecuteWithArgs(args, static_cast(nullptr)); \ - } \ - \ - template \ - void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, void (* /* unused */)(TArgs...)) \ - { \ - ExecuteWithArgs(args, std::make_index_sequence{}); \ - } \ - \ - template \ - void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, std::index_sequence /*unused*/) \ - { \ - ExecuteWithArgs(TransformArg>(args.step_match_arguments[I])...); \ - /*ExecuteWithArgs(cucumber_cpp::library::cucumber_expression::ConverterTypeMap>::Instance().at(args.step_match_arguments[I].parameter_type_name.value())(args.step_match_arguments[I].group)...); */ \ - } \ - \ - private: \ - void ExecuteWithArgs targs; \ - static const std::size_t ID; \ - }; \ - } \ - const std::size_t BODY_STRUCT::ID = registration(matcher, type); \ +#define BODY(matcher, type, targs, registration, base) \ + namespace \ + { \ + struct BODY_STRUCT : cucumber_cpp::library::Body \ + , base \ + { \ + /* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \ + /* Then the result would be Foo::Bar::Foo::Bar which is invalid */ \ + using myBase = base; \ + using myBase::myBase; \ + \ + void Execute(const cucumber::messages::step_match_arguments_list& args) override \ + { \ + cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ + /* ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); */ \ + ExecuteWithArgs(args, static_cast(nullptr)); \ + } \ + \ + template \ + void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, void (* /* unused */)(TArgs...)) \ + { \ + ExecuteWithArgs(args, std::make_index_sequence{}); \ + } \ + \ + template \ + void ExecuteWithArgs(const cucumber::messages::step_match_arguments_list& args, std::index_sequence /*unused*/) \ + { \ + ExecuteWithArgs(TransformArg>(args.step_match_arguments[I])...); \ + } \ + \ + private: \ + void ExecuteWithArgs targs; \ + static const std::size_t ID; \ + }; \ + } \ + const std::size_t BODY_STRUCT::ID = registration(matcher, type); \ void BODY_STRUCT::ExecuteWithArgs targs #endif diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index e8925982..3b248cce 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -1,19 +1,21 @@ #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" -#include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber_cpp/library/cucumber_expression/Argument.hpp" +#include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/RegularExpression.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include #include #include #include #include +#include #include #include #include @@ -23,8 +25,9 @@ namespace cucumber_cpp::library { - StepRegistry::StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, cucumber::gherkin::id_generator_ptr idGenerator) + StepRegistry::StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, support::UndefinedParameters& undefinedParameters, cucumber::gherkin::id_generator_ptr idGenerator) : parameterRegistry{ parameterRegistry } + , undefinedParameters{ undefinedParameters } , idGenerator{ idGenerator } { } @@ -88,31 +91,40 @@ namespace cucumber_cpp::library void StepRegistry::Register(std::string id, const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) { - auto cucumberMatcher = (matcher.starts_with('^') || matcher.ends_with('$')) - ? cucumber_expression::Matcher{ - std::in_place_type, - matcher, - parameterRegistry, - } - : cucumber_expression::Matcher{ - std::in_place_type, - matcher, - parameterRegistry, - }; - auto cucumberMatcherType = std::holds_alternative(cucumberMatcher) - ? cucumber::messages::step_definition_pattern_type::REGULAR_EXPRESSION - : cucumber::messages::step_definition_pattern_type::CUCUMBER_EXPRESSION; - - registry.emplace_back(factory, - id, - sourceLocation.line(), - sourceLocation.file_name(), - stepType, - matcher, - cucumberMatcher, - cucumberMatcherType); - - idToDefinitionMap[id] = std::prev(registry.end()); + try + { + auto cucumberMatcher = (matcher.starts_with('^') || matcher.ends_with('$')) + ? cucumber_expression::Matcher{ + std::in_place_type, + matcher, + parameterRegistry, + } + : cucumber_expression::Matcher{ + std::in_place_type, + matcher, + parameterRegistry, + }; + auto cucumberMatcherType = std::holds_alternative(cucumberMatcher) + ? cucumber::messages::step_definition_pattern_type::REGULAR_EXPRESSION + : cucumber::messages::step_definition_pattern_type::CUCUMBER_EXPRESSION; + + registry.emplace_back(factory, + id, + sourceLocation.line(), + sourceLocation.file_name(), + stepType, + matcher, + cucumberMatcher, + cucumberMatcherType); + + idToDefinitionMap[id] = std::prev(registry.end()); + } + catch (const cucumber_expression::UndefinedParameterTypeError& e) + { + undefinedParameters.definitions.emplace_back( + std::string{ e.expression }, + std::string{ e.undefinedParameterName }); + } } StepStringRegistration& StepStringRegistration::Instance() diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index e5d25af3..81967499 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -12,6 +12,7 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include @@ -95,7 +96,7 @@ namespace cucumber_cpp::library const std::uint32_t& used; }; - explicit StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, cucumber::gherkin::id_generator_ptr idGenerator); + explicit StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, support::UndefinedParameters& undefinedParameters, cucumber::gherkin::id_generator_ptr idGenerator); void LoadSteps(); @@ -114,6 +115,7 @@ namespace cucumber_cpp::library void Register(std::string id, const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); cucumber_expression::ParameterRegistry& parameterRegistry; + support::UndefinedParameters& undefinedParameters; cucumber::gherkin::id_generator_ptr idGenerator; std::list registry; diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index f239b588..0f8ff875 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -15,6 +15,7 @@ #include "cucumber_cpp/library/runtime/MakeRuntime.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include @@ -51,6 +52,12 @@ namespace cucumber_cpp::library::api } } + void EmitUndefinedParameters(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster) + { + for (const auto& parameter : supportCodeLibrary.undefinedParameters.definitions) + broadcaster.BroadcastEvent({ .undefined_parameter_type = parameter }); + } + void EmitStepDefinitions(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { for (const auto& stepDefinition : supportCodeLibrary.stepRegistry.StepDefinitions()) @@ -100,11 +107,11 @@ namespace cucumber_cpp::library::api void EmitSupportCodeMessages(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { EmitParameters(supportCodeLibrary, broadcaster, idGenerator); - // undefined parameters support::DefinitionRegistration::Instance().LoadIds(idGenerator); - supportCodeLibrary.stepRegistry.LoadSteps(); + + EmitUndefinedParameters(supportCodeLibrary, broadcaster); EmitStepDefinitions(supportCodeLibrary, broadcaster, idGenerator); supportCodeLibrary.hookRegistry.LoadHooks(); @@ -117,10 +124,16 @@ namespace cucumber_cpp::library::api { cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); - StepRegistry stepRegistry{ parameterRegistry, idGenerator }; + support::UndefinedParameters undefinedParameters; + StepRegistry stepRegistry{ parameterRegistry, undefinedParameters, idGenerator }; HookRegistry hookRegistry{ idGenerator }; - support::SupportCodeLibrary supportCodeLibrary{ .hookRegistry = hookRegistry, .stepRegistry = stepRegistry, .parameterRegistry = parameterRegistry }; + support::SupportCodeLibrary supportCodeLibrary{ + .hookRegistry = hookRegistry, + .stepRegistry = stepRegistry, + .parameterRegistry = parameterRegistry, + .undefinedParameters = undefinedParameters, + }; formatter::helper::EventDataCollector eventDataCollector{ broadcaster }; formatter::PrettyPrinter prettyPrinter{ supportCodeLibrary, broadcaster, eventDataCollector }; diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 2cf09687..776fbe5d 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -61,7 +61,7 @@ namespace cucumber_cpp::library::assemble stepDefinitionIds.push_back(definition.id); auto& argumentList = stepMatchArgumentsLists.emplace_back(); for (const auto& result : *match) - argumentList.step_match_arguments.emplace_back(result.Group(), result.Name()); + argumentList.step_match_arguments.emplace_back(result.Group(), result.Name().empty() ? std::nullopt : std::make_optional(result.Name())); } } diff --git a/cucumber_cpp/library/cucumber_expression/Errors.cpp b/cucumber_cpp/library/cucumber_expression/Errors.cpp index d16500a7..197a0120 100644 --- a/cucumber_cpp/library/cucumber_expression/Errors.cpp +++ b/cucumber_cpp/library/cucumber_expression/Errors.cpp @@ -7,6 +7,7 @@ #include #include #include +#include namespace cucumber_cpp::library::cucumber_expression { @@ -155,7 +156,7 @@ For more complicated expressions consider using a regular expression instead.)", } {} - UndefinedParameterTypeError::UndefinedParameterTypeError(const Node& node, std::string_view expression, std::string_view undefinedParameterName) + UndefinedParameterTypeError::UndefinedParameterTypeError(const Node& node, std::string expression, std::string undefinedParameterName) : Error{ node.Start(), expression, @@ -163,5 +164,7 @@ For more complicated expressions consider using a regular expression instead.)", std::format(R"(Undefined parameter type '{}')", undefinedParameterName), std::format(R"(Please register a ParameterType for '{}')", undefinedParameterName), } + , expression{ std::move(expression) } + , undefinedParameterName{ std::move(undefinedParameterName) } {} } diff --git a/cucumber_cpp/library/cucumber_expression/Errors.hpp b/cucumber_cpp/library/cucumber_expression/Errors.hpp index 61e3d607..7ed9a217 100644 --- a/cucumber_cpp/library/cucumber_expression/Errors.hpp +++ b/cucumber_cpp/library/cucumber_expression/Errors.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include namespace cucumber_cpp::library::cucumber_expression @@ -90,7 +91,10 @@ namespace cucumber_cpp::library::cucumber_expression struct UndefinedParameterTypeError : Error { - UndefinedParameterTypeError(const Node& node, std::string_view expression, std::string_view undefinedParameterName); + UndefinedParameterTypeError(const Node& node, std::string expression, std::string undefinedParameterName); + + std::string expression; + std::string undefinedParameterName; }; struct InvalidTokenType diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index dbd7588b..d2203dfb 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -52,15 +52,12 @@ namespace cucumber_cpp::library::cucumber_expression const static std::string integerNegativeRegex{ R"__(-?\d+)__" }; const static std::string integerPositiveRegex{ R"__(\d+)__" }; const static std::string floatRegex{ R"__((?=.*\d.*)[-+]?\d*(?:\.(?=\d.*))?\d*(?:\d+[E][+-]?\d+)?)__" }; - // const static std::string stringDoubleRegex{ R"__("([^\"\\]*(\\.[^\"\\]*)*)")__" }; - // const static std::string stringSingleRegex{ R"__('([^'\\]*(\\.[^'\\]*)*)')__" }; - const static std::string stringRegex{ R"__("([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)')__" }; + const static std::string stringRegex{ R"__("([^"\\]*(\\.[^"\\]*)*)"|'([^'\\]*(\\.[^'\\]*)*)')__" }; const static std::string wordRegex{ R"__([^\s]+)__" }; AddBuiltinParameter("int", { integerNegativeRegex, integerPositiveRegex }, CreateStreamConverter()); AddBuiltinParameter("float", { floatRegex }, CreateStreamConverter()); AddBuiltinParameter("word", { wordRegex }, CreateStreamConverter()); - // AddBuiltinParameter("string", { stringDoubleRegex, stringSingleRegex }, CreateStringConverter()); AddBuiltinParameter("string", { stringRegex }, CreateStringConverter()); AddBuiltinParameter("", { ".*" }, CreateStreamConverter()); AddBuiltinParameter("bigdecimal", { floatRegex }, CreateStreamConverter()); diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp index f54db45a..5b3a11f1 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp @@ -75,7 +75,7 @@ namespace cucumber_cpp::library::cucumber_expression return { .children = std::vector(children.begin(), children.end()), - // .start = value.first - match.prefix().first, + .start = match[groupIndex].matched ? std::make_optional(match.position(groupIndex)) : std::nullopt, .value = value, }; } diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp index caa2a069..11be8f08 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.hpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -6,6 +6,7 @@ #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include #include #include @@ -71,6 +72,7 @@ namespace cucumber_cpp::library::support HookRegistry& hookRegistry; StepRegistry& stepRegistry; cucumber_expression::ParameterRegistry& parameterRegistry; + UndefinedParameters& undefinedParameters; }; using Entry = std::variant; diff --git a/cucumber_cpp/library/support/UndefinedParameters.hpp b/cucumber_cpp/library/support/UndefinedParameters.hpp new file mode 100644 index 00000000..77f33859 --- /dev/null +++ b/cucumber_cpp/library/support/UndefinedParameters.hpp @@ -0,0 +1,15 @@ +#ifndef SUPPORT_UNDEFINED_PARAMETERS_HPP +#define SUPPORT_UNDEFINED_PARAMETERS_HPP + +#include "cucumber/messages/undefined_parameter_type.hpp" +#include + +namespace cucumber_cpp::library::support +{ + struct UndefinedParameters + { + std::list definitions; + }; +} + +#endif From 5893f6285fe2360f579588bdfb5f808dd86f3ee2 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 00:27:03 +0000 Subject: [PATCH 035/196] fixed build issues and solved sonarcloud warnings --- cucumber_cpp/example/hooks/Hooks.cpp | 13 +++- cucumber_cpp/example/steps/Steps.cpp | 9 +-- cucumber_cpp/library/Body.cpp | 8 +-- cucumber_cpp/library/BodyMacro.hpp | 1 - cucumber_cpp/library/HookRegistry.cpp | 20 ++---- cucumber_cpp/library/HookRegistry.hpp | 4 +- cucumber_cpp/library/Hooks.hpp | 1 - cucumber_cpp/library/Query.cpp | 37 +++++----- cucumber_cpp/library/Query.hpp | 67 ++++++++++--------- cucumber_cpp/library/StepRegistry.cpp | 8 +-- cucumber_cpp/library/StepRegistry.hpp | 9 +-- .../test/TestTreeRegexp.cpp | 5 +- .../formatter/helper/SummaryHelpers.cpp | 2 +- 13 files changed, 94 insertions(+), 90 deletions(-) diff --git a/cucumber_cpp/example/hooks/Hooks.cpp b/cucumber_cpp/example/hooks/Hooks.cpp index 5aae64b5..a11c4856 100644 --- a/cucumber_cpp/example/hooks/Hooks.cpp +++ b/cucumber_cpp/example/hooks/Hooks.cpp @@ -12,33 +12,42 @@ HOOK_BEFORE_ALL() context.Emplace(); } -HOOK_BEFORE_ALL(.name = "Initialize something") // does NOT have a .tagExpression -{} +HOOK_BEFORE_ALL(.name = "Initialize something") +{ + /* no body, example only */ +} HOOK_BEFORE_SCENARIO(.name = "explicit name only") { + /* no body, example only */ } HOOK_BEFORE_SCENARIO("@dingus", "name", 10) { + /* no body, example only */ } HOOK_BEFORE_SCENARIO("@result:OK") { + /* no body, example only */ } HOOK_BEFORE_SCENARIO() { + /* no body, example only */ } HOOK_AFTER_SCENARIO() { + /* no body, example only */ } HOOK_BEFORE_STEP() { + /* no body, example only */ } HOOK_AFTER_STEP() { + /* no body, example only */ } diff --git a/cucumber_cpp/example/steps/Steps.cpp b/cucumber_cpp/example/steps/Steps.cpp index 36135f9f..f4db211a 100644 --- a/cucumber_cpp/example/steps/Steps.cpp +++ b/cucumber_cpp/example/steps/Steps.cpp @@ -8,15 +8,16 @@ GIVEN(R"(a background step)") { + /* no body, example only */ } GIVEN(R"(a simple data table)") { - // std::cout << "row0.col0: " << table[0][0].As() << "\n"; - // std::cout << "row0.col1: " << table[0][1].As() << "\n"; + [[maybe_unused]] const auto row0col0 = table.value()[0].cells[0].value; + [[maybe_unused]] const auto row0col1 = table.value()[0].cells[1].value; - // std::cout << "row1.col0: " << table[1][0].As() << "\n"; - // std::cout << "row1.col1: " << table[1][1].As() << "\n"; + [[maybe_unused]] const auto row1col0 = table.value()[1].cells[0].value; + [[maybe_unused]] const auto row1col1 = table.value()[1].cells[1].value; } GIVEN(R"(there are {int} cucumbers)", (std::int32_t num)) diff --git a/cucumber_cpp/library/Body.cpp b/cucumber_cpp/library/Body.cpp index 279417bd..14153f06 100644 --- a/cucumber_cpp/library/Body.cpp +++ b/cucumber_cpp/library/Body.cpp @@ -27,7 +27,7 @@ namespace cucumber_cpp::library { } - void ReportTestPartResult(const testing::TestPartResult& testPartResult) + void ReportTestPartResult(const testing::TestPartResult& testPartResult) override { if (testPartResult.failed()) { @@ -71,7 +71,7 @@ namespace cucumber_cpp::library if (!e.message.empty()) testStepResult.message = e.message; } - catch (const FatalError& error) + catch ([[maybe_unused]] const FatalError& error) { testStepResult.status = cucumber::messages::test_step_result_status::FAILED; } @@ -95,8 +95,8 @@ namespace cucumber_cpp::library auto nanoseconds = support::Stopwatch::Instance().Duration(); static constexpr std::size_t nanosecondsPerSecond = 1e9; testStepResult.duration = { - .seconds = static_cast(nanoseconds.count() / static_cast(nanosecondsPerSecond)), - .nanos = static_cast(nanoseconds.count() % static_cast(nanosecondsPerSecond)), + .seconds = nanoseconds.count() / nanosecondsPerSecond, + .nanos = nanoseconds.count() % nanosecondsPerSecond, }; return testStepResult; diff --git a/cucumber_cpp/library/BodyMacro.hpp b/cucumber_cpp/library/BodyMacro.hpp index 0b726ff1..3e2afcef 100644 --- a/cucumber_cpp/library/BodyMacro.hpp +++ b/cucumber_cpp/library/BodyMacro.hpp @@ -36,7 +36,6 @@ T TransformArg(const cucumber::messages::step_match_argument& match) void Execute(const cucumber::messages::step_match_arguments_list& args) override \ { \ cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ - /* ASSERT_NO_THROW(ExecuteWithArgs(args, static_cast(nullptr))); */ \ ExecuteWithArgs(args, static_cast(nullptr)); \ } \ \ diff --git a/cucumber_cpp/library/HookRegistry.cpp b/cucumber_cpp/library/HookRegistry.cpp index dae116da..9d809e38 100644 --- a/cucumber_cpp/library/HookRegistry.cpp +++ b/cucumber_cpp/library/HookRegistry.cpp @@ -64,7 +64,7 @@ namespace cucumber_cpp::library } HookBase::HookBase(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted) - : engine::ExecutionContext{ broadCaster, context, stepOrHookStarted } + : engine::ExecutionContext{ broadCaster, context, std::move(stepOrHookStarted) } {} HookRegistry::Definition::Definition(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation) @@ -72,7 +72,7 @@ namespace cucumber_cpp::library , tagExpression{ tag_expression::Parse(expression.value_or("")) } , factory{ factory } , hook{ - .id = id, + .id = std::move(id), .name = name.has_value() ? std::make_optional(name.value()) : std::nullopt, .source_reference = cucumber::messages::source_reference{ .uri = sourceLocation.file_name(), @@ -118,28 +118,18 @@ namespace cucumber_cpp::library return std::ranges::count(registry | std::views::values, hookType, &Definition::type); } - HookFactory HookRegistry::GetFactoryById(std::string id) const + HookFactory HookRegistry::GetFactoryById(const std::string& id) const { return registry.at(id).factory; } - const HookRegistry::Definition& HookRegistry::GetDefinitionById(std::string id) const + const HookRegistry::Definition& HookRegistry::GetDefinitionById(const std::string& id) const { return registry.at(id); } void HookRegistry::Register(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation) { - registry.emplace(id, Definition{ id, type, expression, name, factory, sourceLocation }); + registry.try_emplace(id, Definition{ id, type, expression, name, factory, sourceLocation }); } - - // std::span HookRegistration::GetEntries() - // { - // return registry; - // } - - // std::span HookRegistration::GetEntries() const - // { - // return registry; - // } } diff --git a/cucumber_cpp/library/HookRegistry.hpp b/cucumber_cpp/library/HookRegistry.hpp index 346f1799..3a017c30 100644 --- a/cucumber_cpp/library/HookRegistry.hpp +++ b/cucumber_cpp/library/HookRegistry.hpp @@ -108,8 +108,8 @@ namespace cucumber_cpp::library [[nodiscard]] std::size_t Size() const; [[nodiscard]] std::size_t Size(HookType hookType) const; - HookFactory GetFactoryById(std::string id) const; - const Definition& GetDefinitionById(std::string id) const; + HookFactory GetFactoryById(const std::string& id) const; + const Definition& GetDefinitionById(const std::string& id) const; private: void Register(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation); diff --git a/cucumber_cpp/library/Hooks.hpp b/cucumber_cpp/library/Hooks.hpp index b29531c2..03f4d66e 100644 --- a/cucumber_cpp/library/Hooks.hpp +++ b/cucumber_cpp/library/Hooks.hpp @@ -8,7 +8,6 @@ #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -// #define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::HookRegistration::Register, cucumber_cpp::library::HookBase) #define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::support::DefinitionRegistration::Register, cucumber_cpp::library::HookBase) #define HOOK_BEFORE_ALL(...) \ diff --git a/cucumber_cpp/library/Query.cpp b/cucumber_cpp/library/Query.cpp index 2afd084a..b2e81d66 100644 --- a/cucumber_cpp/library/Query.cpp +++ b/cucumber_cpp/library/Query.cpp @@ -30,6 +30,7 @@ #include "cucumber/messages/undefined_parameter_type.hpp" #include #include +#include #include #include #include @@ -163,7 +164,7 @@ namespace cucumber_cpp::library return lineageById.at(pickle.ast_node_ids[0]); } - const Lineage& Query::FindLineageByUri(std::string uri) const + const Lineage& Query::FindLineageByUri(const std::string& uri) const { return lineageByUri.at(uri); } @@ -238,12 +239,12 @@ namespace cucumber_cpp::library return lineage.scenario->location; } - const std::map& Query::TestCaseStarted() const + const std::map>& Query::TestCaseStarted() const { return testCaseStartedById; } - const std::map& Query::TestCaseFinishedByTestCaseStartedId() const + const std::map>& Query::TestCaseFinishedByTestCaseStartedId() const { return testCaseFinishedByTestCaseStartedId; } @@ -257,19 +258,19 @@ namespace cucumber_cpp::library void Query::operator+=(const cucumber::messages::pickle& pickle) { - pickleById.emplace(pickle.id, pickle); + pickleById.try_emplace(pickle.id, pickle); for (const auto& pickleStep : pickle.steps) - pickleStepById.emplace(pickleStep.id, pickleStep); + pickleStepById.try_emplace(pickleStep.id, pickleStep); } void Query::operator+=(const cucumber::messages::hook& hook) { - hooksById.emplace(hook.id, hook); + hooksById.try_emplace(hook.id, hook); } void Query::operator+=(const cucumber::messages::step_definition& stepDefinition) { - stepDefinitionById.emplace(stepDefinition.id, stepDefinition); + stepDefinitionById.try_emplace(stepDefinition.id, stepDefinition); } void Query::operator+=(const cucumber::messages::test_run_started& testRunStarted) @@ -279,23 +280,23 @@ namespace cucumber_cpp::library void Query::operator+=(const cucumber::messages::test_run_hook_started& testRunHookStarted) { - testRunHookStartedById.emplace(testRunHookStarted.id, testRunHookStarted); + testRunHookStartedById.try_emplace(testRunHookStarted.id, testRunHookStarted); } void Query::operator+=(const cucumber::messages::test_run_hook_finished& testRunHookFinished) { - testRunHookFinishedByTestRunHookStartedId.emplace(testRunHookFinished.test_run_hook_started_id, testRunHookFinished); + testRunHookFinishedByTestRunHookStartedId.try_emplace(testRunHookFinished.test_run_hook_started_id, testRunHookFinished); } void Query::operator+=(const cucumber::messages::test_case& testCase) { - auto& testCaseRef = testCaseById.emplace(testCase.id, testCase).first->second; - testCaseByPickleId.emplace(testCase.pickle_id, testCaseRef); + auto& testCaseRef = testCaseById.try_emplace(testCase.id, testCase).first->second; + testCaseByPickleId.try_emplace(testCase.pickle_id, testCaseRef); for (const auto& testStep : testCase.test_steps) { - testStepById.emplace(testStep.id, testStep); - pickleIdByTestStepId.emplace(testStep.id, testCase.pickle_id); + testStepById.try_emplace(testStep.id, testStep); + pickleIdByTestStepId.try_emplace(testStep.id, testCase.pickle_id); if (testStep.pickle_step_id) { @@ -310,7 +311,7 @@ namespace cucumber_cpp::library void Query::operator+=(const cucumber::messages::test_case_started& testCaseStarted) { - testCaseStartedById.emplace(testCaseStarted.id, testCaseStarted); + testCaseStartedById.try_emplace(testCaseStarted.id, testCaseStarted); /* reset data? https://github.dev/cucumber/query/blob/f31732e5972c1815614f1d83928a7065e3080dc4/javascript/src/Query.ts#L249 */ } @@ -346,7 +347,7 @@ namespace cucumber_cpp::library void Query::operator+=(const cucumber::messages::test_case_finished& testCaseFinished) { - testCaseFinishedByTestCaseStartedId.emplace(testCaseFinished.test_case_started_id, testCaseFinished); + testCaseFinishedByTestCaseStartedId.try_emplace(testCaseFinished.test_case_started_id, testCaseFinished); } void Query::operator+=(const cucumber::messages::test_run_finished& testRunFinished) @@ -429,12 +430,12 @@ namespace cucumber_cpp::library void Query::operator+=(std::span steps) { for (const auto& step : steps) - stepById.emplace(step.id, step); + stepById.try_emplace(step.id, step); } void Query::operator+=(const cucumber::messages::parameter_type& parameterType) { - auto& ref = parameterTypeById.emplace(parameterType.id, parameterType).first->second; - parameterTypeByName.emplace(parameterType.name, ref); + auto& ref = parameterTypeById.try_emplace(parameterType.id, parameterType).first->second; + parameterTypeByName.try_emplace(parameterType.name, ref); } } diff --git a/cucumber_cpp/library/Query.hpp b/cucumber_cpp/library/Query.hpp index 9202fec7..c0014634 100644 --- a/cucumber_cpp/library/Query.hpp +++ b/cucumber_cpp/library/Query.hpp @@ -33,6 +33,7 @@ #include "cucumber/messages/undefined_parameter_type.hpp" #include #include +#include #include #include #include @@ -77,7 +78,7 @@ namespace cucumber_cpp::library } const Lineage& FindLineageByPickle(const cucumber::messages::pickle& pickle) const; - const Lineage& FindLineageByUri(std::string) const; + const Lineage& FindLineageByUri(const std::string&) const; const cucumber::messages::parameter_type& FindParameterTypeById(const std::string& id) const; const cucumber::messages::parameter_type& FindParameterTypeByName(const std::string& name) const; @@ -101,8 +102,8 @@ namespace cucumber_cpp::library const cucumber::messages::location& FindLocationOf(const cucumber::messages::pickle& pickle) const; - const std::map& TestCaseStarted() const; - const std::map& TestCaseFinishedByTestCaseStartedId() const; + const std::map>& TestCaseStarted() const; + const std::map>& TestCaseFinishedByTestCaseStartedId() const; private: void @@ -130,52 +131,52 @@ namespace cucumber_cpp::library void operator+=(const cucumber::messages::parameter_type& parameterType); - std::map featureCountByName; + std::map> featureCountByName; std::forward_list testStepResults; - std::map> testStepResultByPickleId; - std::map> testStepResultsByPickleStepId; - std::map> testStepResultsbyTestStepId; + std::map, std::less<>> testStepResultByPickleId; + std::map, std::less<>> testStepResultsByPickleStepId; + std::map, std::less<>> testStepResultsbyTestStepId; - std::map testCaseById; - std::map testCaseByPickleId; + std::map> testCaseById; + std::map> testCaseByPickleId; - std::map pickleIdByTestStepId; - std::map pickleStepIdByTestStepId; - std::map> testStepIdsByPickleStepId; - std::map hooksById; + std::map> pickleIdByTestStepId; + std::map> pickleStepIdByTestStepId; + std::map, std::less<>> testStepIdsByPickleStepId; + std::map> hooksById; std::forward_list attachments; - std::map> attachmentsByTestStepId; - std::map> attachmentsByTestCaseStartedId; - // std::map>> attachmentsByTestRunHookStartedId; + std::map, std::less<>> attachmentsByTestStepId; + std::map, std::less<>> attachmentsByTestCaseStartedId; + // std::map>, std::less<>> attachmentsByTestRunHookStartedId; - std::map> stepMatchArgumentsListsByPickleStepId; + std::map, std::less<>> stepMatchArgumentsListsByPickleStepId; std::unique_ptr meta; std::unique_ptr testRunStarted; std::unique_ptr testRunFinished; - std::map testCaseStartedById; - std::map testCaseFinishedByTestCaseStartedId; + std::map> testCaseStartedById; + std::map> testCaseFinishedByTestCaseStartedId; - std::map lineageById; - std::map lineageByUri; + std::map> lineageById; + std::map> lineageByUri; - std::map stepById; - std::map pickleById; - std::map pickleStepById; - std::map stepDefinitionById; - std::map testStepById; - std::map testRunHookStartedById; - std::map testRunHookFinishedByTestRunHookStartedId; - std::map> testStepStartedByTestCaseStartedId; - std::map> testStepFinishedByTestCaseStartedId; + std::map> stepById; + std::map> pickleById; + std::map> pickleStepById; + std::map> stepDefinitionById; + std::map> testStepById; + std::map> testRunHookStartedById; + std::map> testRunHookFinishedByTestRunHookStartedId; + std::map, std::less<>> testStepStartedByTestCaseStartedId; + std::map, std::less<>> testStepFinishedByTestCaseStartedId; - std::map> suggestionsByPickleStepId; + std::map, std::less<>> suggestionsByPickleStepId; std::forward_list undefinedParameterTypes; - std::map parameterTypeById; - std::map parameterTypeByName; + std::map> parameterTypeById; + std::map> parameterTypeByName; }; } diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index 3b248cce..1a44fa6c 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -38,13 +38,13 @@ namespace cucumber_cpp::library Register(matcher.id, matcher.regex, matcher.type, matcher.factory, matcher.sourceLocation); } - [[nodiscard]] std::pair, std::vector>> StepRegistry::FindDefinitions(const std::string& expression) + [[nodiscard]] std::pair, std::vector>> StepRegistry::FindDefinitions(const std::string& expression) const { std::pair, std::vector>> result; result.first.reserve(idToDefinitionMap.size()); result.second.reserve(idToDefinitionMap.size()); - for (auto& [id, iter] : idToDefinitionMap) + for (const auto& [id, iter] : idToDefinitionMap) { const auto match = std::visit(cucumber_expression::MatchVisitor{ expression }, iter->regex); if (match) @@ -74,12 +74,12 @@ namespace cucumber_cpp::library return list; } - StepFactory StepRegistry::GetFactoryById(std::string id) const + StepFactory StepRegistry::GetFactoryById(const std::string& id) const { return idToDefinitionMap.at(id)->factory; } - StepRegistry::Definition StepRegistry::GetDefinitionById(std::string id) const + StepRegistry::Definition StepRegistry::GetDefinitionById(const std::string& id) const { return *idToDefinitionMap.at(id); } diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index 81967499..81089b51 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -100,14 +101,14 @@ namespace cucumber_cpp::library void LoadSteps(); - [[nodiscard]] std::pair, std::vector>> FindDefinitions(const std::string& expression); + [[nodiscard]] std::pair, std::vector>> FindDefinitions(const std::string& expression) const; [[nodiscard]] std::size_t Size() const; [[nodiscard]] std::vector List() const; - StepFactory GetFactoryById(std::string id) const; - Definition GetDefinitionById(std::string id) const; + StepFactory GetFactoryById(const std::string& id) const; + Definition GetDefinitionById(const std::string& id) const; const std::list& StepDefinitions() const; @@ -119,7 +120,7 @@ namespace cucumber_cpp::library cucumber::gherkin::id_generator_ptr idGenerator; std::list registry; - std::map::iterator> idToDefinitionMap; + std::map::iterator, std::less<>> idToDefinitionMap; }; struct StepStringRegistration diff --git a/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp index fb395c50..ade690e5 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp @@ -53,7 +53,10 @@ namespace cucumber_cpp::library::cucumber_expression TEST(TestTreeRegexp, ignores_positive_lookahead_as_a_non_capturing_group) { TreeRegexp treeRegexp{ R"__(a(?=[b])(.+))__" }; - const auto group = *treeRegexp.MatchToGroup("ac"); + const auto group = *treeRegexp.MatchToGroup("abc"); + EXPECT_THAT(group.value.value(), testing::StrEq("abc")); + EXPECT_THAT(group.children.size(), 1); + EXPECT_THAT(group.children[0].value.value(), testing::StrEq("bc")); } TEST(TestTreeRegexp, DISABLED_ignores_positive_lookbehind_as_a_non_capturing_group) diff --git a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp index c64de752..64dca5af 100644 --- a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp +++ b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp @@ -31,7 +31,7 @@ namespace cucumber_cpp::library::formatter::helper ++counts[result.status]; auto values = counts | std::views::values; - const auto total = std::accumulate(values.begin(), values.end(), 0u); + const auto total = std::accumulate(values.begin(), values.end(), std::size_t{ 0u }); std::string text = std::format("{} {}{}", total, type, (total == 1 ? "" : "s")); From d75b7634b7c78ec413b12c66cfd5e6babeb01a04 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 09:16:07 +0000 Subject: [PATCH 036/196] make URI cross-OS compatible --- compatibility/compatibility.cpp | 53 ++++++--------------------------- 1 file changed, 9 insertions(+), 44 deletions(-) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 2e76516f..e69b5b99 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -58,7 +58,7 @@ namespace compatibility }; } - void SanitizeExpectedJson(nlohmann::json& json) + void RemoveIncompatibilities(nlohmann::json& json) { for (auto jsonIter = json.begin(); jsonIter != json.end();) { @@ -75,7 +75,7 @@ namespace compatibility jsonIter = json.erase(jsonIter); else if (value.is_object()) { - SanitizeExpectedJson(value); + RemoveIncompatibilities(value); ++jsonIter; } else if (value.is_array()) @@ -86,7 +86,7 @@ namespace compatibility auto& item = *valueIter; if (item.is_object()) - SanitizeExpectedJson(item); + RemoveIncompatibilities(item); ++valueIter; } @@ -95,47 +95,12 @@ namespace compatibility } else if (key == "uri") { - json[key] = std::regex_replace(value.get(), std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); - json[key] = std::regex_replace(value.get(), std::regex(R"(\.ts$)"), ".cpp"); - ++jsonIter; - } - else - ++jsonIter; - } - } - - void SanitizeActualJson(nlohmann::json& json) - { - for (auto jsonIter = json.begin(); jsonIter != json.end();) - { - auto& key = jsonIter.key(); - auto& value = jsonIter.value(); - - if (key == "exception") - jsonIter = json.erase(jsonIter); - else if (key == "message") - jsonIter = json.erase(jsonIter); - else if (key == "line") - jsonIter = json.erase(jsonIter); - else if (key == "snippets") - jsonIter = json.erase(jsonIter); - else if (value.is_object()) - { - SanitizeActualJson(value); - ++jsonIter; - } - else if (value.is_array()) - { - auto idx = 0; - for (auto valueIter = value.begin(); valueIter != value.end();) - { - auto& item = *valueIter; + auto uri = value.get(); - if (item.is_object()) - SanitizeActualJson(item); + uri = std::regex_replace(uri, std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); + uri = std::regex_replace(uri, std::regex(R"(\.ts$)"), ".cpp"); - ++valueIter; - } + json[key] = std::filesystem::path{ uri }.string(); ++jsonIter; } @@ -186,13 +151,13 @@ namespace compatibility for (auto& json : actualEnvelopes) { - SanitizeActualJson(json); + RemoveIncompatibilities(json); actualOfs << json.dump() << "\n"; } for (auto& json : expectedEnvelopes) { - SanitizeExpectedJson(json); + RemoveIncompatibilities(json); expectedOfs << json.dump() << "\n"; } From 917debc2d384e5c0e03a79f407c4123cd63951ff Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 09:51:58 +0000 Subject: [PATCH 037/196] remove compatibility kit from ls-lint --- .ls-lint.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.ls-lint.yml b/.ls-lint.yml index a40b0114..65d82f84 100644 --- a/.ls-lint.yml +++ b/.ls-lint.yml @@ -12,3 +12,4 @@ ignore: - external - megalinter-reports - testdata + - compatibility From 5815b57dff579235313d47909253c8e10efda623 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 09:52:12 +0000 Subject: [PATCH 038/196] fix possible null-pointer dereference --- .../helper/TestCaseAttemptParser.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp index 24198875..a05bf819 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp @@ -1,8 +1,6 @@ #include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/pickle_step_argument.hpp" -#include "cucumber/messages/step_keyword_type.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" @@ -12,6 +10,7 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include #include +#include #include #include #include @@ -26,14 +25,13 @@ namespace cucumber_cpp::library::formatter::helper const GherkinStepMap& gherkinStepMap, std::string_view keyword, KeywordType keywordType, - const cucumber::messages::pickle_step& pickleStep, + const std::map& pickleStepMap, std::filesystem::path pickleUri, support::SupportCodeLibrary supportCode, const cucumber::messages::test_step& testStep, const cucumber::messages::test_step_result& testStepResult, std::span attachments) { - using std::operator""sv; ParsedTestStep parsedTestStep{ .attachments = attachments, .keyword = std::string{ testStep.pickle_step_id ? keyword : (isBeforeHook ? "Before" : "After") }, @@ -61,6 +59,8 @@ namespace cucumber_cpp::library::formatter::helper if (testStep.pickle_step_id) { + const auto& pickleStep = pickleStepMap.at(*testStep.pickle_step_id); + parsedTestStep.location = { .uri = pickleUri.string(), .line = gherkinStepMap.at(pickleStep.ast_node_ids.front()).location.line @@ -109,13 +109,12 @@ namespace cucumber_cpp::library::formatter::helper const auto& testStepResult = testCaseAttempt.stepResults.at(testStep.id); isBeforeHook = isBeforeHook && testStep.hook_id.has_value(); - std::string_view keyword; - KeywordType keywordType; - const cucumber::messages::pickle_step* pickleStep{ nullptr }; + std::string_view keyword{}; + KeywordType keywordType{}; if (testStep.pickle_step_id) { - pickleStep = &pickleStepMap.at(*testStep.pickle_step_id); - keyword = GetStepKeyword(*pickleStep, gherkinStepMap); + const auto& pickleStep = pickleStepMap.at(*testStep.pickle_step_id); + keyword = GetStepKeyword(pickleStep, gherkinStepMap); keywordType = GetStepKeywordType(keyword, gherkinDocument.feature->language, previousKeyWordType); } @@ -123,7 +122,7 @@ namespace cucumber_cpp::library::formatter::helper gherkinStepMap, keyword, keywordType, - *pickleStep, + pickleStepMap, relativePickleUri, supportCodeLibrary, testStep, From 9986ec84cebd260f438d843b7533c9e5041cc380 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 09:52:28 +0000 Subject: [PATCH 039/196] make uri's canonical --- compatibility/compatibility.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index e69b5b99..b8c38435 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -100,7 +100,7 @@ namespace compatibility uri = std::regex_replace(uri, std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); uri = std::regex_replace(uri, std::regex(R"(\.ts$)"), ".cpp"); - json[key] = std::filesystem::path{ uri }.string(); + json[key] = std::filesystem::canonical(uri).string(); ++jsonIter; } From c66a111d95ac9d5066c1aa262c4111d9e819b595 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 09:53:42 +0000 Subject: [PATCH 040/196] fixed const instead of static --- cucumber_cpp/library/support/SupportCodeLibrary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index c1f3e6c2..d7e49bd2 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -55,7 +55,7 @@ namespace cucumber_cpp::library::support void DefinitionRegistration::LoadIds(cucumber::gherkin::id_generator_ptr idGenerator) { - static auto assignGenerator = [&idGenerator](auto& entry) + const auto assignGenerator = [&idGenerator](auto& entry) { entry.id = idGenerator->next_id(); }; From 3e626e7f359ecde25bfcd543d4aaa2b2ab312a02 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 10:20:58 +0000 Subject: [PATCH 041/196] fix sonar warnings and replace \r\n with \n --- compatibility/compatibility.cpp | 4 ++ .../library/assemble/AssembleTestSuites.cpp | 53 ++++++++++++------- .../cucumber_expression/TreeRegexp.hpp | 2 +- .../library/formatter/PrettyPrinter.cpp | 10 ++-- .../formatter/helper/SummaryHelpers.cpp | 13 +++-- .../library/runtime/TestCaseRunner.cpp | 2 +- cucumber_cpp/library/util/Broadcaster.hpp | 6 +++ 7 files changed, 62 insertions(+), 28 deletions(-) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index b8c38435..72992614 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -93,6 +93,10 @@ namespace compatibility ++jsonIter; } + else if (key == "data") + { + json[key] = std::regex_replace(json[key].get(), std::regex(R"(\r\n)"), "\n"); + } else if (key == "uri") { auto uri = value.get(); diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 776fbe5d..5e3e9221 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -4,7 +4,9 @@ #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_case.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" @@ -22,13 +24,30 @@ namespace cucumber_cpp::library::assemble { + namespace + { + auto TransformToMatch(const std::string& text) + { + return [&text](const StepRegistry::Definition& definition) -> std::pair>> + { + const auto match = std::visit(cucumber_expression::MatchVisitor{ text }, definition.regex); + return { definition.id, match }; + }; + } + + bool HasMatch(const std::pair>>& pair) + { + return pair.second.has_value(); + } + } + std::vector AssembleTestSuites(support::SupportCodeLibrary supportCodeLibrary, std::string_view testRunStartedId, util::Broadcaster& broadcaster, const std::list& sourcedPickles, cucumber::gherkin::id_generator_ptr idGenerator) { - std::vector testUris; + std::list testUris; std::map assembledTestSuiteMap; for (const auto& pickleSource : sourcedPickles) @@ -47,30 +66,24 @@ namespace cucumber_cpp::library::assemble for (const auto& step : pickleSource.pickle->steps) { - // auto definitions = supportCodeLibrary.stepRegistry.FindDefinitions(step.text); const auto& stepDefinitions = supportCodeLibrary.stepRegistry.StepDefinitions(); - std::vector stepDefinitionIds; - std::vector stepMatchArgumentsLists; - - for (const auto& definition : stepDefinitions) - { - const auto match = std::visit(cucumber_expression::MatchVisitor{ step.text }, definition.regex); - if (match) - { - stepDefinitionIds.push_back(definition.id); - auto& argumentList = stepMatchArgumentsLists.emplace_back(); - for (const auto& result : *match) - argumentList.step_match_arguments.emplace_back(result.Group(), result.Name().empty() ? std::nullopt : std::make_optional(result.Name())); - } - } - - testCase.test_steps.emplace_back( + auto& testStep = testCase.test_steps.emplace_back( std::nullopt, idGenerator->next_id(), step.id, - stepDefinitionIds, - stepMatchArgumentsLists); + std::vector{}, + std::vector{}); + + for (const auto& [id, match] : stepDefinitions | + std::views::transform(TransformToMatch(step.text)) | + std::views::filter(HasMatch)) + { + testStep.step_definition_ids.value().push_back(id); + auto& argumentList = testStep.step_match_arguments_lists.value().emplace_back(); + for (const auto& result : *match) + argumentList.step_match_arguments.emplace_back(result.Group(), result.Name().empty() ? std::nullopt : std::make_optional(result.Name())); + } } for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags) | std::views::reverse) diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp index ae4305ef..d5768f82 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp @@ -35,7 +35,7 @@ namespace cucumber_cpp::library::cucumber_expression struct TreeRegexp { - TreeRegexp(std::string_view pattern); + explicit TreeRegexp(std::string_view pattern); const GroupBuilder& RootBuilder() const; diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyPrinter.cpp index 7c05b2d3..8425e605 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.cpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.cpp @@ -113,7 +113,9 @@ namespace cucumber_cpp::library::formatter } void PrettyPrinter::HandleAttachment(const cucumber::messages::attachment& attachment) - {} + { + /* TODO implement */ + } void PrettyPrinter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) { @@ -133,7 +135,9 @@ namespace cucumber_cpp::library::formatter } void PrettyPrinter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) - {} + { + /* TODO implement */ + } void PrettyPrinter::PrintFeatureLine(const cucumber::messages::feature& feature) { @@ -187,7 +191,7 @@ namespace cucumber_cpp::library::formatter } const auto padding = maxContentLength - title.length(); - if (uri && line) + if (uri.has_value() && line.has_value()) support::print(outputStream, "{:{}}{}{:{}} {}\n", "", indent, formatTitle ? formatTitle(title) : std::string(title), "", padding, ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); else support::print(outputStream, "{:{}}{}\n", "", indent, formatTitle ? formatTitle(title) : std::string(title)); diff --git a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp index 64dca5af..009b654d 100644 --- a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp +++ b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp" #include "cucumber/messages/duration.hpp" +#include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" @@ -59,6 +60,11 @@ namespace cucumber_cpp::library::formatter::helper const auto total = support::DurationToMilliseconds(duration); return std::format("{:%Mm %S}s", total); } + + bool HasPickleStepId(const cucumber::messages::test_step& testStep) + { + return testStep.pickle_step_id.has_value(); + } } std::string FormatSummary(std::span testCaseAttempts, cucumber::messages::duration testRunDuration) @@ -75,9 +81,10 @@ namespace cucumber_cpp::library::formatter::helper if (!testCaseAttempt.willBeRetried) { testCaseResults.emplace_back(testCaseAttempt.worstTestStepResult); - for (const auto& testStep : testCaseAttempt.testCase.test_steps) - if (testStep.pickle_step_id) - testStepResults.emplace_back(testCaseAttempt.stepResults.at(testStep.id)); + + for (const auto& testStep : testCaseAttempt.testCase.test_steps | + std::views::filter(HasPickleStepId)) + testStepResults.emplace_back(testCaseAttempt.stepResults.at(testStep.id)); } } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index 5fb27146..ae7971a3 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -181,7 +181,7 @@ namespace cucumber_cpp::library::runtime cucumber::messages::test_step_result TestCaseRunner::RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted) { - auto stepDefinitions = (*testStep.step_definition_ids) | std::views::transform([&](const std::string& id) + auto stepDefinitions = (*testStep.step_definition_ids) | std::views::transform([this](const std::string& id) { return supportCodeLibrary.stepRegistry.GetDefinitionById(id); }); diff --git a/cucumber_cpp/library/util/Broadcaster.hpp b/cucumber_cpp/library/util/Broadcaster.hpp index 56090388..b16ea207 100644 --- a/cucumber_cpp/library/util/Broadcaster.hpp +++ b/cucumber_cpp/library/util/Broadcaster.hpp @@ -12,6 +12,12 @@ namespace cucumber_cpp::library::util struct Listener { explicit Listener(Broadcaster& broadcaster, const std::function& onEvent); + + Listener(const Listener&) = delete; + Listener& operator=(const Listener&) = delete; + Listener(Listener&&) = delete; + Listener& operator=(Listener&&) = delete; + ~Listener(); void Invoke(const cucumber::messages::envelope& envelope) const; From 5c23d801b0f707a684d07a913167e67225b2f268 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 14:28:10 +0000 Subject: [PATCH 042/196] refactor RemoveIncompatibilities to improve iteration and const correctness --- compatibility/compatibility.cpp | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 72992614..6658ac6e 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -60,9 +60,10 @@ namespace compatibility void RemoveIncompatibilities(nlohmann::json& json) { - for (auto jsonIter = json.begin(); jsonIter != json.end();) + + for (auto jsonIter = json.begin(); jsonIter != json.end(); ++jsonIter) { - auto& key = jsonIter.key(); + const auto& key = jsonIter.key(); auto& value = jsonIter.value(); if (key == "exception") @@ -76,7 +77,6 @@ namespace compatibility else if (value.is_object()) { RemoveIncompatibilities(value); - ++jsonIter; } else if (value.is_array()) { @@ -90,8 +90,6 @@ namespace compatibility ++valueIter; } - - ++jsonIter; } else if (key == "data") { @@ -105,11 +103,7 @@ namespace compatibility uri = std::regex_replace(uri, std::regex(R"(\.ts$)"), ".cpp"); json[key] = std::filesystem::canonical(uri).string(); - - ++jsonIter; } - else - ++jsonIter; } } From 1ad24587c73eb9b395ed483e87a58b109ddd60e0 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 14:44:24 +0000 Subject: [PATCH 043/196] refactor RemoveIncompatibilities to enhance iteration logic and ensure proper handling of JSON elements --- compatibility/compatibility.cpp | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 6658ac6e..73cd7f84 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -60,8 +60,7 @@ namespace compatibility void RemoveIncompatibilities(nlohmann::json& json) { - - for (auto jsonIter = json.begin(); jsonIter != json.end(); ++jsonIter) + for (auto jsonIter = json.begin(); jsonIter != json.end();) { const auto& key = jsonIter.key(); auto& value = jsonIter.value(); @@ -77,23 +76,24 @@ namespace compatibility else if (value.is_object()) { RemoveIncompatibilities(value); + ++jsonIter; } else if (value.is_array()) { - auto idx = 0; for (auto valueIter = value.begin(); valueIter != value.end();) { - auto& item = *valueIter; - - if (item.is_object()) - RemoveIncompatibilities(item); + if (valueIter->is_object()) + RemoveIncompatibilities(*valueIter); ++valueIter; } + + ++jsonIter; } else if (key == "data") { json[key] = std::regex_replace(json[key].get(), std::regex(R"(\r\n)"), "\n"); + ++jsonIter; } else if (key == "uri") { @@ -103,7 +103,11 @@ namespace compatibility uri = std::regex_replace(uri, std::regex(R"(\.ts$)"), ".cpp"); json[key] = std::filesystem::canonical(uri).string(); + + ++jsonIter; } + else + ++jsonIter; } } From e10babb99ca456c9f0611c4dfa49292181b1e3fe Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:14:36 +0000 Subject: [PATCH 044/196] remove old reporters --- compatibility/minimal/out.ndjson | 11 - cucumber_cpp/library/CMakeLists.txt | 1 - cucumber_cpp/library/report/CMakeLists.txt | 23 -- cucumber_cpp/library/report/JunitReport.cpp | 238 -------------- cucumber_cpp/library/report/JunitReport.hpp | 64 ---- cucumber_cpp/library/report/Report.cpp | 167 ---------- cucumber_cpp/library/report/Report.hpp | 178 ----------- cucumber_cpp/library/report/StdOutReport.cpp | 294 ------------------ cucumber_cpp/library/report/StdOutReport.hpp | 52 ---- .../library/report/test_helper/CMakeLists.txt | 10 - .../test_helper/ReportForwarderMock.hpp | 23 -- 11 files changed, 1061 deletions(-) delete mode 100644 compatibility/minimal/out.ndjson delete mode 100644 cucumber_cpp/library/report/CMakeLists.txt delete mode 100644 cucumber_cpp/library/report/JunitReport.cpp delete mode 100644 cucumber_cpp/library/report/JunitReport.hpp delete mode 100644 cucumber_cpp/library/report/Report.cpp delete mode 100644 cucumber_cpp/library/report/Report.hpp delete mode 100644 cucumber_cpp/library/report/StdOutReport.cpp delete mode 100644 cucumber_cpp/library/report/StdOutReport.hpp delete mode 100644 cucumber_cpp/library/report/test_helper/CMakeLists.txt delete mode 100644 cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp diff --git a/compatibility/minimal/out.ndjson b/compatibility/minimal/out.ndjson deleted file mode 100644 index 0cc1575f..00000000 --- a/compatibility/minimal/out.ndjson +++ /dev/null @@ -1,11 +0,0 @@ -{"source":{"data":"Feature: minimal\n \n Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list\n \n Scenario: cukes\n Given I have 42 cukes in my belly\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/minimal/minimal.feature"}} -{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"1","keyword":"Scenario","location":{"column":3,"line":9},"name":"cukes","steps":[{"id":"0","keyword":"Given ","keywordType":"Context","location":{"column":5,"line":10},"text":"I have 42 cukes in my belly"}],"tags":[]}}],"description":" Cucumber doesn't execute this markdown, but @cucumber/react renders it.\n \n * This is\n * a bullet\n * list","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"minimal","tags":[]},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/minimal/minimal.feature"}} -{"pickle":{"astNodeIds":["1"],"id":"3","language":"en","location":{"column":3,"line":9},"name":"cukes","steps":[{"astNodeIds":["0"],"id":"2","text":"I have 42 cukes in my belly","type":"Context"}],"tags":[],"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/minimal/minimal.feature"}} -{"stepDefinition":{"id":"4","pattern":{"source":"I have {int} cukes in my belly","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":3},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/minimal/minimal.cpp"}}} -{"testRunStarted":{"id":"5","timestamp":{"nanos":0,"seconds":0}}} -{"testCase":{"id":"6","pickleId":"3","testRunStartedId":"5","testSteps":[{"id":"7","pickleStepId":"2","stepDefinitionIds":["4"],"stepMatchArgumentsLists":[{"stepMatchArguments":[{"group":{"children":[],"value":"42"},"parameterTypeName":"int"}]}]}]}} -{"testCaseStarted":{"attempt":0,"id":"8","testCaseId":"6","timestamp":{"nanos":1000000,"seconds":0}}} -{"testStepStarted":{"testCaseStartedId":"8","testStepId":"7","timestamp":{"nanos":2000000,"seconds":0}}} -{"testStepFinished":{"testCaseStartedId":"8","testStepId":"7","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} -{"testCaseFinished":{"testCaseStartedId":"8","timestamp":{"nanos":4000000,"seconds":0},"willBeRetried":false}} -{"testRunFinished":{"success":true,"testRunStartedId":"5","timestamp":{"nanos":5000000,"seconds":0}}} diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index c3c0f9fa..c33d9726 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -56,7 +56,6 @@ add_subdirectory(cucumber_expression) add_subdirectory(formatter) add_subdirectory(tag_expression) add_subdirectory(engine) -# add_subdirectory(report) add_subdirectory(util) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/report/CMakeLists.txt b/cucumber_cpp/library/report/CMakeLists.txt deleted file mode 100644 index bba15987..00000000 --- a/cucumber_cpp/library/report/CMakeLists.txt +++ /dev/null @@ -1,23 +0,0 @@ -add_library(cucumber_cpp.library.report STATIC ${CCR_EXCLUDE_FROM_ALL}) - -target_sources(cucumber_cpp.library.report PRIVATE - JunitReport.cpp - JunitReport.hpp - Report.cpp - Report.hpp - StdOutReport.cpp - StdOutReport.hpp -) - -target_include_directories(cucumber_cpp.library.report PUBLIC - ../../.. -) - -target_link_libraries(cucumber_cpp.library.report PUBLIC - pugixml::pugixml - cucumber_cpp.library -) - -if (CCR_BUILD_TESTS) - add_subdirectory(test_helper) -endif() diff --git a/cucumber_cpp/library/report/JunitReport.cpp b/cucumber_cpp/library/report/JunitReport.cpp deleted file mode 100644 index 3b321169..00000000 --- a/cucumber_cpp/library/report/JunitReport.cpp +++ /dev/null @@ -1,238 +0,0 @@ - -#include "cucumber_cpp/library/report/JunitReport.hpp" -#include "cucumber_cpp/library/TraceTime.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::report -{ - namespace - { - constexpr double precision = 0.0000001; - - const std::map successLut{ - { engine::Result::passed, "done" }, - { engine::Result::skipped, "skipped" }, - { engine::Result::failed, "failed" }, - { engine::Result::pending, "pending" }, - { engine::Result::ambiguous, "ambiguous" }, - { engine::Result::undefined, "undefined" }, - }; - - std::string RoundTo(double value, double roundToPrecision) - { - const auto d = std::round(value / roundToPrecision) * roundToPrecision; - - std::ostringstream out; - out << std::fixed << d; - return out.str(); - } - } - - JunitReport::JunitReport(const std::string& outputfolder, const std::string& reportfile) - : outputfolder{ outputfolder } - , reportfile{ reportfile } - { - testsuites = doc.append_child("testsuites"); - testsuites.append_attribute("name").set_value("Test run"); - testsuites.append_attribute("time").set_value(0.0); - } - - JunitReport::~JunitReport() - { - testsuites.append_attribute("tests").set_value(totalTests); - testsuites.append_attribute("failures").set_value(totalFailures); - testsuites.append_attribute("skipped").set_value(totalSkipped); - - const auto doubleTime = std::chrono::duration>(totalTime).count(); - testsuites.append_attribute("time").set_value(RoundTo(doubleTime, precision).c_str()); - - try - { - if (!std::filesystem::exists(outputfolder)) - std::filesystem::create_directories(outputfolder); - - const auto outputfile = std::filesystem::path{ outputfolder }.append(reportfile + ".xml"); - doc.save_file(outputfile.c_str()); - } - catch (const std::filesystem::filesystem_error& ex) - { - std::cout << "\nwhat(): " << ex.what() << '\n' - << "path1(): " << ex.path1() << '\n' - << "path2(): " << ex.path2() << '\n' - << "code().value(): " << ex.code().value() << '\n' - << "code().message(): " << ex.code().message() << '\n' - << "code().category(): " << ex.code().category().name() << '\n'; - } - } - - void JunitReport::FeatureStart(const engine::FeatureInfo& featureInfo) - { - testsuite = testsuites.append_child("testsuite"); - testsuite.append_attribute("name").set_value(featureInfo.Title().c_str()); - testsuite.append_attribute("file").set_value(featureInfo.Path().string().c_str()); - - scenarioTests = 0; - scenarioFailures = 0; - scenarioSkipped = 0; - } - - void JunitReport::FeatureEnd(engine::Result /*result*/, const engine::FeatureInfo& /*featureInfo*/, TraceTime::Duration duration) - { - const auto doubleTime = std::chrono::duration>(duration).count(); - testsuite.append_attribute("time").set_value(RoundTo(doubleTime, precision).c_str()); - - totalTests += scenarioTests; - totalFailures += scenarioFailures; - totalSkipped += scenarioSkipped; - - testsuite.append_attribute("tests").set_value(scenarioTests); - testsuite.append_attribute("failures").set_value(scenarioFailures); - testsuite.append_attribute("skipped").set_value(scenarioSkipped); - } - - void JunitReport::RuleStart(const engine::RuleInfo& ruleInfo) - { - /* do nothing */ - } - - void JunitReport::RuleEnd(engine::Result result, const engine::RuleInfo& ruleInfo, TraceTime::Duration duration) - { - /* do nothing */ - } - - void JunitReport::ScenarioStart(const engine::ScenarioInfo& scenarioInfo) - { - testcase = testsuite.append_child("testcase"); - - testcase.append_attribute("name").set_value(scenarioInfo.Title().c_str()); - - ++scenarioTests; - } - - void JunitReport::ScenarioEnd(engine::Result result, const engine::ScenarioInfo& /*scenarioInfo*/, TraceTime::Duration duration) - { - const auto doubleTime = std::chrono::duration>(duration).count(); - testcase.append_attribute("time").set_value(RoundTo(doubleTime, precision).c_str()); - - switch (result) - { - case engine::Result::passed: - break; - - case engine::Result::skipped: - case engine::Result::pending: - case engine::Result::ambiguous: - case engine::Result::undefined: - { - ++scenarioSkipped; - auto skipped = testcase.append_child("skipped"); - - if (result == engine::Result::skipped) - { - skipped.append_attribute("message").set_value("Test is skipped due to previous errors."); - } - else if (result == engine::Result::undefined) - { - skipped.append_attribute("message").set_value("Test is undefined."); - } - else if (result == engine::Result::pending) - { - skipped.append_attribute("message").set_value("Test is pending."); - } - else - { - skipped.append_attribute("message").set_value("Test result unkown."); - } - } - - break; - - case engine::Result::failed: - ++scenarioFailures; - break; - } - - totalTime += duration; - } - - void JunitReport::StepSkipped(const engine::StepInfo& stepInfo) - { - /* do nothing */ - } - - void JunitReport::StepStart(const engine::StepInfo& stepInfo) - { - /* do nothing */ - } - - void JunitReport::StepEnd(engine::Result result, const engine::StepInfo& stepInfo, TraceTime::Duration duration) - { - /* do nothing */ - } - - void JunitReport::Failure(const std::string& error, std::optional path, std::optional line, std::optional column) - { - auto failure = testcase.append_child("failure"); - - failure.append_attribute("message").set_value(error.c_str()); - - std::ostringstream out; - - if (path && line && column) - out - << "\n" - << path.value().string() << ":" << line.value() << ":" << column.value() << ": Failure\n" - << error; - else - out - << "\n" - << error; - - failure.text() = out.str().c_str(); - } - - void JunitReport::Error(const std::string& error, std::optional path, std::optional line, std::optional column) - { - auto errorNode = testcase.append_child("error"); - - errorNode.append_attribute("message").set_value(error.c_str()); - - std::ostringstream out; - - if (path && line && column) - out - << "\n" - << path.value().string() << ":" << line.value() << ":" << column.value() << ": Error\n" - << error; - else - out - << "\n" - << error; - - errorNode.text() = out.str().c_str(); - } - - void JunitReport::Trace(const std::string& trace) - { - /* do nothing */ - } - - void JunitReport::Summary(TraceTime::Duration duration) - { - /* do nothing */} -} diff --git a/cucumber_cpp/library/report/JunitReport.hpp b/cucumber_cpp/library/report/JunitReport.hpp deleted file mode 100644 index 8c528f4e..00000000 --- a/cucumber_cpp/library/report/JunitReport.hpp +++ /dev/null @@ -1,64 +0,0 @@ -#ifndef REPORT_JUNITREPORT_HPP -#define REPORT_JUNITREPORT_HPP - -#include "cucumber_cpp/library/TraceTime.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include "pugixml.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::report -{ - struct JunitReport : ReportHandlerV2 - { - JunitReport(const std::string& outputfolder, const std::string& reportfile); - ~JunitReport() override; - - void FeatureStart(const engine::FeatureInfo& featureInfo) override; - void FeatureEnd(engine::Result result, const engine::FeatureInfo& featureInfo, TraceTime::Duration duration) override; - - void RuleStart(const engine::RuleInfo& ruleInfo) override; - void RuleEnd(engine::Result result, const engine::RuleInfo& ruleInfo, TraceTime::Duration duration) override; - - void ScenarioStart(const engine::ScenarioInfo& scenarioInfo) override; - void ScenarioEnd(engine::Result result, const engine::ScenarioInfo& scenarioInfo, TraceTime::Duration duration) override; - - void StepSkipped(const engine::StepInfo& stepInfo) override; - void StepStart(const engine::StepInfo& stepInfo) override; - void StepEnd(engine::Result result, const engine::StepInfo& stepInfo, TraceTime::Duration duration) override; - - void Failure(const std::string& error, std::optional path, std::optional line, std::optional column) override; - void Error(const std::string& error, std::optional path, std::optional line, std::optional column) override; - - void Trace(const std::string& trace) override; - - void Summary(TraceTime::Duration duration) override; - - private: - const std::string& outputfolder; - const std::string& reportfile; - - pugi::xml_document doc; - pugi::xml_node testsuites; - pugi::xml_node testsuite; - pugi::xml_node testcase; - - std::size_t totalTests{ 0 }; - std::size_t totalFailures{ 0 }; - std::size_t totalSkipped{ 0 }; - TraceTime::Duration totalTime{ 0 }; - - std::size_t scenarioTests{ 0 }; - std::size_t scenarioFailures{ 0 }; - std::size_t scenarioSkipped{ 0 }; - }; -} - -#endif diff --git a/cucumber_cpp/library/report/Report.cpp b/cucumber_cpp/library/report/Report.cpp deleted file mode 100644 index 3ca74a18..00000000 --- a/cucumber_cpp/library/report/Report.cpp +++ /dev/null @@ -1,167 +0,0 @@ -#include "cucumber_cpp/library/report/Report.hpp" -#include "cucumber_cpp/library/TraceTime.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::report -{ - namespace - { - template - void ForwardCall(auto& reporters, TFn fn, TArgs&&... args) - { - std::ranges::for_each( - reporters, [&](const auto& reportHandlerV2) - { - (*reportHandlerV2.*fn)(std::forward(args)...); - }); - } - } - - void Reporters::Add(const std::string& name, std::unique_ptr reporter) - { - availableReporters[name] = std::move(reporter); - } - - void Reporters::Use(const std::string& name) - { - if (availableReporters[name]) - Add(std::move(availableReporters[name])); - } - - void Reporters::Add(std::unique_ptr report) - { - reporters.push_back(std::move(report)); - } - - std::vector Reporters::AvailableReporters() const - { - auto range = std::views::keys(availableReporters); - return { range.begin(), range.end() }; - } - - std::vector>& Reporters::Storage() - { - return reporters; - } - - ReportForwarder::ProgramScope::ProgramScope(cucumber_cpp::library::engine::ProgramContext& programContext, std::vector>& reporters) - : programContext{ programContext } - , reporters{ reporters } - {} - - ReportForwarder::ProgramScope::~ProgramScope() - { - ForwardCall(reporters, &ReportHandlerV2::Summary, programContext.Duration()); - } - - ReportForwarder::FeatureScope::FeatureScope(cucumber_cpp::library::engine::FeatureContext& featureContext, std::vector>& reporters) - : featureContext{ featureContext } - , reporters{ reporters } - { - ForwardCall(reporters, &ReportHandlerV2::FeatureStart, featureContext.info); - } - - ReportForwarder::FeatureScope::~FeatureScope() - { - ForwardCall(reporters, &ReportHandlerV2::FeatureEnd, featureContext.ExecutionStatus(), featureContext.info, featureContext.Duration()); - } - - ReportForwarder::RuleScope::RuleScope(cucumber_cpp::library::engine::RuleContext& ruleContext, std::vector>& reporters) - : ruleContext{ ruleContext } - , reporters{ reporters } - { - ForwardCall(reporters, &ReportHandlerV2::RuleStart, ruleContext.info); - } - - ReportForwarder::RuleScope::~RuleScope() - { - ForwardCall(reporters, &ReportHandlerV2::RuleEnd, ruleContext.ExecutionStatus(), ruleContext.info, ruleContext.Duration()); - } - - ReportForwarder::ScenarioScope::ScenarioScope(cucumber_cpp::library::engine::ScenarioContext& scenarioContext, std::vector>& reporters) - : scenarioContext{ scenarioContext } - , reporters{ reporters } - { - ForwardCall(reporters, &ReportHandlerV2::ScenarioStart, scenarioContext.info); - } - - ReportForwarder::ScenarioScope::~ScenarioScope() - { - ForwardCall(reporters, &ReportHandlerV2::ScenarioEnd, scenarioContext.ExecutionStatus(), scenarioContext.info, scenarioContext.Duration()); - } - - ReportForwarder::StepScope::StepScope(cucumber_cpp::library::engine::StepContext& stepContext, std::vector>& reporters) - : stepContext{ stepContext } - , reporters{ reporters } - { - ForwardCall(reporters, &ReportHandlerV2::StepStart, stepContext.info); - } - - ReportForwarder::StepScope::~StepScope() - { - ForwardCall(reporters, &ReportHandlerV2::StepEnd, stepContext.ExecutionStatus(), stepContext.info, stepContext.Duration()); - } - - ReportForwarderImpl::ReportForwarderImpl(cucumber_cpp::library::engine::ContextManager& contextManager) - : contextManager{ contextManager } - {} - - [[nodiscard]] ReportForwarder::ProgramScope ReportForwarderImpl::ProgramStart() - { - return ProgramScope{ contextManager.ProgramContext(), Storage() }; - } - - ReportForwarder::FeatureScope ReportForwarderImpl::FeatureStart() - { - return FeatureScope{ contextManager.FeatureContext(), Storage() }; - } - - ReportForwarder::RuleScope ReportForwarderImpl::RuleStart() - { - return RuleScope{ contextManager.RuleContext(), Storage() }; - } - - ReportForwarder::ScenarioScope ReportForwarderImpl::ScenarioStart() - { - return ScenarioScope{ contextManager.ScenarioContext(), Storage() }; - } - - ReportForwarder::StepScope ReportForwarderImpl::StepStart() - { - return StepScope{ contextManager.StepContext(), Storage() }; - } - - void ReportForwarderImpl::StepSkipped() - { - ForwardCall(Storage(), &ReportHandlerV2::StepSkipped, contextManager.StepContext().info); - } - - void ReportForwarderImpl::Failure(const std::string& error, std::optional path, std::optional line, std::optional column) - { - ForwardCall(Storage(), &ReportHandlerV2::Failure, error, path, line, column); - } - - void ReportForwarderImpl::Error(const std::string& error, std::optional path, std::optional line, std::optional column) - { - ForwardCall(Storage(), &ReportHandlerV2::Error, error, path, line, column); - } - - void ReportForwarderImpl::Trace(const std::string& trace) - { - ForwardCall(Storage(), &ReportHandlerV2::Trace, trace); - } - - void ReportForwarderImpl::Summary(TraceTime::Duration duration) - { - ForwardCall(Storage(), &ReportHandlerV2::Summary, duration); - } -} diff --git a/cucumber_cpp/library/report/Report.hpp b/cucumber_cpp/library/report/Report.hpp deleted file mode 100644 index a70b6f15..00000000 --- a/cucumber_cpp/library/report/Report.hpp +++ /dev/null @@ -1,178 +0,0 @@ -#ifndef REPORT_REPORT_HPP -#define REPORT_REPORT_HPP - -// IWYU pragma: private, include "cucumber_cpp/CucumberCpp.hpp" -// IWYU pragma: friend cucumber_cpp/.* - -#include "cucumber_cpp/library/TraceTime.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/util/Immoveable.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp -{ - struct FeatureSource; - struct ScenarioSource; - struct StepSource; -} - -namespace cucumber_cpp::library::report -{ - struct ReportHandlerV2 - { - virtual ~ReportHandlerV2() = default; - - virtual void FeatureStart(const engine::FeatureInfo& featureInfo) = 0; - virtual void FeatureEnd(engine::Result result, const engine::FeatureInfo& featureInfo, TraceTime::Duration duration) = 0; - - virtual void RuleStart(const engine::RuleInfo& ruleInfo) = 0; - virtual void RuleEnd(engine::Result result, const engine::RuleInfo& ruleInfo, TraceTime::Duration duration) = 0; - - virtual void ScenarioStart(const engine::ScenarioInfo& scenarioInfo) = 0; - virtual void ScenarioEnd(engine::Result result, const engine::ScenarioInfo& scenarioInfo, TraceTime::Duration duration) = 0; - - virtual void StepSkipped(const engine::StepInfo& stepInfo) = 0; - virtual void StepStart(const engine::StepInfo& stepInfo) = 0; - virtual void StepEnd(engine::Result result, const engine::StepInfo& stepInfo, TraceTime::Duration duration) = 0; - - virtual void Failure(const std::string& error, std::optional path = {}, std::optional line = {}, std::optional column = {}) = 0; - virtual void Error(const std::string& error, std::optional path = {}, std::optional line = {}, std::optional column = {}) = 0; - - virtual void Trace(const std::string& trace) = 0; - - virtual void Summary(TraceTime::Duration duration) = 0; - }; - - struct Reporters - { - void Add(const std::string& name, std::unique_ptr reporter); - void Use(const std::string& name); - - [[nodiscard]] std::vector AvailableReporters() const; - - protected: - void Add(std::unique_ptr report); - std::vector>& Storage(); - - private: - std::map, std::less<>> availableReporters; - std::vector> reporters; - }; - - struct ReportForwarder - { - protected: - ~ReportForwarder() = default; - - public: - struct ProgramScope; - struct FeatureScope; - struct RuleScope; - struct ScenarioScope; - struct StepScope; - - [[nodiscard]] virtual ProgramScope ProgramStart() = 0; - [[nodiscard]] virtual FeatureScope FeatureStart() = 0; - [[nodiscard]] virtual RuleScope RuleStart() = 0; - [[nodiscard]] virtual ScenarioScope ScenarioStart() = 0; - [[nodiscard]] virtual StepScope StepStart() = 0; - - virtual void StepSkipped() = 0; - - virtual void Failure(const std::string& error, std::optional path = {}, std::optional line = {}, std::optional column = {}) = 0; - virtual void Error(const std::string& error, std::optional path = {}, std::optional line = {}, std::optional column = {}) = 0; - - virtual void Trace(const std::string& trace) = 0; - - virtual void Summary(TraceTime::Duration duration) = 0; - }; - - struct ReportForwarder::ProgramScope : util::Immoveable - { - ProgramScope(cucumber_cpp::library::engine::ProgramContext& programContext, std::vector>& reporters); - ~ProgramScope(); - - private: - cucumber_cpp::library::engine::ProgramContext& programContext; - std::vector>& reporters; - }; - - struct ReportForwarder::FeatureScope : util::Immoveable - { - FeatureScope(cucumber_cpp::library::engine::FeatureContext& featureContext, std::vector>& reporters); - ~FeatureScope(); - - private: - cucumber_cpp::library::engine::FeatureContext& featureContext; - std::vector>& reporters; - }; - - struct ReportForwarder::RuleScope : util::Immoveable - { - RuleScope(cucumber_cpp::library::engine::RuleContext& ruleContext, std::vector>& reporters); - ~RuleScope(); - - private: - cucumber_cpp::library::engine::RuleContext& ruleContext; - std::vector>& reporters; - }; - - struct ReportForwarder::ScenarioScope : util::Immoveable - { - ScenarioScope(cucumber_cpp::library::engine::ScenarioContext& scenarioContext, std::vector>& reporters); - ~ScenarioScope(); - - private: - cucumber_cpp::library::engine::ScenarioContext& scenarioContext; - std::vector>& reporters; - }; - - struct ReportForwarder::StepScope : util::Immoveable - { - StepScope(cucumber_cpp::library::engine::StepContext& stepContext, std::vector>& reporters); - ~StepScope(); - - private: - cucumber_cpp::library::engine::StepContext& stepContext; - std::vector>& reporters; - }; - - struct ReportForwarderImpl - : Reporters - , ReportForwarder - { - explicit ReportForwarderImpl(cucumber_cpp::library::engine::ContextManager& contextManager); - virtual ~ReportForwarderImpl() = default; - - [[nodiscard]] ProgramScope ProgramStart() override; - [[nodiscard]] FeatureScope FeatureStart() override; - [[nodiscard]] RuleScope RuleStart() override; - [[nodiscard]] ScenarioScope ScenarioStart() override; - [[nodiscard]] StepScope StepStart() override; - - void StepSkipped() override; - - void Failure(const std::string& error, std::optional path = {}, std::optional line = {}, std::optional column = {}) override; - void Error(const std::string& error, std::optional path = {}, std::optional line = {}, std::optional column = {}) override; - - void Trace(const std::string& trace) override; - - void Summary(TraceTime::Duration duration) override; - - private: - cucumber_cpp::library::engine::ContextManager& contextManager; - }; -} - -#endif diff --git a/cucumber_cpp/library/report/StdOutReport.cpp b/cucumber_cpp/library/report/StdOutReport.cpp deleted file mode 100644 index 48523207..00000000 --- a/cucumber_cpp/library/report/StdOutReport.cpp +++ /dev/null @@ -1,294 +0,0 @@ -#include "cucumber_cpp/library/report/StdOutReport.hpp" -#include "cucumber_cpp/library/TraceTime.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#ifdef _MSC_VER -// clang-format off -#include -#include -#include -#include -#include -#include -// clang-format on -#endif - -namespace cucumber_cpp::library::report -{ - namespace - { -#ifndef _MSC_VER - inline std::ostream& TcRed(std::ostream& o) - { - o << "\o{33}[1m\o{33}[31m"; - return o; - } - - inline std::ostream& TcGreen(std::ostream& o) - { - o << "\o{33}[1m\o{33}[32m"; - return o; - } - - inline std::ostream& TcCyan(std::ostream& o) - { - o << "\o{33}[1m\o{33}[36m"; - return o; - } - - inline std::ostream& TcDefault(std::ostream& o) - { - o << "\o{33}[0m\o{33}[39m"; - return o; - } -#else - WORD GetDefaultConsoleValue() - { - CONSOLE_SCREEN_BUFFER_INFO info; - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); - return info.wAttributes; - } - - WORD GetDefaultConsole() - { - static WORD defaultValue = GetDefaultConsoleValue(); - return defaultValue; - } - - void SetColorConsole(WORD color) - { - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), color | (GetDefaultConsole() & ~(FOREGROUND_INTENSITY | FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE))); - } - - inline std::ostream& TcRed(std::ostream& o) - { - SetColorConsole(FOREGROUND_INTENSITY | FOREGROUND_RED); - return o; - } - - inline std::ostream& TcGreen(std::ostream& o) - { - SetColorConsole(FOREGROUND_INTENSITY | FOREGROUND_GREEN); - return o; - } - - inline std::ostream& TcCyan(std::ostream& o) - { - SetColorConsole(FOREGROUND_INTENSITY | FOREGROUND_GREEN | FOREGROUND_BLUE); - return o; - } - - inline std::ostream& TcDefault(std::ostream& o) - { - SetColorConsole(GetDefaultConsole()); - return o; - } -#endif - - const std::map successLut{ - { engine::Result ::passed, "done" }, - { engine::Result ::skipped, "skipped" }, - { engine::Result ::failed, "failed" }, - { engine::Result ::pending, "pending" }, - { engine::Result ::ambiguous, "ambiguous" }, - { engine::Result ::undefined, "undefined" }, - }; - - const std::map stepTypeLut{ - { engine::StepType::given, "Given" }, - { engine::StepType::when, "When" }, - { engine::StepType::then, "Then" } - }; - - std::string ScaledDuration(TraceTime::Duration duration) - { - std::ostringstream out; - - if (duration < std::chrono::microseconds{ 1 }) - out << std::chrono::duration(duration); - else if (duration < std::chrono::milliseconds{ 1 }) - out << std::chrono::duration(duration); - else if (duration < std::chrono::seconds{ 1 }) - out << std::chrono::duration(duration); - else if (duration < std::chrono::minutes{ 1 }) - out << std::chrono::duration(duration); - else if (duration < std::chrono::hours{ 1 }) - out << std::chrono::duration(duration); - else - out << std::chrono::duration(duration); - - return out.str(); - } - - std::string Repeat(std::string_view input, std::size_t count) - { - std::string result; - result.reserve(input.size() * count); - for (std::size_t i = 0; i < count; ++i) - result += input; - return result; - } - } - - StdOutReport::StdOutReport() - { -#ifdef _MSC_VER - SetConsoleOutputCP(CP_UTF8); - SetConsoleCP(CP_UTF8); -#endif - } - - void StdOutReport::FeatureStart(const engine::FeatureInfo& featureInfo) - { - // not required - } - - void StdOutReport::FeatureEnd(engine::Result result, const engine::FeatureInfo& featureInfo, TraceTime::Duration duration) - { - // not required - } - - void StdOutReport::RuleStart(const engine::RuleInfo& ruleInfo) - { - std::cout << "\n" - << ruleInfo.Title(); - } - - void StdOutReport::RuleEnd(engine::Result result, const engine::RuleInfo& ruleInfo, TraceTime::Duration duration) - { - // not required - } - - void StdOutReport::ScenarioStart(const engine::ScenarioInfo& scenarioInfo) - { - ++nrOfScenarios; - std::cout << "\n" - << scenarioInfo.Title(); - } - - void StdOutReport::ScenarioEnd(engine::Result result, const engine::ScenarioInfo& scenarioInfo, TraceTime::Duration duration) - { - using enum engine::Result; - - std::cout << "\n" - << "\\-> "; - - auto& coloured = ((result == passed) ? std::cout << TcGreen : std::cout << TcRed); - - coloured << successLut.at(result) - << " (" << ScaledDuration(duration) << ")" - << TcDefault << '\n'; - - if (result != engine::Result::passed) - { - failedScenarios.emplace_back(&scenarioInfo); - } - } - - void StdOutReport::StepSkipped(const engine::StepInfo& stepInfo) - { - std::cout << "\n" - << Repeat("| ", nestedSteps) - << TcCyan; - std::cout << successLut.at(engine::Result::skipped) << " " << stepTypeLut.at(stepInfo.Type()) << " " << stepInfo.Text(); - std::cout << TcDefault; - } - - void StdOutReport::StepStart(const engine::StepInfo& stepInfo) - { - std::cout << "\n" - << Repeat("| ", nestedSteps) - << stepTypeLut.at(stepInfo.Type()) << " " << stepInfo.Text() - << std::flush; - ++nestedSteps; - } - - void StdOutReport::StepEnd(engine::Result result, const engine::StepInfo& stepInfo, TraceTime::Duration duration) - { - --nestedSteps; - - using enum engine::Result; - - std::cout << "\n" - << Repeat("| ", nestedSteps) << "\\-> "; - - if (result == passed) - std::cout << TcGreen; - else - std::cout << TcRed; - - std::cout << successLut.at(result); - - if (result != passed) - std::cout << " " << stepInfo.ScenarioInfo().FeatureInfo().Path() << ":" << stepInfo.Line() << ":" << stepInfo.Column(); - - std::cout << " (" << ScaledDuration(duration) << ")"; - - std::cout << TcDefault; - } - - void StdOutReport::Failure(const std::string& error, std::optional path, std::optional line, std::optional column) - { - std::cout << TcRed; - - if (path && line && column) - std::cout << std::format("\nFailure @ ./{}:{}:{}:", path.value().string(), line.value(), column.value()); - else if (path && line) - std::cout << std::format("\nFailure @ ./{}:{}:", path.value().string(), line.value()); - - std::cout << std::format("\n{}", error); - - std::cout << TcDefault; - } - - void StdOutReport::Error(const std::string& error, std::optional path, std::optional line, std::optional column) - { - std::cout << TcRed; - - if (path && line && column) - std::cout << std::format("\nError @ ./{}:{}:{}:", path.value().string(), line.value(), column.value()); - else if (path && line) - std::cout << std::format("\nError @ ./{}:{}:", path.value().string(), line.value()); - - std::cout << std::format("\n{}", error); - - std::cout << TcDefault; - } - - void StdOutReport::Trace(const std::string& trace) - { - std::cout << trace; - } - - void StdOutReport::Summary(TraceTime::Duration duration) - { - std::cout << "\n====================summary===================="; - std::cout << "\nduration: " << ScaledDuration(duration); - std::cout << "\ntests : " << (nrOfScenarios - failedScenarios.size()) << "/" << nrOfScenarios << " passed"; - - if (!failedScenarios.empty()) - { - std::cout << "\n\nfailed tests:"; - - for (const auto* scenarioInfo : failedScenarios) - std::cout << "\n" - << scenarioInfo->Path() << ":" << scenarioInfo->Line() << ":" << scenarioInfo->Column() << " : " << std::quoted(scenarioInfo->Title()); - } - } -} diff --git a/cucumber_cpp/library/report/StdOutReport.hpp b/cucumber_cpp/library/report/StdOutReport.hpp deleted file mode 100644 index ab392b3e..00000000 --- a/cucumber_cpp/library/report/StdOutReport.hpp +++ /dev/null @@ -1,52 +0,0 @@ -#ifndef REPORT_STDOUTREPORT_HPP -#define REPORT_STDOUTREPORT_HPP - -#include "cucumber_cpp/library/TraceTime.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::report -{ - struct StdOutReport : ReportHandlerV2 - { - StdOutReport(); - - void FeatureStart(const engine::FeatureInfo& featureInfo) override; - void FeatureEnd(engine::Result result, const engine::FeatureInfo& featureInfo, TraceTime::Duration duration) override; - - void RuleStart(const engine::RuleInfo& ruleInfo) override; - void RuleEnd(engine::Result result, const engine::RuleInfo& ruleInfo, TraceTime::Duration duration) override; - - void ScenarioStart(const engine::ScenarioInfo& scenarioInfo) override; - void ScenarioEnd(engine::Result result, const engine::ScenarioInfo& scenarioInfo, TraceTime::Duration duration) override; - - void StepSkipped(const engine::StepInfo& stepInfo) override; - void StepStart(const engine::StepInfo& stepInfo) override; - void StepEnd(engine::Result result, const engine::StepInfo& stepInfo, TraceTime::Duration duration) override; - - void Failure(const std::string& error, std::optional path = {}, std::optional line = {}, std::optional column = {}) override; - void Error(const std::string& error, std::optional path = {}, std::optional line = {}, std::optional column = {}) override; - - void Trace(const std::string& trace) override; - - void Summary(TraceTime::Duration duration) override; - - private: - std::uint32_t nrOfScenarios{ 0 }; - std::vector failedScenarios; - - std::size_t nestedSteps{ 1 }; - }; -} - -#endif diff --git a/cucumber_cpp/library/report/test_helper/CMakeLists.txt b/cucumber_cpp/library/report/test_helper/CMakeLists.txt deleted file mode 100644 index 8791d028..00000000 --- a/cucumber_cpp/library/report/test_helper/CMakeLists.txt +++ /dev/null @@ -1,10 +0,0 @@ -add_library(cucumber_cpp.library.report.test_helper INTERFACE) - -target_link_libraries(cucumber_cpp.library.report.test_helper INTERFACE - cucumber_cpp.library.report - gmock_main -) - -target_sources(cucumber_cpp.library.report.test_helper INTERFACE - ReportForwarderMock.hpp -) diff --git a/cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp b/cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp deleted file mode 100644 index 8451dbfc..00000000 --- a/cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp +++ /dev/null @@ -1,23 +0,0 @@ -#ifndef TEST_HELPER_REPORTFORWARDERMOCK_HPP -#define TEST_HELPER_REPORTFORWARDERMOCK_HPP - -#include "cucumber_cpp/library/report/Report.hpp" -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::report::test_helper -{ - struct ReportForwarderMock : report::ReportForwarderImpl - { - using ReportForwarderImpl::ReportForwarderImpl; - virtual ~ReportForwarderMock() = default; - - MOCK_METHOD(void, Failure, (const std::string& error, std::optional path, std::optional line, std::optional column), (override)); - MOCK_METHOD(void, Error, (const std::string& error, std::optional path, std::optional line, std::optional column), (override)); - }; -} - -#endif From a56826e3b5c436acb601d6a4b0c4dbc2a841e22c Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:21:22 +0000 Subject: [PATCH 045/196] remove unused stuff --- .../helper/GherkinDocumentParser.cpp | 19 ------------------- .../helper/GherkinDocumentParser.hpp | 4 ---- cucumber_cpp/library/runtime/Worker.cpp | 8 ++++---- 3 files changed, 4 insertions(+), 27 deletions(-) diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp index bb9e074d..6e5a5097 100644 --- a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp +++ b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp @@ -2,14 +2,11 @@ #include "cucumber/messages/background.hpp" #include "cucumber/messages/feature_child.hpp" #include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/location.hpp" #include "cucumber/messages/rule.hpp" #include "cucumber/messages/rule_child.hpp" #include "cucumber/messages/scenario.hpp" #include "cucumber/messages/step.hpp" -#include #include -#include #include #include @@ -92,22 +89,6 @@ namespace cucumber_cpp::library::formatter::helper return map; } - GherkinScenarioRuleMap GetGherkinScenarioRuleMap(const cucumber::messages::gherkin_document& gherkinDocument) - { - auto rules = gherkinDocument.feature->children | std::views::transform(ExtractRulesFeatureChild) | std::views::join; - - GherkinScenarioRuleMap map; - - for (const auto& rule : rules) - { - auto scenarios = rule.children | std::views::transform(ExtractScenarioFromRuleChild) | std::views::join; - for (const auto& scenario : scenarios) - map.emplace(scenario.id, rule); - } - - return map; - } - GherkinScenarioMap GetGherkinScenarioMap(const cucumber::messages::gherkin_document& gherkinDocument) { GherkinScenarioMap map; diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp index d1f272fd..49106a7f 100644 --- a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp +++ b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp @@ -1,10 +1,8 @@ #ifndef HELPER_GHERKIN_DOCUMENT_PARSER_HPP #define HELPER_GHERKIN_DOCUMENT_PARSER_HPP -#include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/location.hpp" -#include "cucumber/messages/rule.hpp" #include "cucumber/messages/scenario.hpp" #include "cucumber/messages/step.hpp" #include @@ -13,12 +11,10 @@ namespace cucumber_cpp::library::formatter::helper { using GherkinStepMap = std::map; - using GherkinScenarioRuleMap = std::map; using GherkinScenarioMap = std::map; using GherkinScenarioLocationMap = std::map; GherkinStepMap GetGherkinStepMap(const cucumber::messages::gherkin_document& gherkinDocument); - GherkinScenarioRuleMap GetGherkinScenarioRuleMap(const cucumber::messages::gherkin_document& gherkinDocument); GherkinScenarioMap GetGherkinScenarioMap(const cucumber::messages::gherkin_document& gherkinDocument); GherkinScenarioLocationMap GetGherkinScenarioLocationMap(const cucumber::messages::gherkin_document& gherkinDocument); } diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index da55f06d..275a5403 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -56,7 +56,7 @@ namespace cucumber_cpp::library::runtime return to_underlying(a.status) < to_underlying(b.status); }; - inline std::set failingStatuses{ + const inline std::set failingStatuses{ cucumber::messages::test_step_result_status::AMBIGUOUS, cucumber::messages::test_step_result_status::FAILED, cucumber::messages::test_step_result_status::UNDEFINED, @@ -160,10 +160,10 @@ namespace cucumber_cpp::library::runtime cucumber::messages::test_step_result Worker::RunTestHook(std::string id, Context& context) { const auto& definition = supportCodeLibrary.hookRegistry.GetDefinitionById(id); - const auto testRunHookStartedid = idGenerator->next_id(); + const auto testRunHookStartedId = idGenerator->next_id(); const auto testRunHookStarted = cucumber::messages::test_run_hook_started{ - .id = testRunHookStartedid, + .id = testRunHookStartedId, .test_run_started_id = std::string{ testRunStartedId }, .hook_id = definition.hook.id, .timestamp = support::TimestampNow(), @@ -174,7 +174,7 @@ namespace cucumber_cpp::library::runtime auto result = definition.factory(broadcaster, context, testRunHookStarted)->ExecuteAndCatchExceptions(); broadcaster.BroadcastEvent({ .test_run_hook_finished = cucumber::messages::test_run_hook_finished{ - .test_run_hook_started_id = testRunHookStartedid, + .test_run_hook_started_id = testRunHookStartedId, .result = result, .timestamp = support::TimestampNow(), } }); From 998dbd28004da11ceb29aee7fddeb27c0fbf81a0 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:28:12 +0000 Subject: [PATCH 046/196] refactor to reduce nesting --- .../library/assemble/AssembleTestSuites.cpp | 62 ++++++++++++------- 1 file changed, 40 insertions(+), 22 deletions(-) diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 5e3e9221..cd26f163 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -39,31 +39,21 @@ namespace cucumber_cpp::library::assemble { return pair.second.has_value(); } - } - - std::vector AssembleTestSuites(support::SupportCodeLibrary supportCodeLibrary, - std::string_view testRunStartedId, - util::Broadcaster& broadcaster, - const std::list& sourcedPickles, - cucumber::gherkin::id_generator_ptr idGenerator) - { - std::list testUris; - std::map assembledTestSuiteMap; - for (const auto& pickleSource : sourcedPickles) + void AssembleBeforeHooks(support::SupportCodeLibrary supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { - cucumber::messages::test_case testCase{ - .id = idGenerator->next_id(), - .pickle_id = pickleSource.pickle->id, - .test_steps = {}, - .test_run_started_id = std::make_optional(testRunStartedId) - }; - - testCase.test_steps.reserve(pickleSource.pickle->steps.size() * 2); // steps + hooks - for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::before, pickleSource.pickle->tags)) testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); + } + void AssembleAfterHooks(support::SupportCodeLibrary supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) + { + for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags) | std::views::reverse) + testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); + } + + void AssembleSteps(support::SupportCodeLibrary supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) + { for (const auto& step : pickleSource.pickle->steps) { const auto& stepDefinitions = supportCodeLibrary.stepRegistry.StepDefinitions(); @@ -85,9 +75,37 @@ namespace cucumber_cpp::library::assemble argumentList.step_match_arguments.emplace_back(result.Group(), result.Name().empty() ? std::nullopt : std::make_optional(result.Name())); } } + } - for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags) | std::views::reverse) - testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); + void AssembleTestSteps(support::SupportCodeLibrary supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) + { + AssembleBeforeHooks(supportCodeLibrary, pickleSource, testCase, idGenerator); + AssembleSteps(supportCodeLibrary, pickleSource, testCase, idGenerator); + AssembleAfterHooks(supportCodeLibrary, pickleSource, testCase, idGenerator); + } + } + + std::vector AssembleTestSuites(support::SupportCodeLibrary supportCodeLibrary, + std::string_view testRunStartedId, + util::Broadcaster& broadcaster, + const std::list& sourcedPickles, + cucumber::gherkin::id_generator_ptr idGenerator) + { + std::list testUris; + std::map assembledTestSuiteMap; + + for (const auto& pickleSource : sourcedPickles) + { + cucumber::messages::test_case testCase{ + .id = idGenerator->next_id(), + .pickle_id = pickleSource.pickle->id, + .test_steps = {}, + .test_run_started_id = std::make_optional(testRunStartedId) + }; + + testCase.test_steps.reserve(pickleSource.pickle->steps.size() * 2); // steps + hooks + + AssembleTestSteps(supportCodeLibrary, pickleSource, testCase, idGenerator); broadcaster.BroadcastEvent(cucumber::messages::envelope{ .test_case = testCase }); From 811ac18d062a3a254dbdb17e0d17321b77fd98ea Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:31:33 +0000 Subject: [PATCH 047/196] refactory HookRegistry --- cucumber_cpp/library/HookRegistry.cpp | 4 ++-- cucumber_cpp/library/HookRegistry.hpp | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cucumber_cpp/library/HookRegistry.cpp b/cucumber_cpp/library/HookRegistry.cpp index 9d809e38..a0265e5b 100644 --- a/cucumber_cpp/library/HookRegistry.cpp +++ b/cucumber_cpp/library/HookRegistry.cpp @@ -128,8 +128,8 @@ namespace cucumber_cpp::library return registry.at(id); } - void HookRegistry::Register(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation) + void HookRegistry::Register(const std::string& id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation) { - registry.try_emplace(id, Definition{ id, type, expression, name, factory, sourceLocation }); + registry.try_emplace(id, id, type, expression, name, factory, sourceLocation); } } diff --git a/cucumber_cpp/library/HookRegistry.hpp b/cucumber_cpp/library/HookRegistry.hpp index 3a017c30..44513ef6 100644 --- a/cucumber_cpp/library/HookRegistry.hpp +++ b/cucumber_cpp/library/HookRegistry.hpp @@ -12,6 +12,7 @@ #include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include +#include #include #include #include @@ -112,10 +113,10 @@ namespace cucumber_cpp::library const Definition& GetDefinitionById(const std::string& id) const; private: - void Register(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation); + void Register(const std::string& id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation); cucumber::gherkin::id_generator_ptr idGenerator; - std::map registry; + std::map> registry; }; } From 76ed6f4482915e32d374bf444208854596870849 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:42:29 +0000 Subject: [PATCH 048/196] refactored Parameter --- cucumber_cpp/library/CMakeLists.txt | 2 ++ cucumber_cpp/library/Parameter.cpp | 24 ++++++++++++++ cucumber_cpp/library/Parameter.hpp | 49 +++++++++++------------------ 3 files changed, 45 insertions(+), 30 deletions(-) create mode 100644 cucumber_cpp/library/Parameter.cpp diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index c33d9726..0e17335d 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -13,6 +13,8 @@ target_sources(cucumber_cpp.library PRIVATE HookRegistry.cpp HookRegistry.hpp Hooks.hpp + Parameter.cpp + Parameter.hpp Query.cpp Query.hpp Rtrim.cpp diff --git a/cucumber_cpp/library/Parameter.cpp b/cucumber_cpp/library/Parameter.cpp new file mode 100644 index 00000000..037bb98d --- /dev/null +++ b/cucumber_cpp/library/Parameter.cpp @@ -0,0 +1,24 @@ +#include "cucumber_cpp/CucumberCpp.hpp" +#include +#include +#include + +namespace cucumber_cpp::library +{ + std::strong_ordering ParameterEntry::operator<=>(const ParameterEntry& other) const + { + return std::tie(params.name, params.regex) <=> std::tie(other.params.name, other.params.regex); + } + + ParameterRegistration& ParameterRegistration::Instance() + { + static ParameterRegistration instance; + return instance; + } + + const std::set& ParameterRegistration::GetRegisteredParameters() const + { + return customParameters; + } + +} diff --git a/cucumber_cpp/library/Parameter.hpp b/cucumber_cpp/library/Parameter.hpp index edf71b8f..c748eae0 100644 --- a/cucumber_cpp/library/Parameter.hpp +++ b/cucumber_cpp/library/Parameter.hpp @@ -5,23 +5,15 @@ // IWYU pragma: friend cucumber_cpp/.* #include "cucumber/messages/group.hpp" -#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include +#include #include -#include #include #include #include -#include -#include -#include namespace cucumber_cpp::library { - using ToStringFn = std::function; - using ToAnyFn = std::function; - struct ParameterEntryParams { std::string name; @@ -37,10 +29,7 @@ namespace cucumber_cpp::library std::source_location location; - auto operator<=>(const ParameterEntry& other) const - { - return std::tie(params.name, params.regex) <=> std::tie(other.params.name, other.params.regex); - } + std::strong_ordering operator<=>(const ParameterEntry& other) const; }; struct ParameterRegistration @@ -49,30 +38,30 @@ namespace cucumber_cpp::library ParameterRegistration() = default; public: - static ParameterRegistration& Instance() - { - static ParameterRegistration instance; - return instance; - } + static ParameterRegistration& Instance(); template - std::size_t Register(ParameterEntryParams params, std::source_location location = std::source_location::current()) - { - customParameters.emplace(params, customParameters.size() + 1, location); - - cucumber_expression::ConverterTypeMap::Instance()[params.name] = Transformer::Transform; + std::size_t Register(ParameterEntryParams params, std::source_location location = std::source_location::current()); - return customParameters.size(); - } - - const std::set& GetRegisteredParameters() const - { - return customParameters; - } + const std::set& GetRegisteredParameters() const; private: std::set customParameters; }; + + //////////////////// + // Implementation // + //////////////////// + + template + std::size_t ParameterRegistration::Register(ParameterEntryParams params, std::source_location location) + { + customParameters.emplace(params, customParameters.size() + 1, location); + + cucumber_expression::ConverterTypeMap::Instance()[params.name] = Transformer::Transform; + + return customParameters.size(); + } } #define PARAMETER_STRUCT CONCAT(ParameterImpl, __LINE__) From d61854c3ec3a446690652ce2db8558f4eb3a3b91 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:42:46 +0000 Subject: [PATCH 049/196] refactored Query.hpp --- cucumber_cpp/library/Query.cpp | 49 +++++++++++++++------------------- cucumber_cpp/library/Query.hpp | 16 +++++------ 2 files changed, 29 insertions(+), 36 deletions(-) diff --git a/cucumber_cpp/library/Query.cpp b/cucumber_cpp/library/Query.cpp index b2e81d66..a33fe06b 100644 --- a/cucumber_cpp/library/Query.cpp +++ b/cucumber_cpp/library/Query.cpp @@ -39,53 +39,46 @@ namespace cucumber_cpp::library { - Lineage Lineage::operator+(std::shared_ptr gherkinDocument) const + Lineage operator+(Lineage lineage, std::shared_ptr gherkinDocument) { - Lineage copy = *this; - copy.gherkinDocument = gherkinDocument; - return copy; + lineage.gherkinDocument = gherkinDocument; + return std::move(lineage); } - Lineage Lineage::operator+(std::shared_ptr feature) const + Lineage operator+(Lineage lineage, std::shared_ptr feature) { - Lineage copy = *this; - copy.feature = feature; - return copy; + lineage.feature = feature; + return std::move(lineage); } - Lineage Lineage::operator+(std::shared_ptr rule) const + Lineage operator+(Lineage lineage, std::shared_ptr rule) { - Lineage copy = *this; - copy.rule = rule; - return copy; + lineage.rule = rule; + return std::move(lineage); } - Lineage Lineage::operator+(std::shared_ptr scenario) const + Lineage operator+(Lineage lineage, std::shared_ptr scenario) { - Lineage copy = *this; - copy.scenario = scenario; - return copy; + lineage.scenario = scenario; + return std::move(lineage); } - Lineage Lineage::operator+(std::shared_ptr examples) const + Lineage operator+(Lineage lineage, std::shared_ptr examples) { - Lineage copy = *this; - copy.examples = examples; - return copy; + lineage.examples = examples; + return std::move(lineage); } - Lineage Lineage::operator+(std::shared_ptr tableRow) const + Lineage operator+(Lineage lineage, std::shared_ptr tableRow) { - Lineage copy = *this; - copy.tableRow = tableRow; - return copy; + lineage.tableRow = tableRow; + return std::move(lineage); } - Lineage Lineage::operator+(std::uint32_t featureIndex) const + Lineage operator+(Lineage lineage, std::uint32_t featureIndex) { - Lineage copy = *this; - copy.featureIndex = featureIndex; - return copy; + lineage.featureIndex = featureIndex; + return std::move(lineage); } std::string Lineage::GetUniqueFeatureName() const diff --git a/cucumber_cpp/library/Query.hpp b/cucumber_cpp/library/Query.hpp index c0014634..59f13a79 100644 --- a/cucumber_cpp/library/Query.hpp +++ b/cucumber_cpp/library/Query.hpp @@ -47,14 +47,6 @@ namespace cucumber_cpp::library { struct Lineage { - Lineage operator+(std::shared_ptr gherkinDocument) const; - Lineage operator+(std::shared_ptr feature) const; - Lineage operator+(std::shared_ptr rule) const; - Lineage operator+(std::shared_ptr scenario) const; - Lineage operator+(std::shared_ptr examples) const; - Lineage operator+(std::shared_ptr tableRow) const; - Lineage operator+(std::uint32_t featureIndex) const; - std::string GetUniqueFeatureName() const; std::string GetScenarioAndOrRuleName() const; @@ -68,6 +60,14 @@ namespace cucumber_cpp::library std::uint32_t featureIndex{ 0 }; }; + Lineage operator+(Lineage lineage, std::shared_ptr gherkinDocument); + Lineage operator+(Lineage lineage, std::shared_ptr feature); + Lineage operator+(Lineage lineage, std::shared_ptr rule); + Lineage operator+(Lineage lineage, std::shared_ptr scenario); + Lineage operator+(Lineage lineage, std::shared_ptr examples); + Lineage operator+(Lineage lineage, std::shared_ptr tableRow); + Lineage operator+(Lineage lineage, std::uint32_t featureIndex); + struct Query { Query& operator+=(const cucumber::messages::envelope& envelope); From ba22049c90b1052ac6be364d6f5f6701e98304ef Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:44:09 +0000 Subject: [PATCH 050/196] removed TraceTime --- cucumber_cpp/library/CMakeLists.txt | 2 -- cucumber_cpp/library/TraceTime.cpp | 23 ----------------------- cucumber_cpp/library/TraceTime.hpp | 24 ------------------------ 3 files changed, 49 deletions(-) delete mode 100644 cucumber_cpp/library/TraceTime.cpp delete mode 100644 cucumber_cpp/library/TraceTime.hpp diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index 0e17335d..2cedc8d2 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -22,8 +22,6 @@ target_sources(cucumber_cpp.library PRIVATE StepRegistry.cpp StepRegistry.hpp Steps.hpp - TraceTime.cpp - TraceTime.hpp ) target_include_directories(cucumber_cpp.library PUBLIC diff --git a/cucumber_cpp/library/TraceTime.cpp b/cucumber_cpp/library/TraceTime.cpp deleted file mode 100644 index 8ae3b71c..00000000 --- a/cucumber_cpp/library/TraceTime.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// #include "cucumber_cpp/library/TraceTime.hpp" -// #include - -// namespace cucumber_cpp::library -// { -// void TraceTime::Start() -// { -// timeStart = std::chrono::high_resolution_clock::now(); -// } - -// void TraceTime::Stop() -// { -// timeStop = std::chrono::high_resolution_clock::now(); -// } - -// TraceTime::Duration TraceTime::Delta() const -// { -// if (timeStop != TimePoint{}) -// return timeStop - timeStart; - -// return std::chrono::high_resolution_clock::now() - timeStart; -// } -// } diff --git a/cucumber_cpp/library/TraceTime.hpp b/cucumber_cpp/library/TraceTime.hpp deleted file mode 100644 index 815217fd..00000000 --- a/cucumber_cpp/library/TraceTime.hpp +++ /dev/null @@ -1,24 +0,0 @@ -// #ifndef CUCUMBER_CPP_TRACETIME_HPP -// #define CUCUMBER_CPP_TRACETIME_HPP - -// #include - -// namespace cucumber_cpp::library -// { -// struct TraceTime -// { -// using TimePoint = std::chrono::time_point; -// using Duration = TimePoint::duration; - -// void Start(); -// void Stop(); - -// [[nodiscard]] Duration Delta() const; - -// private: -// TimePoint timeStart{}; -// TimePoint timeStop{}; -// }; -// } - -// #endif From 6a2492cc81a3cf1361f2493dbc6e7ee03f93773e Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:46:02 +0000 Subject: [PATCH 051/196] Refactored RunCucumber --- cucumber_cpp/library/api/RunCucumber.cpp | 15 ++++++++------- .../library/assemble/AssembleTestSuites.cpp | 10 +++++----- .../library/assemble/AssembleTestSuites.hpp | 2 +- cucumber_cpp/library/formatter/Formatter.cpp | 2 +- cucumber_cpp/library/formatter/Formatter.hpp | 4 ++-- .../library/formatter/helper/IssueHelpers.cpp | 2 +- .../library/formatter/helper/IssueHelpers.hpp | 2 +- .../formatter/helper/TestCaseAttemptFormatter.cpp | 2 +- .../formatter/helper/TestCaseAttemptFormatter.hpp | 2 +- .../formatter/helper/TestCaseAttemptParser.cpp | 2 +- .../formatter/helper/TestCaseAttemptParser.hpp | 2 +- cucumber_cpp/library/runtime/Coordinator.cpp | 2 +- cucumber_cpp/library/runtime/Coordinator.hpp | 4 ++-- cucumber_cpp/library/runtime/MakeRuntime.cpp | 4 ++-- cucumber_cpp/library/runtime/MakeRuntime.hpp | 4 ++-- .../library/runtime/SerialRuntimeAdapter.cpp | 2 +- .../library/runtime/SerialRuntimeAdapter.hpp | 4 ++-- cucumber_cpp/library/runtime/TestCaseRunner.cpp | 2 +- cucumber_cpp/library/runtime/TestCaseRunner.hpp | 4 ++-- cucumber_cpp/library/runtime/Worker.cpp | 2 +- cucumber_cpp/library/runtime/Worker.hpp | 4 ++-- 21 files changed, 39 insertions(+), 38 deletions(-) diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 0f8ff875..da90ab90 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -2,6 +2,7 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/location.hpp" #include "cucumber/messages/parameter_type.hpp" +#include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/step_definition_pattern.hpp" #include "cucumber_cpp/CucumberCpp.hpp" @@ -28,7 +29,7 @@ namespace cucumber_cpp::library::api { namespace { - void EmitParameters(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) + void EmitParameters(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { for (const auto& [name, parameter] : supportCodeLibrary.parameterRegistry.GetParameters()) { @@ -52,13 +53,13 @@ namespace cucumber_cpp::library::api } } - void EmitUndefinedParameters(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster) + void EmitUndefinedParameters(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { for (const auto& parameter : supportCodeLibrary.undefinedParameters.definitions) broadcaster.BroadcastEvent({ .undefined_parameter_type = parameter }); } - void EmitStepDefinitions(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) + void EmitStepDefinitions(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { for (const auto& stepDefinition : supportCodeLibrary.stepRegistry.StepDefinitions()) { @@ -78,7 +79,7 @@ namespace cucumber_cpp::library::api } } - void EmitTestCaseHooks(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster) + void EmitTestCaseHooks(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { const auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::before); @@ -91,7 +92,7 @@ namespace cucumber_cpp::library::api broadcaster.BroadcastEvent({ .hook = std::move(hook) }); } - void EmitTestRunHooks(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster) + void EmitTestRunHooks(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { const auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::beforeAll); @@ -104,7 +105,7 @@ namespace cucumber_cpp::library::api broadcaster.BroadcastEvent({ .hook = std::move(hook) }); } - void EmitSupportCodeMessages(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) + void EmitSupportCodeMessages(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { EmitParameters(supportCodeLibrary, broadcaster, idGenerator); @@ -112,7 +113,7 @@ namespace cucumber_cpp::library::api supportCodeLibrary.stepRegistry.LoadSteps(); EmitUndefinedParameters(supportCodeLibrary, broadcaster); - EmitStepDefinitions(supportCodeLibrary, broadcaster, idGenerator); + EmitStepDefinitions(supportCodeLibrary, broadcaster); supportCodeLibrary.hookRegistry.LoadHooks(); EmitTestCaseHooks(supportCodeLibrary, broadcaster); diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index cd26f163..9c210c96 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -40,19 +40,19 @@ namespace cucumber_cpp::library::assemble return pair.second.has_value(); } - void AssembleBeforeHooks(support::SupportCodeLibrary supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) + void AssembleBeforeHooks(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::before, pickleSource.pickle->tags)) testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); } - void AssembleAfterHooks(support::SupportCodeLibrary supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) + void AssembleAfterHooks(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags) | std::views::reverse) testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); } - void AssembleSteps(support::SupportCodeLibrary supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) + void AssembleSteps(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { for (const auto& step : pickleSource.pickle->steps) { @@ -77,7 +77,7 @@ namespace cucumber_cpp::library::assemble } } - void AssembleTestSteps(support::SupportCodeLibrary supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) + void AssembleTestSteps(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { AssembleBeforeHooks(supportCodeLibrary, pickleSource, testCase, idGenerator); AssembleSteps(supportCodeLibrary, pickleSource, testCase, idGenerator); @@ -85,7 +85,7 @@ namespace cucumber_cpp::library::assemble } } - std::vector AssembleTestSuites(support::SupportCodeLibrary supportCodeLibrary, + std::vector AssembleTestSuites(support::SupportCodeLibrary& supportCodeLibrary, std::string_view testRunStartedId, util::Broadcaster& broadcaster, const std::list& sourcedPickles, diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.hpp b/cucumber_cpp/library/assemble/AssembleTestSuites.hpp index 10b0988b..90bc52c3 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.hpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.hpp @@ -13,7 +13,7 @@ namespace cucumber_cpp::library::assemble { std::vector AssembleTestSuites( - support::SupportCodeLibrary supportCodeLibrary, + support::SupportCodeLibrary& supportCodeLibrary, std::string_view testRunStartedId, util::Broadcaster& broadcaster, const std::list& sourcedPickles, diff --git a/cucumber_cpp/library/formatter/Formatter.cpp b/cucumber_cpp/library/formatter/Formatter.cpp index ab18941b..e7202759 100644 --- a/cucumber_cpp/library/formatter/Formatter.cpp +++ b/cucumber_cpp/library/formatter/Formatter.cpp @@ -8,7 +8,7 @@ namespace cucumber_cpp::library::formatter { - Formatter::Formatter(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream) + Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream) : util::Listener{ broadcaster, [this](const cucumber::messages::envelope& envelope) { OnEnvelope(envelope); diff --git a/cucumber_cpp/library/formatter/Formatter.hpp b/cucumber_cpp/library/formatter/Formatter.hpp index 98b97051..e62f2a40 100644 --- a/cucumber_cpp/library/formatter/Formatter.hpp +++ b/cucumber_cpp/library/formatter/Formatter.hpp @@ -14,13 +14,13 @@ namespace cucumber_cpp::library::formatter struct Formatter : util::Listener { - Formatter(support::SupportCodeLibrary supportCodeLibrary, util::Broadcaster& broadcaster, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream = std::cout); + Formatter(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream = std::cout); virtual ~Formatter() = default; protected: virtual void OnEnvelope(const cucumber::messages::envelope& envelope) = 0; - support::SupportCodeLibrary supportCodeLibrary; + support::SupportCodeLibrary& supportCodeLibrary; util::Broadcaster& broadcaster; const helper::EventDataCollector& eventDataCollector; std::ostream& outputStream; diff --git a/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp b/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp index 3cc064ea..64359580 100644 --- a/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp +++ b/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp @@ -16,7 +16,7 @@ namespace cucumber_cpp::library::formatter::helper { - std::ostream& FormatIssue(std::ostream& outputStream, std::size_t number, const TestCaseAttempt& testCaseAttempt, support::SupportCodeLibrary supportCodeLibrary, bool printAttachments) + std::ostream& FormatIssue(std::ostream& outputStream, std::size_t number, const TestCaseAttempt& testCaseAttempt, support::SupportCodeLibrary& supportCodeLibrary, bool printAttachments) { using std::operator""sv; diff --git a/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp b/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp index 91584208..6ff37037 100644 --- a/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp +++ b/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp @@ -9,7 +9,7 @@ namespace cucumber_cpp::library::formatter::helper { - std::ostream& FormatIssue(std::ostream& outputStream, std::size_t number, const TestCaseAttempt& testCaseAttempt, support::SupportCodeLibrary supportCodeLibrary, bool printAttachments = true); + std::ostream& FormatIssue(std::ostream& outputStream, std::size_t number, const TestCaseAttempt& testCaseAttempt, support::SupportCodeLibrary& supportCodeLibrary, bool printAttachments = true); } #endif diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp index edff687f..d9a75c20 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp @@ -80,7 +80,7 @@ namespace cucumber_cpp::library::formatter::helper } } - std::string FormatTestCaseAttempt(support::SupportCodeLibrary supportCodeLibrary, const TestCaseAttempt& testCaseAttempt, bool printAttachments) + std::string FormatTestCaseAttempt(support::SupportCodeLibrary& supportCodeLibrary, const TestCaseAttempt& testCaseAttempt, bool printAttachments) { const auto parsed = ParseTestCaseAttempt(supportCodeLibrary, testCaseAttempt); diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp index 559a59b3..95fa7e1e 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp @@ -7,7 +7,7 @@ namespace cucumber_cpp::library::formatter::helper { - std::string FormatTestCaseAttempt(support::SupportCodeLibrary supportCodeLibrary, const TestCaseAttempt& testCaseAttempt, bool printAttachments); + std::string FormatTestCaseAttempt(support::SupportCodeLibrary& supportCodeLibrary, const TestCaseAttempt& testCaseAttempt, bool printAttachments); } #endif diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp index a05bf819..69e197e7 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp @@ -77,7 +77,7 @@ namespace cucumber_cpp::library::formatter::helper } } - ParsedTestCaseAttempt ParseTestCaseAttempt(support::SupportCodeLibrary supportCodeLibrary, const TestCaseAttempt& testCaseAttempt) + ParsedTestCaseAttempt ParseTestCaseAttempt(support::SupportCodeLibrary& supportCodeLibrary, const TestCaseAttempt& testCaseAttempt) { const auto& testCase = testCaseAttempt.testCase; const auto& pickle = testCaseAttempt.pickle; diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp index 0360f1db..8e786056 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp @@ -47,7 +47,7 @@ namespace cucumber_cpp::library::formatter::helper std::vector parsedTestSteps; }; - ParsedTestCaseAttempt ParseTestCaseAttempt(support::SupportCodeLibrary supportCodeLibrary, const TestCaseAttempt& testCaseAttempt); + ParsedTestCaseAttempt ParseTestCaseAttempt(support::SupportCodeLibrary& supportCodeLibrary, const TestCaseAttempt& testCaseAttempt); } #endif diff --git a/cucumber_cpp/library/runtime/Coordinator.cpp b/cucumber_cpp/library/runtime/Coordinator.cpp index 7a7ba189..79b91efc 100644 --- a/cucumber_cpp/library/runtime/Coordinator.cpp +++ b/cucumber_cpp/library/runtime/Coordinator.cpp @@ -18,7 +18,7 @@ namespace cucumber_cpp::library::runtime util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, std::unique_ptr&& runtimeAdapter, - support::SupportCodeLibrary supportCodeLibrary) + support::SupportCodeLibrary& supportCodeLibrary) : testRunStartedId{ testRunStartedId } , broadcaster{ broadcaster } , idGenerator{ idGenerator } diff --git a/cucumber_cpp/library/runtime/Coordinator.hpp b/cucumber_cpp/library/runtime/Coordinator.hpp index 1d32cdb9..5124b3eb 100644 --- a/cucumber_cpp/library/runtime/Coordinator.hpp +++ b/cucumber_cpp/library/runtime/Coordinator.hpp @@ -17,7 +17,7 @@ namespace cucumber_cpp::library::runtime util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, std::unique_ptr&& runtimeAdapter, - support::SupportCodeLibrary supportCodeLibrary); + support::SupportCodeLibrary& supportCodeLibrary); bool Run() override; @@ -26,7 +26,7 @@ namespace cucumber_cpp::library::runtime util::Broadcaster& broadcaster; cucumber::gherkin::id_generator_ptr idGenerator; std::unique_ptr runtimeAdapter; - support::SupportCodeLibrary supportCodeLibrary; + support::SupportCodeLibrary& supportCodeLibrary; }; } diff --git a/cucumber_cpp/library/runtime/MakeRuntime.cpp b/cucumber_cpp/library/runtime/MakeRuntime.cpp index b713ae2c..3e2b46dc 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.cpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.cpp @@ -12,7 +12,7 @@ namespace cucumber_cpp::library::runtime { - std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) + std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary& supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) { return std::make_unique( testRunStartedId, @@ -24,7 +24,7 @@ namespace cucumber_cpp::library::runtime programContext); } - std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) + std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary& supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext) { const auto testRunStartedId{ idGenerator->next_id() }; return std::make_unique( diff --git a/cucumber_cpp/library/runtime/MakeRuntime.hpp b/cucumber_cpp/library/runtime/MakeRuntime.hpp index 7c79896a..59ac9e09 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.hpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.hpp @@ -12,9 +12,9 @@ namespace cucumber_cpp::library::runtime { - std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); + std::unique_ptr MakeAdapter(const support::RunOptions::Runtime& options, std::string testRunStartedId, util::Broadcaster& broadcaster, support::SupportCodeLibrary& supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); - std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); + std::unique_ptr MakeRuntime(const support::RunOptions::Runtime& options, util::Broadcaster& broadcaster, const std::list& sourcedPickles, support::SupportCodeLibrary& supportCodeLibrary, cucumber::gherkin::id_generator_ptr idGenerator, Context& programContext); } #endif diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp index a8334f74..0fefeb5d 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp @@ -19,7 +19,7 @@ namespace cucumber_cpp::library::runtime cucumber::gherkin::id_generator_ptr idGenerator, const std::list& sourcedPickles, const support::RunOptions::Runtime& options, - support::SupportCodeLibrary supportCodeLibrary, + support::SupportCodeLibrary& supportCodeLibrary, Context& programContext) : testRunStartedId{ testRunStartedId } , broadcaster{ broadcaster } diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp index e221439f..4da486ac 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp @@ -18,7 +18,7 @@ namespace cucumber_cpp::library::runtime cucumber::gherkin::id_generator_ptr idGenerator, const std::list& sourcedPickles, const support::RunOptions::Runtime& options, - support::SupportCodeLibrary supportCodeLibrary, + support::SupportCodeLibrary& supportCodeLibrary, Context& programContext); bool Run() override; @@ -29,7 +29,7 @@ namespace cucumber_cpp::library::runtime cucumber::gherkin::id_generator_ptr idGenerator; const std::list& sourcedPickles; const support::RunOptions::Runtime& options; - support::SupportCodeLibrary supportCodeLibrary; + support::SupportCodeLibrary& supportCodeLibrary; Context& programContext; }; } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index ae7971a3..faf86985 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -65,7 +65,7 @@ namespace cucumber_cpp::library::runtime const cucumber::messages::test_case& testCase, std::size_t retries, bool skip, - support::SupportCodeLibrary supportCodeLibrary, + support::SupportCodeLibrary& supportCodeLibrary, Context& testSuiteContext) : broadcaster{ broadcaster } , idGenerator{ idGenerator } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.hpp b/cucumber_cpp/library/runtime/TestCaseRunner.hpp index 1104d3a1..e1c9454c 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.hpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.hpp @@ -31,7 +31,7 @@ namespace cucumber_cpp::library::runtime const cucumber::messages::test_case& testCase, std::size_t retries, bool skip, - support::SupportCodeLibrary supportCodeLibrary, + support::SupportCodeLibrary& supportCodeLibrary, Context& testSuiteContext); cucumber::messages::test_step_result_status Run(); @@ -58,7 +58,7 @@ namespace cucumber_cpp::library::runtime const cucumber::messages::test_case& testCase; std::size_t maximumAttempts; bool skip; - support::SupportCodeLibrary supportCodeLibrary; + support::SupportCodeLibrary& supportCodeLibrary; Context& testSuiteContext; std::vector testStepResults; diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index 275a5403..cf13ab73 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -67,7 +67,7 @@ namespace cucumber_cpp::library::runtime util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, const support::RunOptions::Runtime& options, - support::SupportCodeLibrary supportCodeLibrary, + support::SupportCodeLibrary& supportCodeLibrary, Context& programContext) : testRunStartedId{ testRunStartedId } , broadcaster{ broadcaster } diff --git a/cucumber_cpp/library/runtime/Worker.hpp b/cucumber_cpp/library/runtime/Worker.hpp index 298b74c7..ce170639 100644 --- a/cucumber_cpp/library/runtime/Worker.hpp +++ b/cucumber_cpp/library/runtime/Worker.hpp @@ -74,7 +74,7 @@ namespace cucumber_cpp::library::runtime util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, const support::RunOptions::Runtime& options, - support::SupportCodeLibrary supportCodeLibrary, + support::SupportCodeLibrary& supportCodeLibrary, Context& programContext); std::vector RunBeforeAllHooks(); @@ -95,7 +95,7 @@ namespace cucumber_cpp::library::runtime util::Broadcaster& broadcaster; cucumber::gherkin::id_generator_ptr idGenerator; const support::RunOptions::Runtime& options; - support::SupportCodeLibrary supportCodeLibrary; + support::SupportCodeLibrary& supportCodeLibrary; Context& programContext; }; } From 54278d07bcc1edbb1bac704e27ab5bd1a9aff837 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:54:47 +0000 Subject: [PATCH 052/196] minor refactorings --- cucumber_cpp/library/api/RunCucumber.cpp | 16 ++++++++-------- .../library/assemble/AssembleTestSuites.cpp | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index da90ab90..be514cf6 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -81,27 +81,27 @@ namespace cucumber_cpp::library::api void EmitTestCaseHooks(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { - const auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::before); + auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::before); - for (const auto& hook : beforeAllHooks) + for (auto& hook : beforeAllHooks) broadcaster.BroadcastEvent({ .hook = std::move(hook) }); - const auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::after); + auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::after); - for (const auto& hook : afterAllHooks) + for (auto& hook : afterAllHooks) broadcaster.BroadcastEvent({ .hook = std::move(hook) }); } void EmitTestRunHooks(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { - const auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::beforeAll); + auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::beforeAll); - for (const auto& hook : beforeAllHooks) + for (auto& hook : beforeAllHooks) broadcaster.BroadcastEvent({ .hook = std::move(hook) }); - const auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::afterAll); + auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::afterAll); - for (const auto& hook : afterAllHooks) + for (auto& hook : afterAllHooks) broadcaster.BroadcastEvent({ .hook = std::move(hook) }); } diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 9c210c96..3b1c1ba5 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -121,7 +121,7 @@ namespace cucumber_cpp::library::assemble std::vector assembledTestSuites; assembledTestSuites.reserve(assembledTestSuiteMap.size()); - for (auto uri : testUris) + for (const auto& uri : testUris) assembledTestSuites.emplace_back(std::move(assembledTestSuiteMap.at(uri))); return assembledTestSuites; From 867e06ef20e1bf8ac524d756609084821fcf3fc3 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:57:27 +0000 Subject: [PATCH 053/196] minor refactorings --- .../library/cucumber_expression/test/TestTransformation.cpp | 2 -- cucumber_cpp/library/formatter/PrettyPrinter.cpp | 4 ---- cucumber_cpp/library/formatter/PrettyPrinter.hpp | 1 - 3 files changed, 7 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp b/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp index 3d94d83f..8b51ed69 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp @@ -1,4 +1,3 @@ - #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "yaml-cpp/node/node.h" @@ -10,7 +9,6 @@ #include #include #include -#include #include #include #include diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyPrinter.cpp index 8425e605..1f5a3eee 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.cpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.cpp @@ -185,10 +185,6 @@ namespace cucumber_cpp::library::formatter void PrettyPrinter::PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) { - if (title.length() > maxContentLength) - { - std::abort(); - } const auto padding = maxContentLength - title.length(); if (uri.has_value() && line.has_value()) diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.hpp b/cucumber_cpp/library/formatter/PrettyPrinter.hpp index 6123bb77..f7a4ea34 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.hpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.hpp @@ -19,7 +19,6 @@ #include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" #include #include -#include #include #include #include From 0715a6e9944005ede5a5abe5b27cfd6a6e0c2542 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 23 Dec 2025 16:59:52 +0000 Subject: [PATCH 054/196] remove unused files --- compatibility/data-tables/out.ndjson | 14 -------------- compatibility/empty/out.ndjson | 8 -------- 2 files changed, 22 deletions(-) delete mode 100644 compatibility/data-tables/out.ndjson delete mode 100644 compatibility/empty/out.ndjson diff --git a/compatibility/data-tables/out.ndjson b/compatibility/data-tables/out.ndjson deleted file mode 100644 index a72cd150..00000000 --- a/compatibility/data-tables/out.ndjson +++ /dev/null @@ -1,14 +0,0 @@ -{"source":{"data":"Feature: Data Tables\n Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.\n\n Scenario: transposed table\n When the following table is transposed:\n | a | b |\n | 1 | 2 |\n Then it should be:\n | a | 1 |\n | b | 2 |\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.feature"}} -{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"6","keyword":"Scenario","location":{"column":3,"line":7},"name":"transposed table","steps":[{"dataTable":{"location":{"column":7,"line":9},"rows":[{"cells":[{"location":{"column":9,"line":9},"value":"a"},{"location":{"column":13,"line":9},"value":"b"}],"id":"0","location":{"column":7,"line":9}},{"cells":[{"location":{"column":9,"line":10},"value":"1"},{"location":{"column":13,"line":10},"value":"2"}],"id":"1","location":{"column":7,"line":10}}]},"id":"2","keyword":"When ","keywordType":"Action","location":{"column":5,"line":8},"text":"the following table is transposed:"},{"dataTable":{"location":{"column":7,"line":12},"rows":[{"cells":[{"location":{"column":9,"line":12},"value":"a"},{"location":{"column":13,"line":12},"value":"1"}],"id":"3","location":{"column":7,"line":12}},{"cells":[{"location":{"column":9,"line":13},"value":"b"},{"location":{"column":13,"line":13},"value":"2"}],"id":"4","location":{"column":7,"line":13}}]},"id":"5","keyword":"Then ","keywordType":"Outcome","location":{"column":5,"line":11},"text":"it should be:"}],"tags":[]}}],"description":" Data Tables can be placed underneath a step and will be passed as the last\n argument to the step definition.\n\n They can be used to represent richer data structures, and can be transformed to other data-types.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Data Tables","tags":[]},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.feature"}} -{"pickle":{"astNodeIds":["6"],"id":"9","language":"en","location":{"column":3,"line":7},"name":"transposed table","steps":[{"argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"b"}]},{"cells":[{"value":"1"},{"value":"2"}]}]}},"astNodeIds":["2"],"id":"7","text":"the following table is transposed:","type":"Action"},{"argument":{"dataTable":{"rows":[{"cells":[{"value":"a"},{"value":"1"}]},{"cells":[{"value":"b"},{"value":"2"}]}]}},"astNodeIds":["5"],"id":"8","text":"it should be:","type":"Outcome"}],"tags":[],"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.feature"}} -{"stepDefinition":{"id":"10","pattern":{"source":"the following table is transposed:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":10},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.cpp"}}} -{"stepDefinition":{"id":"11","pattern":{"source":"it should be:","type":"CUCUMBER_EXPRESSION"},"sourceReference":{"location":{"line":24},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/data-tables/data-tables.cpp"}}} -{"testRunStarted":{"id":"12","timestamp":{"nanos":0,"seconds":0}}} -{"testCase":{"id":"13","pickleId":"9","testRunStartedId":"12","testSteps":[{"id":"14","pickleStepId":"7","stepDefinitionIds":["10"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]},{"id":"15","pickleStepId":"8","stepDefinitionIds":["11"],"stepMatchArgumentsLists":[{"stepMatchArguments":[]}]}]}} -{"testCaseStarted":{"attempt":0,"id":"16","testCaseId":"13","timestamp":{"nanos":1000000,"seconds":0}}} -{"testStepStarted":{"testCaseStartedId":"16","testStepId":"14","timestamp":{"nanos":2000000,"seconds":0}}} -{"testStepFinished":{"testCaseStartedId":"16","testStepId":"14","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":3000000,"seconds":0}}} -{"testStepStarted":{"testCaseStartedId":"16","testStepId":"15","timestamp":{"nanos":4000000,"seconds":0}}} -{"testStepFinished":{"testCaseStartedId":"16","testStepId":"15","testStepResult":{"duration":{"nanos":1000000,"seconds":0},"status":"PASSED"},"timestamp":{"nanos":5000000,"seconds":0}}} -{"testCaseFinished":{"testCaseStartedId":"16","timestamp":{"nanos":6000000,"seconds":0},"willBeRetried":false}} -{"testRunFinished":{"success":true,"testRunStartedId":"12","timestamp":{"nanos":7000000,"seconds":0}}} diff --git a/compatibility/empty/out.ndjson b/compatibility/empty/out.ndjson deleted file mode 100644 index 4da1f103..00000000 --- a/compatibility/empty/out.ndjson +++ /dev/null @@ -1,8 +0,0 @@ -{"source":{"data":"Feature: Empty Scenarios\n Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.\n\n Scenario: Blank Scenario\n","mediaType":"text/x.cucumber.gherkin+plain","uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/empty/empty.feature"}} -{"gherkinDocument":{"comments":[],"feature":{"children":[{"scenario":{"description":"","examples":[],"id":"0","keyword":"Scenario","location":{"column":3,"line":7},"name":"Blank Scenario","steps":[],"tags":[]}}],"description":" Sometimes we want to quickly jot down a new scenario without specifying any actual steps\n for what should be executed.\n\n In this instance we want to stipulate what should / shouldn't run and what the output is.","keyword":"Feature","language":"en","location":{"column":1,"line":1},"name":"Empty Scenarios","tags":[]},"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/empty/empty.feature"}} -{"pickle":{"astNodeIds":["0"],"id":"1","language":"en","location":{"column":3,"line":7},"name":"Blank Scenario","steps":[],"tags":[],"uri":"/workspaces/amp-cucumber-cpp-runner/compatibility/empty/empty.feature"}} -{"testRunStarted":{"id":"2","timestamp":{"nanos":0,"seconds":0}}} -{"testCase":{"id":"3","pickleId":"1","testRunStartedId":"2","testSteps":[]}} -{"testCaseStarted":{"attempt":0,"id":"4","testCaseId":"3","timestamp":{"nanos":1000000,"seconds":0}}} -{"testCaseFinished":{"testCaseStartedId":"4","timestamp":{"nanos":2000000,"seconds":0},"willBeRetried":false}} -{"testRunFinished":{"success":true,"testRunStartedId":"2","timestamp":{"nanos":3000000,"seconds":0}}} From 2d07945cc3648061c463a8a0c2ee36c2bf8280e1 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 24 Dec 2025 10:30:18 +0000 Subject: [PATCH 055/196] fixed co-pilot comments --- compatibility/data-tables/data-tables.cpp | 51 ++- cucumber_cpp/example/steps/Steps.cpp | 11 +- cucumber_cpp/library/StepRegistry.hpp | 7 +- cucumber_cpp/library/engine/CMakeLists.txt | 2 +- cucumber_cpp/library/engine/Step.cpp | 4 +- cucumber_cpp/library/engine/Step.hpp | 5 +- .../library/engine/test/CMakeLists.txt | 9 +- .../engine/test/TestContextManager.cpp | 139 -------- .../engine/test/TestFailureHandler.cpp | 57 ---- .../engine/test/TestFeatureFactory.cpp | 316 ------------------ .../library/engine/test/TestHookExecutor.cpp | 98 ------ .../engine/test/TestHookExecutorHooks.cpp | 51 --- cucumber_cpp/library/engine/test/TestStep.cpp | 88 ++--- .../test_helper/ContextManagerInstance.hpp | 48 --- .../test_helper/FailureHandlerFixture.cpp | 19 -- .../test_helper/FailureHandlerFixture.hpp | 24 -- .../test_helper/TestExecutionInstance.hpp | 66 ---- .../library/formatter/PrettyPrinter.cpp | 4 + .../library/formatter/SummaryFormatter.cpp | 8 +- .../library/formatter/helper/CMakeLists.txt | 2 + .../formatter/helper/LocationHelpers.cpp | 17 + .../formatter/helper/LocationHelpers.hpp | 15 + .../helper/TestCaseAttemptFormatter.cpp | 12 +- .../library/runtime/TestCaseRunner.cpp | 35 +- cucumber_cpp/library/support/Join.cpp | 2 +- 25 files changed, 116 insertions(+), 974 deletions(-) delete mode 100644 cucumber_cpp/library/engine/test/TestContextManager.cpp delete mode 100644 cucumber_cpp/library/engine/test/TestFailureHandler.cpp delete mode 100644 cucumber_cpp/library/engine/test/TestFeatureFactory.cpp delete mode 100644 cucumber_cpp/library/engine/test/TestHookExecutor.cpp delete mode 100644 cucumber_cpp/library/engine/test/TestHookExecutorHooks.cpp delete mode 100644 cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp delete mode 100644 cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.cpp delete mode 100644 cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.hpp delete mode 100644 cucumber_cpp/library/engine/test_helper/TestExecutionInstance.hpp create mode 100644 cucumber_cpp/library/formatter/helper/LocationHelpers.cpp create mode 100644 cucumber_cpp/library/formatter/helper/LocationHelpers.hpp diff --git a/compatibility/data-tables/data-tables.cpp b/compatibility/data-tables/data-tables.cpp index ab88df3a..2222bb90 100644 --- a/compatibility/data-tables/data-tables.cpp +++ b/compatibility/data-tables/data-tables.cpp @@ -1,52 +1,39 @@ -#include "cucumber/messages/pickle_table_row.hpp" +#include "cucumber/messages/pickle_table.hpp" #include "cucumber_cpp/CucumberCpp.hpp" -#include "gmock/gmock.h" -#include "gtest/gtest.h" #include #include -#include +#include #include STEP(R"(the following table is transposed:)") { - std::vector transposedTable; - transposedTable.reserve(this->table->front().cells.size()); - for (std::size_t colIdx = 0; colIdx < this->table->front().cells.size(); ++colIdx) - transposedTable.emplace_back().cells.resize(this->table->size()); + auto transposedTable = context.Emplace(); + transposedTable->rows.reserve(this->dataTable->rows[0].cells.size()); + for (std::size_t colIdx = 0; colIdx < this->dataTable->rows[0].cells.size(); ++colIdx) + transposedTable->rows.emplace_back().cells.resize(this->dataTable->rows.size()); - for (std::size_t rowIdx = 0; rowIdx < this->table->size(); ++rowIdx) - for (std::size_t colIdx = 0; colIdx < this->table->begin()[rowIdx].cells.size(); ++colIdx) - transposedTable[colIdx].cells[rowIdx] = this->table->begin()[rowIdx].cells[colIdx]; - - context.Insert(transposedTable); + for (std::size_t rowIdx = 0; rowIdx < this->dataTable->rows.size(); ++rowIdx) + for (std::size_t colIdx = 0; colIdx < this->dataTable->rows[rowIdx].cells.size(); ++colIdx) + transposedTable->rows[colIdx].cells[rowIdx] = this->dataTable->rows[rowIdx].cells[colIdx]; } STEP(R"(it should be:)") { - std::span expected = context.Get>(); - const auto& actual = *table; - const auto rows = expected.size(); - ASSERT_THAT(rows, testing::Eq(actual.size())); + const auto& actualTable = context.Get(); + const auto& expectedTalbe = dataTable; + + const auto rows = actualTable.rows.size(); + ASSERT_THAT(rows, testing::Eq(expectedTalbe->rows.size())); + for (auto rowIdx = 0; rowIdx < rows; ++rowIdx) { - const auto columns = expected[rowIdx].cells.size(); - ASSERT_THAT(columns, testing::Eq(actual[rowIdx].cells.size())); + const auto columns = expectedTalbe->rows[rowIdx].cells.size(); + ASSERT_THAT(columns, testing::Eq(actualTable.rows[rowIdx].cells.size())); for (auto colIdx = 0; colIdx < columns; ++colIdx) { - const auto& expectedCell = expected[rowIdx].cells[colIdx]; - const auto& actualCell = actual[rowIdx].cells[colIdx]; + const auto& expectedCell = expectedTalbe->rows[rowIdx].cells[colIdx]; + const auto& actualCell = actualTable.rows[rowIdx].cells[colIdx]; EXPECT_THAT(expectedCell.value, testing::StrEq(actualCell.value)) << "at row " << rowIdx << " column " << colIdx; } } } - -// import assert from 'node:assert' -// import { DataTable, When, Then } from '@cucumber/fake-cucumber' - -// When('the following table is transposed:', function (table: DataTable) { -// this.transposed = table.transpose() -// }) - -// Then('it should be:', function (expected: DataTable) { -// assert.deepStrictEqual(this.transposed.raw(), expected.raw()) -// } diff --git a/cucumber_cpp/example/steps/Steps.cpp b/cucumber_cpp/example/steps/Steps.cpp index f4db211a..a455fc2e 100644 --- a/cucumber_cpp/example/steps/Steps.cpp +++ b/cucumber_cpp/example/steps/Steps.cpp @@ -3,7 +3,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include -#include #include GIVEN(R"(a background step)") @@ -13,11 +12,11 @@ GIVEN(R"(a background step)") GIVEN(R"(a simple data table)") { - [[maybe_unused]] const auto row0col0 = table.value()[0].cells[0].value; - [[maybe_unused]] const auto row0col1 = table.value()[0].cells[1].value; + [[maybe_unused]] const auto row0col0 = dataTable->rows[0].cells[0].value; + [[maybe_unused]] const auto row0col1 = dataTable->rows[0].cells[1].value; - [[maybe_unused]] const auto row1col0 = table.value()[1].cells[0].value; - [[maybe_unused]] const auto row1col1 = table.value()[1].cells[1].value; + [[maybe_unused]] const auto row1col0 = dataTable->rows[1].cells[0].value; + [[maybe_unused]] const auto row1col1 = dataTable->rows[1].cells[1].value; } GIVEN(R"(there are {int} cucumbers)", (std::int32_t num)) @@ -99,7 +98,7 @@ STEP("this step should be skipped") STEP(R"(a step stores the value at row {int} and column {int} from the table:)", (std::int32_t row, std::int32_t column)) { - context.EmplaceAt("cell", table.value()[row].cells[column].value); + context.EmplaceAt("cell", dataTable->rows[row].cells[column].value); } STEP(R"(the value should be {string})", (const std::string& expected_value)) diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index 81089b51..47416a22 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -3,6 +3,7 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/pickle_doc_string.hpp" +#include "cucumber/messages/pickle_table.hpp" #include "cucumber/messages/pickle_table_row.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" #include "cucumber_cpp/library/Body.hpp" @@ -34,12 +35,12 @@ namespace cucumber_cpp::library { - using StepFactory = std::unique_ptr (&)(util::Broadcaster& broadCaster, Context&, engine::StepOrHookStarted stepOrHookStarted, std::optional>, const std::optional&); + using StepFactory = std::unique_ptr (&)(util::Broadcaster& broadCaster, Context&, engine::StepOrHookStarted stepOrHookStarted, const std::optional&, const std::optional&); template - std::unique_ptr StepBodyFactory(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, std::optional> table, const std::optional& docString) + std::unique_ptr StepBodyFactory(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString) { - return std::make_unique(broadCaster, context, stepOrHookStarted, table, docString); + return std::make_unique(broadCaster, context, stepOrHookStarted, dataTable, docString); } struct StepMatch diff --git a/cucumber_cpp/library/engine/CMakeLists.txt b/cucumber_cpp/library/engine/CMakeLists.txt index 5297e8c3..20cdc12b 100644 --- a/cucumber_cpp/library/engine/CMakeLists.txt +++ b/cucumber_cpp/library/engine/CMakeLists.txt @@ -24,6 +24,6 @@ target_link_libraries(cucumber_cpp.library.engine PUBLIC ) if (CCR_BUILD_TESTS) - # add_subdirectory(test) + add_subdirectory(test) # add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index c313f816..abd67624 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -12,9 +12,9 @@ namespace cucumber_cpp::library::engine { - Step::Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, std::optional> table, const std::optional& docString) + Step::Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString) : ExecutionContext{ broadCaster, context, stepOrHookStarted } - , table{ table } + , dataTable{ dataTable } , docString{ docString } {} diff --git a/cucumber_cpp/library/engine/Step.hpp b/cucumber_cpp/library/engine/Step.hpp index 678bbd7b..34758c84 100644 --- a/cucumber_cpp/library/engine/Step.hpp +++ b/cucumber_cpp/library/engine/Step.hpp @@ -5,6 +5,7 @@ // IWYU pragma: friend cucumber_cpp/.* #include "cucumber/messages/pickle_doc_string.hpp" +#include "cucumber/messages/pickle_table.hpp" #include "cucumber/messages/pickle_table_row.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" @@ -20,7 +21,7 @@ namespace cucumber_cpp::library::engine { struct Step : ExecutionContext { - Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, std::optional> table, const std::optional& docString); + Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString); virtual ~Step() = default; virtual void SetUp() @@ -38,7 +39,7 @@ namespace cucumber_cpp::library::engine void When(const std::string& step) const; void Then(const std::string& step) const; - std::optional> table; + const std::optional& dataTable; const std::optional& docString; }; } diff --git a/cucumber_cpp/library/engine/test/CMakeLists.txt b/cucumber_cpp/library/engine/test/CMakeLists.txt index 1f4d530c..efb2fdf9 100644 --- a/cucumber_cpp/library/engine/test/CMakeLists.txt +++ b/cucumber_cpp/library/engine/test/CMakeLists.txt @@ -4,16 +4,11 @@ add_test(NAME cucumber_cpp.library.engine.test COMMAND cucumber_cpp.library.engi target_link_libraries(cucumber_cpp.library.engine.test PUBLIC cucumber_cpp.library.engine gmock_main - cucumber_cpp.library.engine.test_helper - cucumber_cpp.library.engine.test_helper.steps + # cucumber_cpp.library.engine.test_helper + # cucumber_cpp.library.engine.test_helper.steps ) target_sources(cucumber_cpp.library.engine.test PRIVATE - TestContextManager.cpp - TestFailureHandler.cpp - TestFeatureFactory.cpp - TestHookExecutor.cpp - TestHookExecutorHooks.cpp TestStep.cpp TestStringTo.cpp ) diff --git a/cucumber_cpp/library/engine/test/TestContextManager.cpp b/cucumber_cpp/library/engine/test/TestContextManager.cpp deleted file mode 100644 index b048917a..00000000 --- a/cucumber_cpp/library/engine/test/TestContextManager.cpp +++ /dev/null @@ -1,139 +0,0 @@ - -#include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::engine -{ - struct TestContextManager : testing::Test - { - std::shared_ptr contextStorageFactory = std::make_shared(); - cucumber_cpp::library::engine::ContextManager contextManager{ contextStorageFactory }; - - 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, {}, {}, {}, {}, {}, {} }; - }; - - TEST_F(TestContextManager, Construct) - {} - - TEST_F(TestContextManager, ProgramContext) - { - auto& programContext = contextManager.ProgramContext(); - EXPECT_THAT(programContext.ExecutionStatus(), testing::Eq(cucumber_cpp::library::engine::Result::passed)); - } - - TEST_F(TestContextManager, FeatureContextNotAvailableAfterConstruction) - { - ASSERT_THROW(contextManager.FeatureContext(), cucumber_cpp::library::engine::ContextNotAvailable); - - try - { - contextManager.FeatureContext(); - FAIL() << "Expected to throw ContextNotAvailable"; - } - catch (const cucumber_cpp::library::engine::ContextNotAvailable& e) - { - EXPECT_THAT(e.what(), testing::StrEq(R"(FeatureContext not available)")); - } - } - - TEST_F(TestContextManager, RuleContextNotAvailableAfterConstruction) - { - ASSERT_THROW(contextManager.RuleContext(), cucumber_cpp::library::engine::ContextNotAvailable); - - try - { - contextManager.RuleContext(); - FAIL() << "Expected to throw ContextNotAvailable"; - } - catch (const cucumber_cpp::library::engine::ContextNotAvailable& e) - { - EXPECT_THAT(e.what(), testing::StrEq(R"(RuleContext not available)")); - } - } - - TEST_F(TestContextManager, ScenarioContextNotAvailableAfterConstruction) - { - ASSERT_THROW(contextManager.ScenarioContext(), cucumber_cpp::library::engine::ContextNotAvailable); - - try - { - contextManager.ScenarioContext(); - FAIL() << "Expected to throw ContextNotAvailable"; - } - catch (const cucumber_cpp::library::engine::ContextNotAvailable& e) - { - EXPECT_THAT(e.what(), testing::StrEq(R"(ScenarioContext not available)")); - } - } - - TEST_F(TestContextManager, StepContextNotAvailableAfterConstruction) - { - ASSERT_THROW(contextManager.StepContext(), cucumber_cpp::library::engine::ContextNotAvailable); - - try - { - contextManager.StepContext(); - FAIL() << "Expected to throw ContextNotAvailable"; - } - catch (const cucumber_cpp::library::engine::ContextNotAvailable& e) - { - EXPECT_THAT(e.what(), testing::StrEq(R"(StepContext not available)")); - } - } - - TEST_F(TestContextManager, FeatureContextLifetimeManagement) - { - { - auto scoped = contextManager.CreateFeatureContext(feature); - ASSERT_NO_THROW(contextManager.FeatureContext()); - } - ASSERT_ANY_THROW(contextManager.FeatureContext()); - } - - TEST_F(TestContextManager, RuleContextLifetimeManagement) - { - { - auto scopedFeature = contextManager.CreateFeatureContext(feature); - auto scopedRule = contextManager.CreateRuleContext(rule); - - ASSERT_NO_THROW(contextManager.RuleContext()); - } - ASSERT_ANY_THROW(contextManager.RuleContext()); - } - - TEST_F(TestContextManager, ScenarioContextLifetimeManagement) - { - { - auto scopedFeature = contextManager.CreateFeatureContext(feature); - auto scopedRule = contextManager.CreateRuleContext(rule); - auto scopedScenario = contextManager.CreateScenarioContext(scenario); - - ASSERT_NO_THROW(contextManager.ScenarioContext()); - } - ASSERT_ANY_THROW(contextManager.ScenarioContext()); - } - - TEST_F(TestContextManager, StepContextLifetimeManagement) - { - { - auto scopedFeature = contextManager.CreateFeatureContext(feature); - auto scopedRule = contextManager.CreateRuleContext(rule); - auto scopedScenario = contextManager.CreateScenarioContext(scenario); - auto stepContextScope = contextManager.CreateStepContext(step); - - ASSERT_NO_THROW(contextManager.StepContext()); - } - ASSERT_ANY_THROW(contextManager.StepContext()); - } -} diff --git a/cucumber_cpp/library/engine/test/TestFailureHandler.cpp b/cucumber_cpp/library/engine/test/TestFailureHandler.cpp deleted file mode 100644 index 3efde06d..00000000 --- a/cucumber_cpp/library/engine/test/TestFailureHandler.cpp +++ /dev/null @@ -1,57 +0,0 @@ - -#include "cucumber_cpp/library/engine/FailureHandler.hpp" -#include "cucumber_cpp/library/engine/Result.hpp" -#include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp" -#include "cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp" -#include "gtest/gtest.h" -#include -#include - -namespace cucumber_cpp::library::engine -{ - namespace - { - - struct TestFailureHandler : testing::Test - { - - void Error(const char* failure) - { - googleTestEventListener.OnTestPartResult({ testing::TestPartResult::Type::kFatalFailure, "custom_file.cpp", 0, failure }); - } - - test_helper::ContextManagerInstance contextManager; - report::test_helper::ReportForwarderMock reportHandler{ contextManager }; - TestAssertionHandlerImpl testAssertionHandler{ contextManager, reportHandler }; - - private: - GoogleTestEventListener googleTestEventListener{ testAssertionHandler }; - }; - } - - TEST_F(TestFailureHandler, SetContextToFailed) - { - ASSERT_THAT(contextManager.CurrentContext().ExecutionStatus(), testing::Eq(Result::passed)); - ASSERT_THAT(contextManager.ProgramContext().ExecutionStatus(), testing::Eq(Result::passed)); - ASSERT_THAT(contextManager.FeatureContext().ExecutionStatus(), testing::Eq(Result::passed)); - ASSERT_THAT(contextManager.RuleContext().ExecutionStatus(), testing::Eq(Result::passed)); - ASSERT_THAT(contextManager.ScenarioContext().ExecutionStatus(), testing::Eq(Result::passed)); - ASSERT_THAT(contextManager.StepContext().ExecutionStatus(), testing::Eq(Result::passed)); - - Error("failure"); - - EXPECT_THAT(contextManager.CurrentContext().ExecutionStatus(), testing::Eq(Result::failed)); - EXPECT_THAT(contextManager.ProgramContext().ExecutionStatus(), testing::Eq(Result::failed)); - EXPECT_THAT(contextManager.FeatureContext().ExecutionStatus(), testing::Eq(Result::failed)); - EXPECT_THAT(contextManager.RuleContext().ExecutionStatus(), testing::Eq(Result::failed)); - EXPECT_THAT(contextManager.ScenarioContext().ExecutionStatus(), testing::Eq(Result::failed)); - EXPECT_THAT(contextManager.StepContext().ExecutionStatus(), testing::Eq(Result::failed)); - } - - TEST_F(TestFailureHandler, ReportFailureMessage) - { - EXPECT_CALL(reportHandler, Failure("Failure Message", testing::_, testing::_, testing::_)); - - Error("Failure Message"); - } -} diff --git a/cucumber_cpp/library/engine/test/TestFeatureFactory.cpp b/cucumber_cpp/library/engine/test/TestFeatureFactory.cpp deleted file mode 100644 index 96acc4f7..00000000 --- a/cucumber_cpp/library/engine/test/TestFeatureFactory.cpp +++ /dev/null @@ -1,316 +0,0 @@ -// #include "cucumber_cpp/CucumberCpp.hpp" -// #include "cucumber_cpp/library/StepRegistry.hpp" -// #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -// #include "cucumber_cpp/library/engine/FeatureFactory.hpp" -// #include "cucumber_cpp/library/engine/StepType.hpp" -// #include "cucumber_cpp/library/engine/test_helper/TemporaryFile.hpp" -// #include -// #include -// #include -// #include -// #include -// #include -// #include - -// namespace cucumber_cpp::library::engine -// { - -// struct TestFeatureFactory : testing::Test -// { -// cucumber_expression::ParameterRegistry parameterRegistry; -// StepRegistry stepRegistry{ parameterRegistry }; - -// std::shared_ptr contextStorageFactory = std::make_shared(); -// GoogleTestBuilder featureTreeFactory{ std::make_unique(contextStorageFactory), stepRegistry }; -// }; - -// TEST_F(TestFeatureFactory, CreateEmptyFeature) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "Feature: Test feature\n" -// "Custom\n" -// "Description\n"; - -// auto feature = featureTreeFactory.Create(tmp.Path(), ""); -// EXPECT_THAT(feature->Title(), testing::StrEq("Test feature")); -// EXPECT_THAT(feature->Description(), testing::StrEq("Custom\nDescription")); -// EXPECT_THAT(feature->Tags(), testing::Eq(std::set>{})); -// EXPECT_THAT(feature->Path(), testing::Eq(tmp.Path())); -// EXPECT_THAT(feature->Line(), testing::Eq(1)); -// EXPECT_THAT(feature->Column(), testing::Eq(1)); -// EXPECT_THAT(feature->Rules().size(), testing::Eq(0)); -// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); -// } - -// TEST_F(TestFeatureFactory, CreateScenario) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "Feature: Test feature\n" -// "Custom\n" -// "Description\n" -// " Scenario: Test scenario\n" -// " Custom Scenario\n" -// " Description\n"; - -// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); -// EXPECT_THAT(feature->Rules().size(), testing::Eq(0)); -// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(1)); - -// const auto& scenario = feature->Scenarios().front(); -// EXPECT_THAT(scenario->Title(), testing::StrEq("Test scenario")); -// EXPECT_THAT(scenario->Description(), testing::StrEq(" Custom Scenario\n Description")); -// EXPECT_THAT(scenario->Tags(), testing::Eq(std::set>{})); -// EXPECT_THAT(scenario->Path(), testing::Eq(tmp.Path())); -// EXPECT_THAT(scenario->Line(), testing::Eq(4)); -// EXPECT_THAT(scenario->Column(), testing::Eq(3)); -// EXPECT_THAT(scenario->Children().size(), testing::Eq(0)); -// } - -// TEST_F(TestFeatureFactory, CreateRules) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "Feature: Test feature\n" -// "Custom\n" -// "Description\n" -// " Rule: Test rule\n" -// " Custom Rule\n" -// " Description1\n" -// " Scenario: Test scenario1\n" -// " Custom Scenario\n" -// " Description\n" -// " Rule: Test rule\n" -// " Custom Rule\n" -// " Description2\n" -// " Scenario: Test scenario2\n" -// " Custom Scenario\n" -// " Description\n"; - -// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); -// EXPECT_THAT(feature->Rules().size(), testing::Eq(2)); -// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); - -// const auto& rule1 = feature->Rules().front(); -// EXPECT_THAT(rule1->Title(), testing::StrEq("Test rule")); -// EXPECT_THAT(rule1->Description(), testing::StrEq(" Custom Rule\n Description1")); -// EXPECT_THAT(rule1->Line(), testing::Eq(4)); -// EXPECT_THAT(rule1->Column(), testing::Eq(3)); -// EXPECT_THAT(rule1->Scenarios().size(), testing::Eq(1)); - -// const auto& rule1scenario1 = rule1->Scenarios().front(); -// EXPECT_THAT(rule1scenario1->Title(), testing::StrEq("Test scenario1")); -// EXPECT_THAT(rule1scenario1->Line(), testing::Eq(7)); - -// const auto& rule2 = feature->Rules().back(); -// EXPECT_THAT(rule2->Title(), testing::StrEq("Test rule")); -// EXPECT_THAT(rule2->Description(), testing::StrEq(" Custom Rule\n Description2")); -// EXPECT_THAT(rule2->Line(), testing::Eq(10)); -// EXPECT_THAT(rule2->Column(), testing::Eq(3)); -// EXPECT_THAT(rule2->Scenarios().size(), testing::Eq(1)); - -// const auto& rule2scenario1 = rule2->Scenarios().front(); -// EXPECT_THAT(rule2scenario1->Title(), testing::StrEq("Test scenario2")); -// EXPECT_THAT(rule2scenario1->Line(), testing::Eq(13)); -// } - -// TEST_F(TestFeatureFactory, CreateSteps) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "Feature: Test feature\n" -// " Scenario: Test scenario1\n" -// " Given I have a step1\n" -// " When I do something1\n" -// " Then I expect something1\n" -// " Rule: Test rule\n" -// " Scenario: Test scenario2\n" -// " Given I have a step2\n" -// " When I do something2\n" -// " Then I expect something2\n" -// " Rule: Test rule\n" -// " Scenario: Test scenario3\n" -// " Given I have a step3\n" -// " When I do something3\n" -// " Then I expect something3\n"; - -// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); -// EXPECT_THAT(feature->Rules().size(), testing::Eq(2)); -// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(1)); - -// const auto& scenario1 = feature->Scenarios()[0]; -// EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); -// EXPECT_THAT(scenario1->Children().size(), testing::Eq(3)); -// EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario1->Children()[1]->Type(), testing::Eq(StepType::when)); -// EXPECT_THAT(scenario1->Children()[2]->Type(), testing::Eq(StepType::then)); -// EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("I have a step1")); -// EXPECT_THAT(scenario1->Children()[1]->Text(), testing::StrEq("I do something1")); -// EXPECT_THAT(scenario1->Children()[2]->Text(), testing::StrEq("I expect something1")); - -// const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; -// EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); -// EXPECT_THAT(scenario2->Children().size(), testing::Eq(3)); -// EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario2->Children()[1]->Type(), testing::Eq(StepType::when)); -// EXPECT_THAT(scenario2->Children()[2]->Type(), testing::Eq(StepType::then)); -// EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("I have a step2")); -// EXPECT_THAT(scenario2->Children()[1]->Text(), testing::StrEq("I do something2")); -// EXPECT_THAT(scenario2->Children()[2]->Text(), testing::StrEq("I expect something2")); - -// const auto& scenario3 = feature->Rules()[1]->Scenarios()[0]; -// EXPECT_THAT(scenario3->Title(), testing::StrEq("Test scenario3")); -// EXPECT_THAT(scenario3->Children().size(), testing::Eq(3)); -// EXPECT_THAT(scenario3->Children()[0]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario3->Children()[1]->Type(), testing::Eq(StepType::when)); -// EXPECT_THAT(scenario3->Children()[2]->Type(), testing::Eq(StepType::then)); -// EXPECT_THAT(scenario3->Children()[0]->Text(), testing::StrEq("I have a step3")); -// EXPECT_THAT(scenario3->Children()[1]->Text(), testing::StrEq("I do something3")); -// EXPECT_THAT(scenario3->Children()[2]->Text(), testing::StrEq("I expect something3")); -// } - -// TEST_F(TestFeatureFactory, CreateBackground) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "Feature: Test feature\n" -// " Background: Test background\n" -// " Given a background step1\n" -// " Scenario: Test scenario1\n" -// " Given I have a step1\n" -// " Rule: Test rule\n" -// " Background: Test background\n" -// " Given a background step2\n" -// " Scenario: Test scenario2\n" -// " Given I have a step2\n" -// " Rule: Test rule\n" -// " Scenario: Test scenario3\n" -// " Given I have a step3\n"; - -// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - -// const auto& scenario1 = feature->Scenarios()[0]; -// EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); -// EXPECT_THAT(scenario1->Children().size(), testing::Eq(2)); -// EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario1->Children()[1]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("a background step1")); -// EXPECT_THAT(scenario1->Children()[1]->Text(), testing::StrEq("I have a step1")); - -// const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; -// EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); -// EXPECT_THAT(scenario2->Children().size(), testing::Eq(3)); -// EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario2->Children()[1]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("a background step1")); -// EXPECT_THAT(scenario2->Children()[1]->Text(), testing::StrEq("a background step2")); -// EXPECT_THAT(scenario2->Children()[2]->Text(), testing::StrEq("I have a step2")); - -// const auto& scenario3 = feature->Rules()[1]->Scenarios()[0]; -// EXPECT_THAT(scenario3->Title(), testing::StrEq("Test scenario3")); -// EXPECT_THAT(scenario3->Children().size(), testing::Eq(2)); -// EXPECT_THAT(scenario3->Children()[0]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario3->Children()[1]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario3->Children()[0]->Text(), testing::StrEq("a background step1")); -// EXPECT_THAT(scenario3->Children()[1]->Text(), testing::StrEq("I have a step3")); -// } - -// TEST_F(TestFeatureFactory, CreateTagsTags) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "@feature\n" -// "Feature: Test feature\n" -// " @scenario1\n" -// " Scenario: Test scenario1\n" -// " Given I have a step1\n" -// " @rule\n" -// " Rule: Test rule\n" -// " @scenario2\n" -// " Scenario: Test scenario2\n" -// " Given I have a step2\n"; - -// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - -// const auto& scenario1 = feature->Scenarios()[0]; -// EXPECT_THAT(scenario1->Tags(), testing::Eq(std::set>{ "@feature", "@scenario1" })); - -// const auto& scenario2 = feature->Rules()[0]->Scenarios()[0]; -// EXPECT_THAT(scenario2->Tags(), testing::Eq(std::set>{ "@feature", "@rule", "@scenario2" })); -// } - -// TEST_F(TestFeatureFactory, SelectTags) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "@feature\n" -// "Feature: Test feature\n" -// " Rule: Test rule1\n" -// " @scenario1 @debug\n" -// " Scenario: Test scenario1\n" -// " Given I have a step1\n" -// " @rule\n" -// " Rule: Test rule2\n" -// " @scenario2 @debug\n" -// " Scenario: Test scenario2\n" -// " Given I have a step2\n"; - -// const auto feature = featureTreeFactory.Create(tmp.Path(), "@debug & @rule"); -// EXPECT_THAT(feature->Scenarios().size(), testing::Eq(0)); -// EXPECT_THAT(feature->Rules().size(), testing::Eq(1)); - -// const auto& scenario1 = feature->Rules()[0]->Scenarios()[0]; -// EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario2")); -// } - -// TEST_F(TestFeatureFactory, CreateMultipleScenariosInOneRule) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "Feature: Test feature\n" -// " Rule: Test rule\n" -// " Scenario: Test scenario1\n" -// " Given I have a step1\n" -// " Scenario: Test scenario2\n" -// " Given I have a step2\n"; - -// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); -// EXPECT_THAT(feature->Rules().size(), testing::Eq(1)); - -// const auto& rule = feature->Rules()[0]; -// EXPECT_THAT(rule->Scenarios().size(), testing::Eq(2)); - -// const auto& scenario1 = rule->Scenarios()[0]; -// EXPECT_THAT(scenario1->Title(), testing::StrEq("Test scenario1")); -// EXPECT_THAT(scenario1->Children().size(), testing::Eq(1)); -// EXPECT_THAT(scenario1->Children()[0]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario1->Children()[0]->Text(), testing::StrEq("I have a step1")); - -// const auto& scenario2 = rule->Scenarios()[1]; -// EXPECT_THAT(scenario2->Title(), testing::StrEq("Test scenario2")); -// EXPECT_THAT(scenario2->Children().size(), testing::Eq(1)); -// EXPECT_THAT(scenario2->Children()[0]->Type(), testing::Eq(StepType::given)); -// EXPECT_THAT(scenario2->Children()[0]->Text(), testing::StrEq("I have a step2")); -// } - -// TEST_F(TestFeatureFactory, CreateTable) -// { -// auto tmp = test_helper::TemporaryFile{ "tmpfile.feature" }; - -// tmp << "Feature: Test feature\n" -// " Scenario: Test scenario1\n" -// " Given I have a step1\n" -// " | a | b |\n" -// " | c | d |\n"; - -// const auto feature = featureTreeFactory.Create(tmp.Path(), ""); - -// const auto& scenario1 = feature->Scenarios()[0]; -// EXPECT_THAT(scenario1->Children().size(), testing::Eq(1)); -// EXPECT_THAT(scenario1->Children()[0]->Table()[0][0].As(), testing::StrEq("a")); -// EXPECT_THAT(scenario1->Children()[0]->Table()[0][1].As(), testing::StrEq("b")); -// EXPECT_THAT(scenario1->Children()[0]->Table()[1][0].As(), testing::StrEq("c")); -// EXPECT_THAT(scenario1->Children()[0]->Table()[1][1].As(), testing::StrEq("d")); -// } -// } diff --git a/cucumber_cpp/library/engine/test/TestHookExecutor.cpp b/cucumber_cpp/library/engine/test/TestHookExecutor.cpp deleted file mode 100644 index 9f260859..00000000 --- a/cucumber_cpp/library/engine/test/TestHookExecutor.cpp +++ /dev/null @@ -1,98 +0,0 @@ -// #include "cucumber_cpp/library/engine/FailureHandler.hpp" -// #include "cucumber_cpp/library/engine/HookExecutor.hpp" -// #include "cucumber_cpp/library/engine/Result.hpp" -// #include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp" -// #include "cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.hpp" -// #include "cucumber_cpp/library/report/test_helper/ReportForwarderMock.hpp" -// #include -// #include -// #include -// #include -// #include -// #include -// #include -// #include - -// namespace cucumber_cpp::library::engine -// { -// namespace -// { -// std::function expectFatalStatement; -// } - -// struct TestHookExecutor : testing::Test -// { -// std::optional contextManagerInstance{ std::in_place }; -// std::optional hookExecutor{ *contextManagerInstance }; - -// test_helper::FailureHandlerFixture failureHandlerFixture{ *contextManagerInstance }; -// }; - -// TEST_F(TestHookExecutor, Construct) -// { -// } - -// TEST_F(TestHookExecutor, ExecuteProgramHooks) -// { -// ASSERT_FALSE(contextManagerInstance->ProgramContext().Contains("hookBeforeAll")); -// ASSERT_FALSE(contextManagerInstance->ProgramContext().Contains("hookAfterAll")); - -// { -// auto scope = hookExecutor->BeforeAll(); -// EXPECT_TRUE(contextManagerInstance->ProgramContext().Contains("hookBeforeAll")); -// } -// EXPECT_TRUE(contextManagerInstance->ProgramContext().Contains("hookAfterAll")); -// } - -// TEST_F(TestHookExecutor, ExecuteBeforeFeature) -// { -// ASSERT_FALSE(contextManagerInstance->FeatureContext().Contains("hookBeforeFeature")); -// ASSERT_FALSE(contextManagerInstance->FeatureContext().Contains("hookAfterFeature")); - -// { -// auto scope = hookExecutor->FeatureStart(); -// EXPECT_TRUE(contextManagerInstance->FeatureContext().Contains("hookBeforeFeature")); -// } -// EXPECT_TRUE(contextManagerInstance->FeatureContext().Contains("hookAfterFeature")); -// } - -// TEST_F(TestHookExecutor, ExecuteBeforeScenario) -// { -// ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookBeforeScenario")); -// ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookAfterScenario")); - -// { -// auto scope = hookExecutor->ScenarioStart(); -// EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookBeforeScenario")); -// } -// EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookAfterScenario")); -// } - -// TEST_F(TestHookExecutor, ExecuteBeforeStep) -// { -// ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookBeforeStep")); -// ASSERT_FALSE(contextManagerInstance->ScenarioContext().Contains("hookAfterStep")); - -// { -// auto scope = hookExecutor->StepStart(); -// EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookBeforeStep")); -// } -// EXPECT_TRUE(contextManagerInstance->ScenarioContext().Contains("hookAfterStep")); -// } - -// TEST_F(TestHookExecutor, BeforeHookError) -// { -// contextManagerInstance.emplace(std::set>{ "@errorbefore" }); -// hookExecutor.emplace(*contextManagerInstance); - -// report::test_helper::ReportForwarderMock reportHandler{ *contextManagerInstance }; -// TestAssertionHandlerImpl assertionHandler{ *contextManagerInstance, reportHandler }; - -// expectFatalStatement = [this] -// { -// auto hook = hookExecutor->StepStart(); -// }; - -// EXPECT_FATAL_FAILURE(expectFatalStatement(), "Expected: false"); -// } -// } diff --git a/cucumber_cpp/library/engine/test/TestHookExecutorHooks.cpp b/cucumber_cpp/library/engine/test/TestHookExecutorHooks.cpp deleted file mode 100644 index 15888377..00000000 --- a/cucumber_cpp/library/engine/test/TestHookExecutorHooks.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include "cucumber_cpp/library/Hooks.hpp" -#include -#include - -namespace cucumber_cpp::library::engine -{ - HOOK_BEFORE_ALL() - { - context.InsertAt("hookBeforeAll", std::string{ "hookBeforeAll" }); - } - - HOOK_AFTER_ALL() - { - context.InsertAt("hookAfterAll", std::string{ "hookAfterAll" }); - } - - HOOK_BEFORE_FEATURE() - { - context.InsertAt("hookBeforeFeature", std::string{ "hookBeforeFeature" }); - } - - HOOK_AFTER_FEATURE() - { - context.InsertAt("hookAfterFeature", std::string{ "hookAfterFeature" }); - } - - HOOK_BEFORE_SCENARIO() - { - context.InsertAt("hookBeforeScenario", std::string{ "hookBeforeScenario" }); - } - - HOOK_AFTER_SCENARIO() - { - context.InsertAt("hookAfterScenario", std::string{ "hookAfterScenario" }); - } - - HOOK_BEFORE_STEP() - { - context.InsertAt("hookBeforeStep", std::string{ "hookBeforeStep" }); - } - - HOOK_AFTER_STEP() - { - context.InsertAt("hookAfterStep", std::string{ "hookAfterStep" }); - } - - HOOK_BEFORE_STEP("@errorbefore") - { - ASSERT_FALSE(true); - } -} diff --git a/cucumber_cpp/library/engine/test/TestStep.cpp b/cucumber_cpp/library/engine/test/TestStep.cpp index 764c713f..c0d0e72a 100644 --- a/cucumber_cpp/library/engine/test/TestStep.cpp +++ b/cucumber_cpp/library/engine/test/TestStep.cpp @@ -1,13 +1,12 @@ +#include "cucumber/messages/pickle_step_argument.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/engine/Step.hpp" -#include "cucumber_cpp/library/engine/Table.hpp" -#include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" -#include +#include #include -#include namespace cucumber_cpp::library::engine { @@ -18,26 +17,33 @@ namespace cucumber_cpp::library::engine MOCK_METHOD(void, SetUp, (), (override)); MOCK_METHOD(void, TearDown, (), (override)); - using Step::context; - using Step::Given; using Step::Pending; - using Step::table; + using Step::Skipped; + + using Step::Given; using Step::Then; using Step::When; + + using Step::context; + using Step::dataTable; + using Step::docString; }; struct TestStep : testing::Test { - Table table{ - std::vector{ TableValue{ "header1" }, TableValue{ "header2}" } }, - std::vector{ TableValue{ "value1" }, TableValue{ "value2}" } } + util::Broadcaster broadcaster; + std::shared_ptr contextStorageFactory{ std::make_shared() }; + Context context{ contextStorageFactory }; + engine::StepOrHookStarted stepOrHookStarted; + cucumber::messages::pickle_step_argument pickleStepArgument; + + StepMock step{ + broadcaster, + context, + stepOrHookStarted, + pickleStepArgument.data_table, + pickleStepArgument.doc_string }; - - const std::string docString = "multiline \n string"; - - library::engine::test_helper::ContextManagerInstance contextManager; - - StepMock step{ contextManager.StepContext(), table, docString }; }; TEST_F(TestStep, StepProvidesAccessToSetUpFunction) @@ -56,53 +62,21 @@ namespace cucumber_cpp::library::engine TEST_F(TestStep, ProvidesAccessToCurrentContext) { - ASSERT_THAT(contextManager.StepContext().Contains("top level value"), testing::Eq(false)); + ASSERT_THAT(context.Contains("top level value"), testing::Eq(false)); step.context.InsertAt("top level value", "value"); - ASSERT_THAT(contextManager.StepContext().Contains("top level value"), testing::Eq(true)); + ASSERT_THAT(context.Contains("top level value"), testing::Eq(true)); } - TEST_F(TestStep, ProvidesAccessToTable) - { - ASSERT_THAT(step.table[0][0].As(), testing::Eq(table[0][0].As())); - ASSERT_THAT(step.table[1][1].As(), testing::Eq(table[1][1].As())); - } + // TEST_F(TestStep, ProvidesAccessToTable) + // { + // ASSERT_THAT(step.table[0][0].As(), testing::Eq(table[0][0].As())); + // ASSERT_THAT(step.table[1][1].As(), testing::Eq(table[1][1].As())); + // } TEST_F(TestStep, ThrowsStepPendingExceptionOnPending) { - ASSERT_THROW(step.Pending("message"), Step::StepPending); + ASSERT_THROW(step.Pending("message"), StepPending); } - - ///////////////////////////////////////// - class FooTest : public testing::TestWithParam - { - // You can implement all the usual fixture class members here. - // To access the test parameter, call GetParam() from class - // TestWithParam. - }; - - // Or, when you want to add parameters to a pre-existing fixture class: - class BaseTest : public testing::Test - { - }; - - class BarTest : public BaseTest - , public testing::WithParamInterface - { - }; - - TEST_P(FooTest, DoesBlah) - { - // Inside a test, access the test parameter with the GetParam() method - // of the TestWithParam class: - } - - TEST_P(FooTest, HasBlahBlah) - { - } - - INSTANTIATE_TEST_SUITE_P(MeenyMinyMoe, - FooTest, - testing::Values("meeny", "miny", "moe")); } diff --git a/cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp b/cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp deleted file mode 100644 index fecbcc0a..00000000 --- a/cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp +++ /dev/null @@ -1,48 +0,0 @@ -#ifndef TEST_HELPER_CONTEXTMANAGERHELPER_HPP -#define TEST_HELPER_CONTEXTMANAGERHELPER_HPP - -#include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::engine::test_helper -{ - struct ContextManagerInstanceStorage - { - protected: - std::shared_ptr contextStorageFactory = std::make_shared(); - }; - - struct ContextManagerInstance : private ContextManagerInstanceStorage - , cucumber_cpp::library::engine::ContextManager - { - ContextManagerInstance(std::set> tags = {}) - : cucumber_cpp::library::engine::ContextManager{ contextStorageFactory } - , feature{ tags, {}, {}, {}, {}, {} } - , rule{ feature, {}, {}, {}, {}, {} } - , scenario{ rule, tags, {}, {}, {}, {} } - , step{ scenario, {}, {}, {}, {}, {}, {} } - { - } - - private: - cucumber_cpp::library::engine::FeatureInfo feature; - cucumber_cpp::library::engine::RuleInfo rule; - cucumber_cpp::library::engine::ScenarioInfo scenario; - cucumber_cpp::library::engine::StepInfo step; - - ContextManager::ScopedFeatureContext featureContextScope{ CreateFeatureContext(feature) }; - ContextManager::ScopedRuleContext ruleContextScope{ CreateRuleContext(rule) }; - ContextManager::ScopedScenarioContext scenarioContextScope{ CreateScenarioContext(scenario) }; - ContextManager::ScopedStepContext stepContextScope{ CreateStepContext(step) }; - }; -} - -#endif diff --git a/cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.cpp b/cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.cpp deleted file mode 100644 index 26a7bb2a..00000000 --- a/cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "gtest/gtest.h" - -namespace cucumber_cpp::library::engine::test_helper -{ - FailureHandlerFixture::FailureHandlerFixture(ContextManager& contextManager) - : contextManager{ contextManager } - { - auto& listeners = testing::UnitTest::GetInstance()->listeners(); - listeners.Append(&googleTestEventListener); - } - - FailureHandlerFixture::~FailureHandlerFixture() - { - auto& listeners = testing::UnitTest::GetInstance()->listeners(); - listeners.Release(&googleTestEventListener); - } -} diff --git a/cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.hpp b/cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.hpp deleted file mode 100644 index 13530a15..00000000 --- a/cucumber_cpp/library/engine/test_helper/FailureHandlerFixture.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef TEST_HELPER_FAILURE_HANDLER_FIXTURE_HPP -#define TEST_HELPER_FAILURE_HANDLER_FIXTURE_HPP - -#include "cucumber_cpp/CucumberCpp.hpp" -#include "cucumber_cpp/library/engine/ContextManager.hpp" -#include "cucumber_cpp/library/engine/FailureHandler.hpp" - -namespace cucumber_cpp::library::engine::test_helper -{ - struct FailureHandlerFixture - { - explicit FailureHandlerFixture(ContextManager& contextManager); - ~FailureHandlerFixture(); - - ContextManager& contextManager; - - report::ReportForwarderImpl reportHandler{ contextManager }; - TestAssertionHandlerImpl testAssertionHandler{ contextManager, reportHandler }; - GoogleTestEventListener googleTestEventListener{ testAssertionHandler }; - }; - -} - -#endif diff --git a/cucumber_cpp/library/engine/test_helper/TestExecutionInstance.hpp b/cucumber_cpp/library/engine/test_helper/TestExecutionInstance.hpp deleted file mode 100644 index 55c42c46..00000000 --- a/cucumber_cpp/library/engine/test_helper/TestExecutionInstance.hpp +++ /dev/null @@ -1,66 +0,0 @@ -#ifndef TEST_HELPER_TESTEXECUTIONINSTANCE_HPP -#define TEST_HELPER_TESTEXECUTIONINSTANCE_HPP - -#include "cucumber_cpp/library/engine/FeatureInfo.hpp" -#include "cucumber_cpp/library/engine/HookExecutor.hpp" -#include "cucumber_cpp/library/engine/RuleInfo.hpp" -#include "cucumber_cpp/library/engine/ScenarioInfo.hpp" -#include "cucumber_cpp/library/engine/StepInfo.hpp" -#include "cucumber_cpp/library/engine/TestExecution.hpp" -#include "cucumber_cpp/library/engine/test_helper/ContextManagerInstance.hpp" -#include "cucumber_cpp/library/report/Report.hpp" -#include "gmock/gmock.h" - -namespace cucumber_cpp::library::engine::test_helper -{ - struct TestExecutionMockInstance : TestExecution - { - virtual ~TestExecutionMockInstance() = default; - - MOCK_METHOD(void, StartRunMock, ()); - MOCK_METHOD(void, StartFeatureMock, (const FeatureInfo& featureInfo)); - MOCK_METHOD(void, StartRuleMock, (const RuleInfo& ruleInfo)); - MOCK_METHOD(void, StartScenarioMock, (const ScenarioInfo& scenarioInfo)); - - MOCK_METHOD(void, RunStepMock, (const StepInfo& stepInfo)); - - private: - [[nodiscard]] ProgramScope StartRun() override - { - StartRunMock(); - return testExecutionImpl.StartRun(); - } - - [[nodiscard]] FeatureScope StartFeature(const cucumber_cpp::library::engine::FeatureInfo& featureInfo) override - { - StartFeatureMock(featureInfo); - return testExecutionImpl.StartFeature(featureInfo); - } - - [[nodiscard]] RuleScope StartRule(const cucumber_cpp::library::engine::RuleInfo& ruleInfo) override - { - StartRuleMock(ruleInfo); - return testExecutionImpl.StartRule(ruleInfo); - } - - [[nodiscard]] ScenarioScope StartScenario(const cucumber_cpp::library::engine::ScenarioInfo& scenarioInfo) override - { - StartScenarioMock(scenarioInfo); - return testExecutionImpl.StartScenario(scenarioInfo); - } - - void RunStep(const cucumber_cpp::library::engine::StepInfo& stepInfo) override - { - RunStepMock(stepInfo); - testExecutionImpl.RunStep(stepInfo); - } - - ContextManagerInstance contextManager; - HookExecutorImpl hookExecutor{ contextManager }; - report::ReportForwarderImpl reporters{ contextManager }; - - TestExecutionImpl testExecutionImpl{ contextManager, reporters, hookExecutor }; - }; -} - -#endif diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyPrinter.cpp index 1f5a3eee..7b231494 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.cpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -185,6 +186,9 @@ namespace cucumber_cpp::library::formatter void PrettyPrinter::PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) { + if (title.length() > maxContentLength) + throw std::logic_error("maxContentLength is smaller than title length"); + const auto padding = maxContentLength - title.length(); if (uri.has_value() && line.has_value()) diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index 692915b8..ad38719b 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -19,17 +19,17 @@ namespace cucumber_cpp::library::formatter { namespace { - bool IsFailure(cucumber::messages::test_step_result_status status, bool willBeRetries) + bool IsFailure(cucumber::messages::test_step_result_status status, bool willBeRetried) { return status == cucumber::messages::test_step_result_status::AMBIGUOUS || status == cucumber::messages::test_step_result_status::UNDEFINED || - (status == cucumber::messages::test_step_result_status::FAILED && !willBeRetries); + (status == cucumber::messages::test_step_result_status::FAILED && !willBeRetried); } - bool IsWarning(cucumber::messages::test_step_result_status status, bool willBeRetries) + bool IsWarning(cucumber::messages::test_step_result_status status, bool willBeRetried) { return status == cucumber::messages::test_step_result_status::PENDING || - (status == cucumber::messages::test_step_result_status::FAILED && willBeRetries); + (status == cucumber::messages::test_step_result_status::FAILED && willBeRetried); } } diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index d44d0a35..10f8eafd 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -11,6 +11,8 @@ target_sources(cucumber_cpp.library.formatter.helper PRIVATE IssueHelpers.hpp KeywordType.cpp KeywordType.hpp + LocationHelpers.cpp + LocationHelpers.hpp PickleParser.cpp PickleParser.hpp SummaryHelpers.hpp diff --git a/cucumber_cpp/library/formatter/helper/LocationHelpers.cpp b/cucumber_cpp/library/formatter/helper/LocationHelpers.cpp new file mode 100644 index 00000000..112f8f08 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/LocationHelpers.cpp @@ -0,0 +1,17 @@ +#include "cucumber_cpp/library/formatter/helper/LocationHelpers.hpp" +#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::string FormatLocation(LineAndUri obj, std::optional cwd) + { + std::string uri = obj.uri; + if (cwd) + uri = std::filesystem::relative(obj.uri, *cwd).string(); + return std::format("{}:{}", uri, obj.line); + } +} diff --git a/cucumber_cpp/library/formatter/helper/LocationHelpers.hpp b/cucumber_cpp/library/formatter/helper/LocationHelpers.hpp new file mode 100644 index 00000000..37453654 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/LocationHelpers.hpp @@ -0,0 +1,15 @@ +#ifndef HELPER_FORMAT_LOCATION_HPP +#define HELPER_FORMAT_LOCATION_HPP + +#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + + std::string FormatLocation(LineAndUri obj, std::optional cwd = std::nullopt); +} + +#endif diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp index d9a75c20..f0ddde1a 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp @@ -3,6 +3,7 @@ #include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/formatter/helper/IndentString.hpp" +#include "cucumber_cpp/library/formatter/helper/LocationHelpers.hpp" #include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include @@ -15,19 +16,10 @@ namespace cucumber_cpp::library::formatter::helper { namespace { - // to be moved tyo LocationHelpers.hpp - std::string FormatLocation(LineAndUri obj, std::optional cwd = std::nullopt) - { - std::string uri = obj.uri; - if (cwd) - uri = std::filesystem::relative(obj.uri, *cwd).string(); - return std::format("{}:{}", uri, obj.line); - } - std::string GetAttemptText(std::size_t attempt, bool willBeRetried) { if (attempt > 0 || willBeRetried) - return std::format(" (attempt {}{})", attempt + 1, willBeRetried ? ", retried" : ""); + return std::format(" (attempt {}{})", attempt + 1, willBeRetried ? ", retried" : ""); return ""; } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index faf86985..4daa4733 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -6,7 +6,6 @@ #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_step.hpp" #include "cucumber/messages/pickle_table_row.hpp" -#include "cucumber/messages/step_match_argument.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/suggestion.hpp" #include "cucumber/messages/test_case.hpp" @@ -21,43 +20,22 @@ #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" #include -#include #include -#include -#include #include #include #include #include #include -#include #include namespace cucumber_cpp::library::runtime { - namespace - { - // cucumber::messages::duration operator+=(cucumber::messages::duration durationA, cucumber::messages::duration durationB) - // { - // const auto seconds = durationA.seconds + durationB.seconds; - // const auto nanos = durationA.nanos + durationB.nanos; - - // if (nanos >= support::nanosecondsPerSecond) - // return { seconds + 1, nanos - support::nanosecondsPerSecond }; - // else - // return { seconds, nanos }; - // } - - } - TestCaseRunner::TestCaseRunner(util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator, const cucumber::messages::gherkin_document& gherkinDocument, @@ -219,16 +197,11 @@ namespace cucumber_cpp::library::runtime if (util::GetWorstTestStepResult(stepResults).status != cucumber::messages::test_step_result_status::FAILED) { - const auto& definition = stepDefinitions.front(); + const auto& dataTable = pickleStep.argument ? pickleStep.argument->data_table : std::nullopt; + const auto& docString = pickleStep.argument ? pickleStep.argument->doc_string : std::nullopt; - const auto toOptionalTable = [](const cucumber::messages::pickle_step& pickleStep) -> std::optional> - { - if (pickleStep.argument && pickleStep.argument->data_table) - return pickleStep.argument->data_table->rows; - return std::nullopt; - }; - - const auto result = InvokeStep(definition.factory(broadcaster, testCaseContext, testStepStarted, toOptionalTable(pickleStep), pickleStep.argument ? pickleStep.argument->doc_string : std::nullopt), testStep.step_match_arguments_lists->front()); + const auto& definition = stepDefinitions.front(); + const auto result = InvokeStep(definition.factory(broadcaster, testCaseContext, testStepStarted, dataTable, docString), testStep.step_match_arguments_lists->front()); stepResults.push_back(result); } diff --git a/cucumber_cpp/library/support/Join.cpp b/cucumber_cpp/library/support/Join.cpp index 2ff492ca..9663e9f1 100644 --- a/cucumber_cpp/library/support/Join.cpp +++ b/cucumber_cpp/library/support/Join.cpp @@ -14,7 +14,7 @@ namespace cucumber_cpp::library::support std::string Join(std::span parts, const std::string& separator) { - if (parts.size() == 0) + if (parts.empty()) return ""; std::string joined = *parts.begin(); From 03832cd70c744e50ff459ed3becec694ee923bae Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 24 Dec 2025 13:36:53 +0000 Subject: [PATCH 056/196] fix bats-acceptance tests --- .github/workflows/static-analysis.yml | 2 +- compatibility/compatibility.cpp | 5 +- cucumber_cpp/acceptance_test/coverage.bats | 208 ------------------- cucumber_cpp/acceptance_test/hooks/Hooks.cpp | 7 +- cucumber_cpp/acceptance_test/test.bats | 131 ++++++------ cucumber_cpp/library/Application.cpp | 70 +++++-- cucumber_cpp/library/Application.hpp | 27 ++- cucumber_cpp/library/api/RunCucumber.cpp | 5 +- cucumber_cpp/library/runtime/Worker.cpp | 13 +- cucumber_cpp/library/runtime/Worker.hpp | 50 ----- cucumber_cpp/library/support/Types.hpp | 18 +- 11 files changed, 166 insertions(+), 370 deletions(-) delete mode 100644 cucumber_cpp/acceptance_test/coverage.bats diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index b498d9eb..5099c554 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -44,7 +44,7 @@ jobs: - name: Run acceptance tests run: | - bats --formatter junit cucumber_cpp/acceptance_test/coverage.bats | tee test-report.xml + bats --formatter junit cucumber_cpp/acceptance_test/test.bats | tee test-report.xml - name: Collect coverage run: | diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 73cd7f84..fb91a78a 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -2,10 +2,11 @@ #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include "library/support/Duration.hpp" #include "nlohmann/json.hpp" #include "nlohmann/json_fwd.hpp" #include "gmock/gmock.h" @@ -248,7 +249,7 @@ namespace compatibility cucumber_cpp::library::support::RunOptions runOptions{ .sources = { .paths = devkit.paths, - .tagExpression = devkit.tagExpression, + .tagExpression = cucumber_cpp::library::tag_expression::Parse(devkit.tagExpression), .ordering = isReversed ? cucumber_cpp::library::support::RunOptions::Ordering::reverse : cucumber_cpp::library::support::RunOptions::Ordering::defined, }, .runtime = { diff --git a/cucumber_cpp/acceptance_test/coverage.bats b/cucumber_cpp/acceptance_test/coverage.bats deleted file mode 100644 index e14cbe43..00000000 --- a/cucumber_cpp/acceptance_test/coverage.bats +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env bats - -setup() { - load '/usr/local/bats-support/load' - load '/usr/local/bats-assert/load' -} - -teardown() { - rm -rf ./out/ -} - -@test "Successful test" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --tag "@result:OK" --feature cucumber_cpp/acceptance_test/features --report console - assert_success -} - -@test "Parse tag expression" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --tag @smoke @result:OK --feature cucumber_cpp/acceptance_test/features --report console - assert_success -} - -@test "Failed tests" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --tag "@result:FAILED" --feature cucumber_cpp/acceptance_test/features --report console - assert_failure - assert_output --partial "failed \"cucumber_cpp/acceptance_test/features/test_scenarios.feature\"" - assert_output --partial "skipped Then a then step" -} - -@test "Undefined tests" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --tag "@result:UNDEFINED" --feature cucumber_cpp/acceptance_test/features --report console - assert_failure - assert_output --partial "undefined \"cucumber_cpp/acceptance_test/features/test_scenarios.feature\"" - assert_output --partial "skipped Then this should be skipped" -} - -@test "No tests" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --tag "@invalidtag" --feature cucumber_cpp/acceptance_test/features --report console - assert_success -} - -@test "All features in a folder" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features/subfolder --report console - assert_success - assert_output --partial "test1 scenario" - assert_output --partial "test2 scenario" -} - -@test "Missing mandatory feature argument" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --report console - assert_failure - assert_output --partial "--feature is required" -} - -@test "Missing mandatory report argument" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features - assert_failure - assert_output --partial "--report is required" -} - -@test "Missing mandatory custom argument" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test.custom run --feature cucumber_cpp/acceptance_test/features --report console - assert_failure - assert_output --partial "--required is required" -} - -@test "Second feature file does not overwrite success with an undefined status" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --tag "@undefinedsuccess and @result:success" --feature cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature --report console - assert_success -} - -@test "Valid reporters only" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --report doesnotexist - assert_failure - assert_output --partial "--report: 'doesnotexist' is not a reporter" -} - -@test "Run Program hooks" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag @bats and @program_hooks --report console - assert_success - - assert_output --partial "HOOK_BEFORE_ALL" - assert_output --partial "HOOK_AFTER_ALL" -} - -@test "Run Scenario hooks" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag @bats and @scenariohook and not @stephook --report console - assert_success - - assert_output --partial "HOOK_BEFORE_SCENARIO" - assert_output --partial "HOOK_AFTER_SCENARIO" - - refute_output --partial "HOOK_BEFORE_STEP" - refute_output --partial "HOOK_AFTER_STEP" -} - -@test "Run Step hooks" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag @bats and @stephook and not @scenariohook --report console - assert_success - - refute_output --partial "HOOK_BEFORE_SCENARIO" - refute_output --partial "HOOK_AFTER_SCENARIO" - - assert_output --partial "HOOK_BEFORE_STEP" - assert_output --partial "HOOK_AFTER_STEP" -} - -@test "Run Scenario and Step hooks" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@bats and (@scenariohook or @stephook)" --report console - assert_success - - assert_output --partial "HOOK_BEFORE_SCENARIO" - assert_output --partial "HOOK_AFTER_SCENARIO" - - assert_output --partial "HOOK_BEFORE_STEP" - assert_output --partial "HOOK_AFTER_STEP" -} - -@test "Dry run with known failing steps" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@result:FAILED" --report console - assert_failure - - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@result:FAILED" --report console --dry - assert_success -} - -@test "Dry run with known missing steps" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@result:UNDEFINED" --report console --dry - assert_failure - assert_output --partial "undefined \"cucumber_cpp/acceptance_test/features/test_scenarios.feature\"" - assert_output --partial "skipped Then this should be skipped" -} - -@test "Test the and keyword" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@keyword-and" --report console - assert_success - assert_output --partial "--when--" - assert_output --partial "--and--" -} - -@test "Test the but keyword" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@keyword-but" --report console - assert_success - assert_output --partial "--when--" - assert_output --partial "--but--" -} - -@test "Test the asterisk keyword - will fail" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@keyword-asterisk" --report console - assert_failure -} - -@test "Test passing scenario after failed scenario reports feature as failed" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_feature" --report console - assert_failure - assert_output --partial "tests : 1/2 passed" -} - -@test "Test failing hook before results in error" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_scenariohook_before" --report console - assert_failure - assert_output --partial "skipped Given a given step" - assert_output --partial "tests : 0/1 passed" -} - -@test "Test failing hook after results in error" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_scenariohook_after" --report console - assert_failure - assert_output --partial "Given a given step" - assert_output --partial "done" - assert_output --partial "failed" - assert_output --partial "tests : 0/1 passed" -} - -@test "Test throwing hook results in error" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@throw_scenariohook" --report console - assert_failure - assert_output --partial "skipped Given a given step" - assert_output --partial "tests : 0/1 passed" -} - - -@test "Test error program hook results in error and skipped steps" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test.custom run --feature cucumber_cpp/acceptance_test/features --tag "@smoke and @result:OK" --report console --required --failprogramhook - assert_failure - refute_output --partial "skipped Given a given step" - refute_output --partial "should not be executed" - assert_output --partial "tests : 0/0 passed" -} - -@test "Test unicode" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@unicode" --report console - assert_success - assert_output --partial "tests : 1/1 passed" -} - -@test "TestExceptionContinuesWithNextScenario" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@stepthrowcontinues" --report console - assert_failure - assert_output --partial "Exception thrown" - refute_output --partial "Should Not Be Thrown" - assert_output --partial "tests : 1/2 passed" -} - -@test "RunFeatureFileWithError" { - run .build/Coverage/cucumber_cpp/acceptance_test/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_scenario" --report console - assert_failure - assert_output --partial "tests : 1/2 passed" -} diff --git a/cucumber_cpp/acceptance_test/hooks/Hooks.cpp b/cucumber_cpp/acceptance_test/hooks/Hooks.cpp index 056172f2..3d4dc920 100644 --- a/cucumber_cpp/acceptance_test/hooks/Hooks.cpp +++ b/cucumber_cpp/acceptance_test/hooks/Hooks.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/CucumberCpp.hpp" #include "gmock/gmock.h" +#include #include #include @@ -8,7 +9,7 @@ HOOK_BEFORE_ALL() std::cout << "HOOK_BEFORE_ALL\n"; if (context.Contains("--failprogramhook") && context.Get("--failprogramhook")) - ASSERT_THAT(false, testing::IsTrue()); + FAIL(); } HOOK_AFTER_ALL() @@ -38,12 +39,12 @@ HOOK_AFTER_STEP("@stephook and @bats") HOOK_BEFORE_SCENARIO("@fail_scenariohook_before") { - ASSERT_THAT(false, testing::IsTrue()); + FAIL(); } HOOK_AFTER_SCENARIO("@fail_scenariohook_after") { - ASSERT_THAT(false, testing::IsTrue()); + FAIL(); } HOOK_BEFORE_SCENARIO("@throw_scenariohook") diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index 30ab9af4..249bd8c4 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -1,5 +1,10 @@ #!/usr/bin/env bats +setup_file() { + acceptance_test=$(find / -name "cucumber_cpp.acceptance_test" -print -quit) + export acceptance_test +} + setup() { load '/usr/local/bats-support/load' load '/usr/local/bats-assert/load' @@ -10,73 +15,72 @@ teardown() { } @test "Successful test" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@result:OK" --feature cucumber_cpp/acceptance_test/features --report console + run $acceptance_test run --tags "@result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Parse tag expression" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@smoke and @result:OK" --feature cucumber_cpp/acceptance_test/features --report console + run $acceptance_test run --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Failed tests" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@result:FAILED" --feature cucumber_cpp/acceptance_test/features --report console + run $acceptance_test run --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "[ FAILED ] Simple feature file/3.A failing scenario" - assert_output --partial "skipped Then a then step" } @test "Undefined tests" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@result:UNDEFINED" --feature cucumber_cpp/acceptance_test/features --report console + run $acceptance_test run --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "missing Given a missing step" - assert_output --partial "skipped Then this should be skipped" - assert_output --partial "[ FAILED ] Simple feature file/3.A scenario with undefined step" + assert_output --partial "UNDEFINED Given a missing step" + assert_output --partial "SKIPPED Then this should be skipped" } @test "No tests" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@invalidtag" --feature cucumber_cpp/acceptance_test/features --report console + run $acceptance_test run --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features assert_success } @test "All features in a folder" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features/subfolder --report console + run $acceptance_test run cucumber_cpp/acceptance_test/features/subfolder assert_success + assert_output --partial "test1 feature" assert_output --partial "test1 scenario" + assert_output --partial "test2 feature" assert_output --partial "test2 scenario" } @test "Missing mandatory feature argument" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --report console + run $acceptance_test run assert_failure - assert_output --partial "--feature is required" + assert_output --partial "paths is required" } -@test "Missing mandatory report argument" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features - assert_failure - assert_output --partial "--report is required" -} +# @test "Missing mandatory report argument" { +# run $acceptance_test run cucumber_cpp/acceptance_test/features +# assert_failure +# assert_output --partial "--report is required" +# } @test "Missing mandatory custom argument" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test.custom run --feature cucumber_cpp/acceptance_test/features --report console + run $acceptance_test.custom run cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "--required is required" } @test "Second feature file does not overwrite success with an undefined status" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --tag "@undefinedsuccess and @result:success" --feature cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature --report console + run $acceptance_test run --tags "@undefinedsuccess and @result:success" -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature assert_success } -@test "Valid reporters only" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --report doesnotexist - assert_failure - assert_output --partial "--report: 'doesnotexist' is not a reporter" -} +# @test "Valid reporters only" { +# run $acceptance_test run cucumber_cpp/acceptance_test/features +# assert_failure +# assert_output --partial "--report: 'doesnotexist' is not a reporter" +# } @test "Run Program hooks" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag @bats and @program_hooks --report console + run $acceptance_test run --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_ALL" @@ -84,7 +88,7 @@ teardown() { } @test "Run Scenario hooks" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag @bats and @scenariohook and not @stephook --report console + run $acceptance_test run --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -95,7 +99,7 @@ teardown() { } @test "Run Step hooks" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag @bats and @stephook and not @scenariohook --report console + run $acceptance_test run --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "HOOK_BEFORE_SCENARIO" @@ -106,7 +110,7 @@ teardown() { } @test "Run Scenario and Step hooks" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@bats and (@scenariohook or @stephook)" --report console + run $acceptance_test run --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -117,94 +121,93 @@ teardown() { } @test "Dry run with known failing steps" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@result:FAILED" --report console + run $acceptance_test run --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@result:FAILED" --report console --dry + run $acceptance_test run --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features assert_success } @test "Dry run with known missing steps" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@result:UNDEFINED" --report console --dry + run $acceptance_test run --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "undefined \"cucumber_cpp/acceptance_test/features/test_scenarios.feature\"" - assert_output --partial "skipped Then this should be skipped" + + run $acceptance_test run --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features + assert_success + assert_output --partial "UNDEFINED Given a missing step" } @test "Test the and keyword" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@keyword-and" --report console + run $acceptance_test run --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--and--" } @test "Test the but keyword" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@keyword-but" --report console + run $acceptance_test run --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--but--" } @test "Test the asterisk keyword" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@keyword-asterisk" --report console + run $acceptance_test run --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features assert_output --partial "print: --when--" assert_output --partial "print: --asterisk--" assert_success } @test "Test passing scenario after failed scenario reports feature as failed" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_feature" --report console + run $acceptance_test run --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "[==========] Running 2 tests from 1 test suite." - assert_output --partial "1 FAILED TEST" + assert_output --partial "2 scenarios" + assert_output --partial "1 passed" } @test "Test failing hook before results in error" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_scenariohook_before" --report console + run $acceptance_test run --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "[ PASSED ] 0 tests" - assert_output --partial "[ FAILED ] Test scenario and step hook bindings.Run failing Scenario hooks before" + assert_output --partial "FAILED Before" } @test "Test failing hook after results in error" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@fail_scenariohook_after" --report console + run $acceptance_test run --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "[ PASSED ] 0 tests" - assert_output --partial "[ FAILED ] Test scenario and step hook bindings.Run failing Scenario hooks after" + assert_output --partial "FAILED After" } @test "Test throwing hook results in error" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@throw_scenariohook" --report console + run $acceptance_test run --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "[ PASSED ] 0 tests" - assert_output --partial "[ FAILED ] Test scenario and step hook bindings.Run throwing Scenario hooks" + assert_output --partial "FAILED Before" } - @test "Test error program hook results in error and skipped steps" { - 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 + run $acceptance_test.custom run --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "[ PASSED ] 0 tests" - assert_output --partial "[ SKIPPED ] 1 test, listed below:" - assert_output --partial "[ SKIPPED ] Simple feature file.An OK scenario" - assert_output --partial "[ FAILED ] 0 tests, listed below:" + assert_output --partial "HOOK_BEFORE_ALL" + assert_output --partial "HOOK_AFTER_ALL" + assert_output --partial "0 scenarios" + assert_output --partial "0 steps" } @test "Test unicode" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@unicode" --report console + run $acceptance_test run --tags "@unicode" -- cucumber_cpp/acceptance_test/features assert_success - assert_output --partial "[ OK ] Test for unicode characters.Can match unicode characters" + assert_output --partial "1 scenario" + assert_output --partial "1 passed" } -@test "Test unused step reporting" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@unused_steps" --report console --unused - assert_success - assert_output --regexp ".*The following steps have not been used:.*this step is not being used.*" - refute_output --regexp ".*The following steps have not been used:.*this step is being used.*" -} +# @test "Test unused step reporting" { +# run $acceptance_test run --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features +# assert_success +# assert_output --regexp ".*The following steps have not been used:.*this step is not being used.*" +# refute_output --regexp ".*The following steps have not been used:.*this step is being used.*" +# } @test "Test unused steps by default not reported" { - run .build/Host/cucumber_cpp/acceptance_test/Debug/cucumber_cpp.acceptance_test run --feature cucumber_cpp/acceptance_test/features --tag "@unused_steps" --report console + run $acceptance_test run --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "The following steps have not been used:" } diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 29d6b498..3882431d 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -7,6 +7,8 @@ #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Types.hpp" +#include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include #include #include #include @@ -19,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -58,7 +61,7 @@ namespace cucumber_cpp::library { std::set files; - for (const auto feature : options.features | std::views::transform(ToFileSystemPath)) + for (const auto feature : options.paths | std::views::transform(ToFileSystemPath)) if (std::filesystem::is_directory(feature)) for (const auto& entry : std::filesystem::directory_iterator{ feature } | std::views::filter(IsFeatureFile)) files.emplace(entry.path()); @@ -92,30 +95,48 @@ namespace cucumber_cpp::library { RunFeatures(); }); - - runCommand->add_option("-t,--tag", options.tags, "Cucumber tag expression"); - runCommand->add_option("-f,--feature", options.features, "Feature file or folder with feature files")->required()->check(CLI::ExistingPath); - - runCommand->add_option("-r,--report", options.reporters, "Name of the report generator: ")->required()->group("report generation"); //->check(reportHandlerValidator); - runCommand->add_option("--outputfolder", options.outputfolder, "Specifies the output folder for generated report files")->group("report generation"); - runCommand->add_option("--reportfile", options.reportfile, "Specifies the output name for generated report files")->group("report generation"); - runCommand->add_flag("--dry", options.dryrun, "Generate report without running tests"); - runCommand->add_flag("--unused", options.printStepsNotUsed, "Show step definitions that were not used"); - - // reporters.Add("console", std::make_unique()); - // reporters.Add("junit-xml", std::make_unique(options.outputfolder, options.reportfile)); - - ProgramContext().InsertRef(options); } int Application::Run(int argc, const char* const* argv) { try { - const auto reportDescription = runCommand->get_option("--report")->get_description(); - const auto joinedReporters = reportDescription; // + Join(reporters.AvailableReporters(), ", "); - runCommand->get_option("--report")->description(joinedReporters); + const std::map orderingMap{ + { "defined", support::RunOptions::Ordering::defined }, + { "reverse", support::RunOptions::Ordering::reverse }, + }; + + runCommand->add_flag("-d,--dry-run", options.dryRun, "Perform a dry run without executing steps"); + runCommand->add_flag("--fail-fast", options.failFast, "Stop execution on first failure"); + runCommand->add_option("--format", options.format, "specify the output format, optionally supply PATH to redirect formatter output. Available formats:\n"); + runCommand->add_option("--format-options", options.formatOptions, "provide options for formatters"); + runCommand->add_option("--language", options.language, "Default langauge for feature files, eg 'en'")->default_str(options.language); + runCommand->add_option("--order", options.ordering, "Run scenarios in specificed order")->transform(CLI::CheckedTransformer(orderingMap, CLI::ignore_case)); + auto* retryOpt = runCommand->add_option("--retry", options.retry, "Number of times to retry failed scenarios")->default_val(options.retry); + runCommand->add_option("--retry-tag-filter", options.retryTagFilter, "Only retry scenarios matching this tag expression")->needs(retryOpt); + runCommand->add_flag("--strict,!--no-strict", options.strict, "Fail if there are pending steps")->default_val(options.strict); + + CLI::deprecate_option(runCommand->add_option("--tag", options.tags, "Cucumber tag expression"), "-t,--tags"); + runCommand->add_option("-t,--tags", options.tags, "Cucumber tag expression"); + + CLI::deprecate_option(runCommand->add_option("-f,--feature", options.paths, "Paths to where your feature files are")->check(CLI::ExistingPath), "paths"); + runCommand->add_option("paths", options.paths, "Paths to where your feature files are")->required()->check(CLI::ExistingPath); + + // runCommand->add_option("-r,--report", options.reporters, "Name of the report generator: ")->required()->group("report generation"); //->check(reportHandlerValidator); + // runCommand->add_option("--outputfolder", options.outputfolder, "Specifies the output folder for generated report files")->group("report generation"); + // runCommand->add_option("--reportfile", options.reportfile, "Specifies the output name for generated report files")->group("report generation"); + // runCommand->add_flag("--unused", options.printStepsNotUsed, "Show step definitions that were not used"); + + // reporters.Add("console", std::make_unique()); + // reporters.Add("junit-xml", std::make_unique(options.outputfolder, options.reportfile)); + + ProgramContext().InsertRef(options); + + // const auto reportDescription = runCommand->get_option("--report")->get_description(); + // const auto joinedReporters = reportDescription; // + Join(reporters.AvailableReporters(), ", "); + + // runCommand->get_option("--report")->description(joinedReporters); cli.parse(argc, argv); } catch (const CLI::ParseError& e) @@ -168,16 +189,19 @@ namespace cucumber_cpp::library void Application::RunFeatures() { - const auto tagExpression = Join(options.tags, " "); - const auto featureFiles = GetFeatureFiles(options); const auto runOptions = support::RunOptions{ .sources = { - .paths = featureFiles, - .tagExpression = tagExpression, + .paths = GetFeatureFiles(options), + .tagExpression = tag_expression::Parse(Join(options.tags, " ")), + .ordering = options.ordering, }, .runtime = { - .retry = 1, + .dryRun = options.dryRun, + .failFast = options.failFast, + .retry = options.retry, + .strict = options.strict, + .retryTagExpression = tag_expression::Parse(Join(options.retryTagFilter, " ")), }, }; diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 79d8768b..c3afcd56 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -9,11 +9,14 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include +#include #include +#include #include #include @@ -28,15 +31,24 @@ namespace cucumber_cpp::library { struct Options { - std::vector tags{}; - std::vector features{}; - std::vector reporters{}; + std::set paths{}; + + bool dryRun{ false }; + bool failFast{ false }; + + std::set format{}; + std::set formatOptions{}; + + std::string language{ "en" }; - std::string outputfolder{ "./out" }; - std::string reportfile{ "TestReport" }; + enum support::RunOptions::Ordering ordering{ support::RunOptions::Ordering::defined }; - bool dryrun{ false }; - bool printStepsNotUsed{ false }; + std::size_t retry{ 0 }; + std::vector retryTagFilter{}; + + bool strict{ true }; + + std::vector tags{}; }; explicit Application(std::shared_ptr contextStorageFactory = std::make_shared(), bool removeDefaultGoogleTestListener = true); @@ -57,6 +69,7 @@ namespace cucumber_cpp::library [[nodiscard]] int GetExitCode() const; Options options; + CLI::App cli; CLI::App* runCommand; diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index be514cf6..ae2fb81a 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -142,10 +142,9 @@ namespace cucumber_cpp::library::api const auto pickleSources = CollectPickles(options.sources, idGenerator, broadcaster); - const auto tagExpression = cucumber_cpp::library::tag_expression::Parse(options.sources.tagExpression); - const auto pickleFilter = [&tagExpression](const support::PickleSource& pickle) + const auto pickleFilter = [&options](const support::PickleSource& pickle) { - return tagExpression->Evaluate(pickle.pickle->tags); + return options.sources.tagExpression->Evaluate(pickle.pickle->tags); }; auto filteredPicklesView = pickleSources | std::views::filter(pickleFilter); diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index cf13ab73..71674251 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -3,6 +3,7 @@ #include "cucumber/messages/duration.hpp" #include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" +#include "cucumber/messages/pickle.hpp" #include "cucumber/messages/test_run_hook_finished.hpp" #include "cucumber/messages/test_run_hook_started.hpp" #include "cucumber/messages/test_step_result.hpp" @@ -18,7 +19,7 @@ #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" -#include +#include #include #include #include @@ -61,6 +62,16 @@ namespace cucumber_cpp::library::runtime cucumber::messages::test_step_result_status::FAILED, cucumber::messages::test_step_result_status::UNDEFINED, }; + + std::size_t RetriesForPickle(const cucumber::messages::pickle& pickle, support::RunOptions::Runtime& options) + { + if (options.retry == 0) + return 0; + else if (options.retryTagExpression->Evaluate(pickle.tags)) + return options.retry; + else + return 0; + } } Worker::Worker(std::string_view testRunStartedId, diff --git a/cucumber_cpp/library/runtime/Worker.hpp b/cucumber_cpp/library/runtime/Worker.hpp index ce170639..916e2414 100644 --- a/cucumber_cpp/library/runtime/Worker.hpp +++ b/cucumber_cpp/library/runtime/Worker.hpp @@ -1,69 +1,19 @@ #ifndef RUNTIME_WORKER_HPP #define RUNTIME_WORKER_HPP -#include "cucumber/gherkin/app.hpp" -#include "cucumber/gherkin/exceptions.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/gherkin/pickle_compiler.hpp" -#include "cucumber/gherkin/utils.hpp" -#include "cucumber/messages/duration.hpp" -#include "cucumber/messages/envelope.hpp" #include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/parameter_type.hpp" -#include "cucumber/messages/parse_error.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/source.hpp" -#include "cucumber/messages/source_reference.hpp" -#include "cucumber/messages/step_definition.hpp" -#include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber/messages/step_match_argument.hpp" -#include "cucumber/messages/suggestion.hpp" -#include "cucumber/messages/test_case.hpp" -#include "cucumber/messages/test_case_finished.hpp" -#include "cucumber/messages/test_case_started.hpp" -#include "cucumber/messages/test_run_finished.hpp" -#include "cucumber/messages/test_run_hook_finished.hpp" -#include "cucumber/messages/test_run_hook_started.hpp" -#include "cucumber/messages/test_run_started.hpp" -#include "cucumber/messages/test_step.hpp" -#include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber/messages/test_step_started.hpp" -#include "cucumber/messages/timestamp.hpp" #include "cucumber_cpp/CucumberCpp.hpp" -#include "cucumber_cpp/library/Body.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" -#include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" #include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" -#include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" -#include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include #include #include -#include -#include -#include #include namespace cucumber_cpp::library::runtime diff --git a/cucumber_cpp/library/support/Types.hpp b/cucumber_cpp/library/support/Types.hpp index 44b4ee91..36f9255a 100644 --- a/cucumber_cpp/library/support/Types.hpp +++ b/cucumber_cpp/library/support/Types.hpp @@ -4,13 +4,14 @@ #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle.hpp" +#include "cucumber_cpp/library/tag_expression/Model.hpp" #include #include #include #include #include #include -#include +#include namespace cucumber_cpp::library::support { @@ -24,18 +25,19 @@ namespace cucumber_cpp::library::support struct Sources { - std::set paths; - std::string_view tagExpression; - Ordering ordering; + std::set paths{}; + std::unique_ptr tagExpression; + Ordering ordering{ Ordering::defined }; } sources; struct Runtime { - bool dryRun; - bool failFast; - std::size_t retry; - bool strict; + bool dryRun{ false }; + bool failFast{ false }; + std::size_t retry{ 0 }; + bool strict{ true }; + std::unique_ptr retryTagExpression{}; } runtime; struct RunEnvironment From c9994f4de42bfab2785044028dd2870f57844ec4 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 25 Dec 2025 00:39:08 +0000 Subject: [PATCH 057/196] minor updates --- .vscode/launch.json | 18 ++++++------------ CMakeLists.txt | 3 --- compatibility/CMakeLists.txt | 2 +- compatibility/attachments/attachments.cpp | 2 +- cucumber_cpp/CMakeLists.txt | 4 +--- cucumber_cpp/CucumberCpp.hpp | 2 +- cucumber_cpp/example/steps/Steps.cpp | 4 ++-- cucumber_cpp/library/Application.cpp | 1 - 8 files changed, 12 insertions(+), 24 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index dc2737c0..b9ed3cc1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -43,16 +43,13 @@ "program": "${command:cmake.launchTargetPath}", "args": [ "run", - "--feature", - "${input:features}", - "--report", - "console", - // "junit-xml", "--com", "COMx", "--nordic", "--tag", - "${input:tag}" + "${input:tag}", + "--", + "${input:features}", ], "stopAtEntry": false, "cwd": "${workspaceFolder}", @@ -79,14 +76,11 @@ "program": "${command:cmake.launchTargetPath}", "args": [ "run", - "--feature", - "${input:features}", - "--report", - "console", - // "junit-xml", "--com", "COMx", - "--nordic" + "--nordic", + "--", + "${input:features}", ], "stopAtEntry": false, "cwd": "${workspaceFolder}", diff --git a/CMakeLists.txt b/CMakeLists.txt index ce79df2c..6ec81b44 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -23,9 +23,6 @@ option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements set(BUILD_SHARED_LIBS Off CACHE STRING "") -message(STATUS "CXX_COMPILER_ID: ${CMAKE_CXX_COMPILER_ID}") -message(STATUS "CXX_SIMULATE_ID: ${CMAKE_CXX_SIMULATE_ID}") - if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) add_compile_options(-fsanitize=address) add_link_options(-fsanitize=address) diff --git a/compatibility/CMakeLists.txt b/compatibility/CMakeLists.txt index ff923e22..11237dbd 100644 --- a/compatibility/CMakeLists.txt +++ b/compatibility/CMakeLists.txt @@ -12,7 +12,7 @@ function(add_compatibility_kit name) ) target_include_directories(${libname} PUBLIC - $ + $ ) target_sources(${libname} PRIVATE diff --git a/compatibility/attachments/attachments.cpp b/compatibility/attachments/attachments.cpp index 5812ff0d..8b607c5e 100644 --- a/compatibility/attachments/attachments.cpp +++ b/compatibility/attachments/attachments.cpp @@ -1,5 +1,5 @@ #include "cucumber_cpp/CucumberCpp.hpp" -#include "library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include #include #include diff --git a/cucumber_cpp/CMakeLists.txt b/cucumber_cpp/CMakeLists.txt index eca2965c..d6b5a532 100644 --- a/cucumber_cpp/CMakeLists.txt +++ b/cucumber_cpp/CMakeLists.txt @@ -1,5 +1,3 @@ - - add_subdirectory(library) add_subdirectory(runner) add_subdirectory(example) @@ -12,7 +10,7 @@ target_sources(cucumber_cpp INTERFACE ) target_include_directories(cucumber_cpp INTERFACE - ./ + $ ) target_link_libraries(cucumber_cpp INTERFACE diff --git a/cucumber_cpp/CucumberCpp.hpp b/cucumber_cpp/CucumberCpp.hpp index eb103edf..6d78cf7e 100644 --- a/cucumber_cpp/CucumberCpp.hpp +++ b/cucumber_cpp/CucumberCpp.hpp @@ -6,8 +6,8 @@ #include "cucumber_cpp/library/Hooks.hpp" #include "cucumber_cpp/library/Parameter.hpp" #include "cucumber_cpp/library/Steps.hpp" +#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" -#include "library/cucumber_expression/MatchRange.hpp" namespace cucumber_cpp { diff --git a/cucumber_cpp/example/steps/Steps.cpp b/cucumber_cpp/example/steps/Steps.cpp index a455fc2e..111f1025 100644 --- a/cucumber_cpp/example/steps/Steps.cpp +++ b/cucumber_cpp/example/steps/Steps.cpp @@ -35,7 +35,7 @@ STEP("expect and assert") ASSERT_THAT(true, testing::Eq(false)); } -THEN(R"(^I should have ([0-9]+) cucumbers$)", (std::int32_t num)) +THEN(R"(I should have {int} cucumbers)", (std::int32_t num)) { const auto& before = context.Get("cucumbers_before"); const auto& eaten = context.Get("cucumbers_eaten"); @@ -45,7 +45,7 @@ THEN(R"(^I should have ([0-9]+) cucumbers$)", (std::int32_t num)) ASSERT_THAT(actual, testing::Eq(num)); } -THEN(R"(^I should have ([0-9]+) cucumbers left$)", (std::int32_t num)) +THEN(R"(I should have {int} cucumbers left)", (std::int32_t num)) { const auto& before = context.Get("cucumbers_before"); const auto& eaten = context.Get("cucumbers_eaten"); diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 3882431d..58348a19 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -101,7 +101,6 @@ namespace cucumber_cpp::library { try { - const std::map orderingMap{ { "defined", support::RunOptions::Ordering::defined }, { "reverse", support::RunOptions::Ordering::reverse }, From ef2d0c15f11eef73e35d8d12c720070f9f2732e7 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 25 Dec 2025 00:39:26 +0000 Subject: [PATCH 058/196] add summary formatter to debug launch configurations --- .vscode/launch.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index b9ed3cc1..5a03502a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -46,6 +46,8 @@ "--com", "COMx", "--nordic", + "--format", + "summary", "--tag", "${input:tag}", "--", @@ -79,6 +81,8 @@ "--com", "COMx", "--nordic", + "--format", + "summary", "--", "${input:features}", ], From 3dbb76e5ee5c334581a402b19a5d7a98c6763e86 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 25 Dec 2025 00:39:35 +0000 Subject: [PATCH 059/196] add summary formatter to test.bats --- cucumber_cpp/acceptance_test/test.bats | 66 +++++++++++++------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index 249bd8c4..031c403a 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -15,72 +15,70 @@ teardown() { } @test "Successful test" { - run $acceptance_test run --tags "@result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Parse tag expression" { - run $acceptance_test run --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Failed tests" { - run $acceptance_test run --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure } @test "Undefined tests" { - run $acceptance_test run --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "UNDEFINED Given a missing step" assert_output --partial "SKIPPED Then this should be skipped" } @test "No tests" { - run $acceptance_test run --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features assert_success } @test "All features in a folder" { - run $acceptance_test run cucumber_cpp/acceptance_test/features/subfolder + run $acceptance_test run --format summary cucumber_cpp/acceptance_test/features/subfolder assert_success - assert_output --partial "test1 feature" - assert_output --partial "test1 scenario" - assert_output --partial "test2 feature" - assert_output --partial "test2 scenario" + assert_output --partial "2 scenarios" + assert_output --partial "2 passed" } @test "Missing mandatory feature argument" { - run $acceptance_test run + run $acceptance_test run --format summary assert_failure assert_output --partial "paths is required" } # @test "Missing mandatory report argument" { -# run $acceptance_test run cucumber_cpp/acceptance_test/features +# run $acceptance_test run --format summary cucumber_cpp/acceptance_test/features # assert_failure # assert_output --partial "--report is required" # } @test "Missing mandatory custom argument" { - run $acceptance_test.custom run cucumber_cpp/acceptance_test/features + run $acceptance_test.custom run --format summary cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "--required is required" } @test "Second feature file does not overwrite success with an undefined status" { - run $acceptance_test run --tags "@undefinedsuccess and @result:success" -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature + run $acceptance_test run --format summary --tags "@undefinedsuccess and @result:success" -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature assert_success } # @test "Valid reporters only" { -# run $acceptance_test run cucumber_cpp/acceptance_test/features +# run $acceptance_test run --format summary cucumber_cpp/acceptance_test/features # assert_failure # assert_output --partial "--report: 'doesnotexist' is not a reporter" # } @test "Run Program hooks" { - run $acceptance_test run --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_ALL" @@ -88,7 +86,7 @@ teardown() { } @test "Run Scenario hooks" { - run $acceptance_test run --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -99,7 +97,7 @@ teardown() { } @test "Run Step hooks" { - run $acceptance_test run --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "HOOK_BEFORE_SCENARIO" @@ -110,7 +108,7 @@ teardown() { } @test "Run Scenario and Step hooks" { - run $acceptance_test run --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -121,70 +119,70 @@ teardown() { } @test "Dry run with known failing steps" { - run $acceptance_test run --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test run --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features assert_success } @test "Dry run with known missing steps" { - run $acceptance_test run --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test run --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features assert_success assert_output --partial "UNDEFINED Given a missing step" } @test "Test the and keyword" { - run $acceptance_test run --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--and--" } @test "Test the but keyword" { - run $acceptance_test run --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--but--" } @test "Test the asterisk keyword" { - run $acceptance_test run --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features assert_output --partial "print: --when--" assert_output --partial "print: --asterisk--" assert_success } @test "Test passing scenario after failed scenario reports feature as failed" { - run $acceptance_test run --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "2 scenarios" assert_output --partial "1 passed" } @test "Test failing hook before results in error" { - run $acceptance_test run --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED Before" } @test "Test failing hook after results in error" { - run $acceptance_test run --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED After" } @test "Test throwing hook results in error" { - run $acceptance_test run --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED Before" } @test "Test error program hook results in error and skipped steps" { - run $acceptance_test.custom run --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features + run $acceptance_test.custom run --format summary --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "HOOK_BEFORE_ALL" assert_output --partial "HOOK_AFTER_ALL" @@ -193,21 +191,21 @@ teardown() { } @test "Test unicode" { - run $acceptance_test run --tags "@unicode" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@unicode" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "1 scenario" assert_output --partial "1 passed" } # @test "Test unused step reporting" { -# run $acceptance_test run --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features +# run $acceptance_test run --format summary --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features # assert_success # assert_output --regexp ".*The following steps have not been used:.*this step is not being used.*" # refute_output --regexp ".*The following steps have not been used:.*this step is being used.*" # } @test "Test unused steps by default not reported" { - run $acceptance_test run --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features + run $acceptance_test run --format summary --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "The following steps have not been used:" } From efe611ef25c33f1f4fc6965cb8ae3c772ec6baf6 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 25 Dec 2025 00:40:26 +0000 Subject: [PATCH 060/196] query is now also a broadcaster for events --- cucumber_cpp/library/Query.cpp | 127 +++++++++++++++++---------------- cucumber_cpp/library/Query.hpp | 10 ++- 2 files changed, 74 insertions(+), 63 deletions(-) diff --git a/cucumber_cpp/library/Query.cpp b/cucumber_cpp/library/Query.cpp index a33fe06b..0450ba9e 100644 --- a/cucumber_cpp/library/Query.cpp +++ b/cucumber_cpp/library/Query.cpp @@ -28,6 +28,7 @@ #include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_started.hpp" #include "cucumber/messages/undefined_parameter_type.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include @@ -93,64 +94,12 @@ namespace cucumber_cpp::library return scenario->name; } - Query& Query::operator+=(const cucumber::messages::envelope& envelope) - { - if (envelope.meta) - meta = std::make_unique(*envelope.meta); - - if (envelope.gherkin_document) - *this += *envelope.gherkin_document; - - if (envelope.pickle) - *this += *envelope.pickle; - - if (envelope.hook) - *this += *envelope.hook; - - if (envelope.step_definition) - *this += *envelope.step_definition; - - if (envelope.test_run_started) - *this += *envelope.test_run_started; - - if (envelope.test_run_hook_started) - *this += *envelope.test_run_hook_started; - - if (envelope.test_run_hook_finished) - *this += *envelope.test_run_hook_finished; - - if (envelope.test_case) - *this += *envelope.test_case; - - if (envelope.test_case_started) - *this += *envelope.test_case_started; - - if (envelope.test_step_started) - *this += *envelope.test_step_started; - - if (envelope.attachment) - *this += *envelope.attachment; - - if (envelope.test_step_finished) - *this += *envelope.test_step_finished; - - if (envelope.test_case_finished) - *this += *envelope.test_case_finished; - - if (envelope.test_run_finished) - *this += *envelope.test_run_finished; - - if (envelope.suggestion) - *this += *envelope.suggestion; - - if (envelope.undefined_parameter_type) - *this += *envelope.undefined_parameter_type; - - if (envelope.parameter_type) - *this += *envelope.parameter_type; - - return *this; - } + Query::Query(util::Broadcaster& broadcaster) + : util::Listener{ broadcaster, [this](const cucumber::messages::envelope& envelope) + { + operator+=(envelope); + } } + {} const Lineage& Query::FindLineageByPickle(const cucumber::messages::pickle& pickle) const { @@ -242,8 +191,66 @@ namespace cucumber_cpp::library return testCaseFinishedByTestCaseStartedId; } - void - Query::operator+=(const cucumber::messages::gherkin_document& gherkinDocument) + void Query::operator+=(const cucumber::messages::envelope& envelope) + { + if (envelope.meta) + meta = std::make_unique(*envelope.meta); + + if (envelope.gherkin_document) + *this += *envelope.gherkin_document; + + if (envelope.pickle) + *this += *envelope.pickle; + + if (envelope.hook) + *this += *envelope.hook; + + if (envelope.step_definition) + *this += *envelope.step_definition; + + if (envelope.test_run_started) + *this += *envelope.test_run_started; + + if (envelope.test_run_hook_started) + *this += *envelope.test_run_hook_started; + + if (envelope.test_run_hook_finished) + *this += *envelope.test_run_hook_finished; + + if (envelope.test_case) + *this += *envelope.test_case; + + if (envelope.test_case_started) + *this += *envelope.test_case_started; + + if (envelope.test_step_started) + *this += *envelope.test_step_started; + + if (envelope.attachment) + *this += *envelope.attachment; + + if (envelope.test_step_finished) + *this += *envelope.test_step_finished; + + if (envelope.test_case_finished) + *this += *envelope.test_case_finished; + + if (envelope.test_run_finished) + *this += *envelope.test_run_finished; + + if (envelope.suggestion) + *this += *envelope.suggestion; + + if (envelope.undefined_parameter_type) + *this += *envelope.undefined_parameter_type; + + if (envelope.parameter_type) + *this += *envelope.parameter_type; + + BroadcastEvent(envelope); + } + + void Query::operator+=(const cucumber::messages::gherkin_document& gherkinDocument) { if (gherkinDocument.feature) *this += { *gherkinDocument.feature, Lineage{ std::make_unique(gherkinDocument) } }; diff --git a/cucumber_cpp/library/Query.hpp b/cucumber_cpp/library/Query.hpp index 59f13a79..414f8c8a 100644 --- a/cucumber_cpp/library/Query.hpp +++ b/cucumber_cpp/library/Query.hpp @@ -31,6 +31,7 @@ #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_started.hpp" #include "cucumber/messages/undefined_parameter_type.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include @@ -69,8 +70,10 @@ namespace cucumber_cpp::library Lineage operator+(Lineage lineage, std::uint32_t featureIndex); struct Query + : util::Broadcaster + , util::Listener { - Query& operator+=(const cucumber::messages::envelope& envelope); + explicit Query(util::Broadcaster& broadcaster); auto GetPickles() const { @@ -106,8 +109,9 @@ namespace cucumber_cpp::library const std::map>& TestCaseFinishedByTestCaseStartedId() const; private: - void - operator+=(const cucumber::messages::gherkin_document& gherkinDocument); + void operator+=(const cucumber::messages::envelope& envelope); + + void operator+=(const cucumber::messages::gherkin_document& gherkinDocument); void operator+=(const cucumber::messages::pickle& pickle); void operator+=(const cucumber::messages::hook& hook); void operator+=(const cucumber::messages::step_definition& stepDefinition); From a0f27cc6110cf345a3f41224d956651c14b93dd5 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 25 Dec 2025 00:41:53 +0000 Subject: [PATCH 061/196] moved to new api::Formatters to register/enable formatters --- compatibility/compatibility.cpp | 4 +- cucumber_cpp/library/Application.cpp | 3 +- cucumber_cpp/library/Application.hpp | 5 +- cucumber_cpp/library/api/CMakeLists.txt | 2 + cucumber_cpp/library/api/Formatters.cpp | 39 +++++++++++++++ cucumber_cpp/library/api/Formatters.hpp | 49 +++++++++++++++++++ cucumber_cpp/library/api/RunCucumber.cpp | 12 +++-- cucumber_cpp/library/api/RunCucumber.hpp | 5 +- cucumber_cpp/library/formatter/Formatter.cpp | 7 +-- cucumber_cpp/library/formatter/Formatter.hpp | 5 +- .../library/formatter/PrettyPrinter.cpp | 2 - .../library/formatter/PrettyPrinter.hpp | 2 - 12 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 cucumber_cpp/library/api/Formatters.cpp create mode 100644 cucumber_cpp/library/api/Formatters.hpp diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index fb91a78a..f7eaaa2e 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -1,5 +1,6 @@ #include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Duration.hpp" @@ -267,7 +268,8 @@ namespace compatibility BroadcastListener broadcastListener{ devkit.ndjsonFile, devkit.ndjsonFile.parent_path() / "expected.ndjson", devkit.ndjsonFile.parent_path() / "actual.ndjson", broadcaster }; - cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster); + cucumber_cpp::library::api::Formatters formatters; + cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, { "summary" }, {}); broadcastListener.CompareEnvelopes(); } diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 58348a19..53e958bb 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -188,7 +188,6 @@ namespace cucumber_cpp::library void Application::RunFeatures() { - const auto runOptions = support::RunOptions{ .sources = { .paths = GetFeatureFiles(options), @@ -207,7 +206,7 @@ namespace cucumber_cpp::library auto& listeners = testing::UnitTest::GetInstance()->listeners(); auto* defaultEventListener = listeners.Release(listeners.default_result_printer()); - runPassed = api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster); + runPassed = api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, options.format, options.formatOptions); listeners.Append(defaultEventListener); diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index c3afcd56..98ef822a 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -6,6 +6,7 @@ #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" @@ -37,7 +38,7 @@ namespace cucumber_cpp::library bool failFast{ false }; std::set format{}; - std::set formatOptions{}; + std::string formatOptions{}; std::string language{ "en" }; @@ -77,6 +78,8 @@ namespace cucumber_cpp::library std::unique_ptr programContext{ std::make_unique(contextStorageFactory) }; Context& programContextRef{ *programContext }; + api::Formatters formatters; + util::Broadcaster broadcaster; // ReportHandlerValidator reportHandlerValidator; diff --git a/cucumber_cpp/library/api/CMakeLists.txt b/cucumber_cpp/library/api/CMakeLists.txt index 5334f26d..7dc4fca3 100644 --- a/cucumber_cpp/library/api/CMakeLists.txt +++ b/cucumber_cpp/library/api/CMakeLists.txt @@ -1,6 +1,8 @@ add_library(cucumber_cpp.library.api STATIC ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.api PRIVATE + Formatters.cpp + Formatters.hpp Gherkin.cpp Gherkin.hpp RunCucumber.cpp diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp new file mode 100644 index 00000000..634b4d78 --- /dev/null +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -0,0 +1,39 @@ +#include "cucumber_cpp/library/api/Formatters.hpp" +#include "cucumber_cpp/library/Query.hpp" +#include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" +#include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::api +{ + Formatters::Formatters() + { + RegisterFormatter("pretty"); + RegisterFormatter("summary"); + } + + std::vector Formatters::GetAvailableFormatterNames() const + { + auto values = availableFormatters | std::views::keys; + return { values.begin(), values.end() }; + } + + std::list> Formatters::EnableFormatters(const std::set& format, const std::string& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) + { + std::list> activeFormatters; + + for (const auto& formatterName : format) + activeFormatters.emplace_back(availableFormatters.at(formatterName)(supportCodeLibrary, query, eventDataCollector, output)); + + return activeFormatters; + } +} diff --git a/cucumber_cpp/library/api/Formatters.hpp b/cucumber_cpp/library/api/Formatters.hpp new file mode 100644 index 00000000..6fd85342 --- /dev/null +++ b/cucumber_cpp/library/api/Formatters.hpp @@ -0,0 +1,49 @@ +#ifndef API_FORMATTERS_HPP +#define API_FORMATTERS_HPP + +#include "cucumber_cpp/library/Query.hpp" +#include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::api +{ + struct Formatters + { + Formatters(); + + template + void RegisterFormatter(const std::string& name); + + std::vector GetAvailableFormatterNames() const; + + [[nodiscard]] std::list> EnableFormatters(const std::set& format, const std::string& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); + + private: + std::map(support::SupportCodeLibrary&, Query&, const formatter::helper::EventDataCollector&, std::ostream&)>> availableFormatters; + }; + + //////////////////// + // Implementation // + //////////////////// + + template + void Formatters::RegisterFormatter(const std::string& name) + { + availableFormatters.try_emplace(name, [](support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) + { + return std::make_unique(supportCodeLibrary, query, eventDataCollector, output); + }); + } +} + +#endif diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index ae2fb81a..aabce1d2 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -7,7 +7,9 @@ #include "cucumber/messages/step_definition_pattern.hpp" #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/Gherkin.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" @@ -17,12 +19,13 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" -#include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include #include #include +#include +#include #include namespace cucumber_cpp::library::api @@ -121,7 +124,7 @@ namespace cucumber_cpp::library::api } } - bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster) + bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set& format, const std::string& formatOptions) { cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); @@ -137,8 +140,9 @@ namespace cucumber_cpp::library::api }; formatter::helper::EventDataCollector eventDataCollector{ broadcaster }; - formatter::PrettyPrinter prettyPrinter{ supportCodeLibrary, broadcaster, eventDataCollector }; - formatter::SummaryFormatter summaryFormatter{ supportCodeLibrary, broadcaster, eventDataCollector }; + Query query{ broadcaster }; + + const auto activeFormatters = formatters.EnableFormatters(format, formatOptions, supportCodeLibrary, query, eventDataCollector); const auto pickleSources = CollectPickles(options.sources, idGenerator, broadcaster); diff --git a/cucumber_cpp/library/api/RunCucumber.hpp b/cucumber_cpp/library/api/RunCucumber.hpp index f343ee5d..f8d45a11 100644 --- a/cucumber_cpp/library/api/RunCucumber.hpp +++ b/cucumber_cpp/library/api/RunCucumber.hpp @@ -2,13 +2,16 @@ #define API_RUN_CUCUMBER_HPP #include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include namespace cucumber_cpp::library::api { - bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster); + bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set& format, const std::string& formatOptions); } #endif diff --git a/cucumber_cpp/library/formatter/Formatter.cpp b/cucumber_cpp/library/formatter/Formatter.cpp index e7202759..52a4aaaf 100644 --- a/cucumber_cpp/library/formatter/Formatter.cpp +++ b/cucumber_cpp/library/formatter/Formatter.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber/messages/envelope.hpp" +#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -8,13 +9,13 @@ namespace cucumber_cpp::library::formatter { - Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream) - : util::Listener{ broadcaster, [this](const cucumber::messages::envelope& envelope) + Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, Query& query, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream) + : util::Listener{ query, [this](const cucumber::messages::envelope& envelope) { OnEnvelope(envelope); } } , supportCodeLibrary{ supportCodeLibrary } - , broadcaster{ broadcaster } + , query{ query } , eventDataCollector{ eventDataCollector } , outputStream{ outputStream } { diff --git a/cucumber_cpp/library/formatter/Formatter.hpp b/cucumber_cpp/library/formatter/Formatter.hpp index e62f2a40..52bdba1e 100644 --- a/cucumber_cpp/library/formatter/Formatter.hpp +++ b/cucumber_cpp/library/formatter/Formatter.hpp @@ -2,6 +2,7 @@ #define FORMATTER_FORMATTER_HPP #include "cucumber/messages/envelope.hpp" +#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -14,14 +15,14 @@ namespace cucumber_cpp::library::formatter struct Formatter : util::Listener { - Formatter(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream = std::cout); + Formatter(support::SupportCodeLibrary& supportCodeLibrary, Query& query, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream = std::cout); virtual ~Formatter() = default; protected: virtual void OnEnvelope(const cucumber::messages::envelope& envelope) = 0; support::SupportCodeLibrary& supportCodeLibrary; - util::Broadcaster& broadcaster; + Query& query; const helper::EventDataCollector& eventDataCollector; std::ostream& outputStream; }; diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyPrinter.cpp index 7b231494..985b41a8 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.cpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.cpp @@ -46,8 +46,6 @@ namespace cucumber_cpp::library::formatter void PrettyPrinter::OnEnvelope(const cucumber::messages::envelope& envelope) { - query += envelope; - if (envelope.test_case_started) { CalculateIndent(envelope.test_case_started.value()); diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.hpp b/cucumber_cpp/library/formatter/PrettyPrinter.hpp index f7a4ea34..976fcbaa 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.hpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.hpp @@ -59,8 +59,6 @@ namespace cucumber_cpp::library::formatter std::set printedFeatureUris; std::set printedRuleIds; - - Query query; }; } From da2fcfb8f4df8a27160ab782ccac6016dbffe3ba Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 5 Jan 2026 09:54:03 +0000 Subject: [PATCH 062/196] remove unused eventDataCollector member from PrettyPrinter --- cucumber_cpp/library/formatter/PrettyPrinter.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.hpp b/cucumber_cpp/library/formatter/PrettyPrinter.hpp index 976fcbaa..7dc4e91f 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.hpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.hpp @@ -33,8 +33,6 @@ namespace cucumber_cpp::library::formatter using Formatter::Formatter; private: - bool eventDataCollector{ false }; - void OnEnvelope(const cucumber::messages::envelope& envelope) override; void CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted); From 88e3a93c6b294787fdce71920665364bfaac143b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 5 Jan 2026 09:54:16 +0000 Subject: [PATCH 063/196] refactor: simplify title formatting in PrettyPrinter --- cucumber_cpp/library/formatter/PrettyPrinter.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyPrinter.cpp index 985b41a8..c2e7b0b1 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.cpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.cpp @@ -189,9 +189,15 @@ namespace cucumber_cpp::library::formatter const auto padding = maxContentLength - title.length(); + if (!formatTitle) + formatTitle = [](std::string_view str) + { + return std::string{ str }; + }; + if (uri.has_value() && line.has_value()) - support::print(outputStream, "{:{}}{}{:{}} {}\n", "", indent, formatTitle ? formatTitle(title) : std::string(title), "", padding, ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); + support::print(outputStream, "{:{}}{}{:{}} {}\n", "", indent, formatTitle(title), "", padding, ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); else - support::print(outputStream, "{:{}}{}\n", "", indent, formatTitle ? formatTitle(title) : std::string(title)); + support::print(outputStream, "{:{}}{}\n", "", indent, formatTitle(title)); } } From ee24b6f7decf4928810e57cd2085d2db77df2161 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 5 Jan 2026 09:56:54 +0000 Subject: [PATCH 064/196] refactor: update message formatting in FormatMessage function --- .../library/cucumber_expression/test/TestExpression.cpp | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index 73b3c4ff..7db7d63b 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -3,13 +3,11 @@ #include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" -#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" #include "yaml-cpp/yaml.h" #include "gmock/gmock.h" -#include #include #include #include @@ -17,9 +15,7 @@ #include #include #include -#include #include -#include #include #include #include @@ -41,7 +37,7 @@ namespace cucumber_cpp::library::cucumber_expression std::string FormatMessage(const std::string& file, const YAML::Node& node, const Expression& expression) { - return std::format("input: {}\n" + return std::format("file: {}\n" "failed to match {}\n" "regex {}\n" "against {}", From f3f2b9f89fd94f6ff9bb98f08478c916bb9b9bee Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 5 Jan 2026 10:09:34 +0000 Subject: [PATCH 065/196] refactor: enhance error message in BuildArguments for better clarity --- cucumber_cpp/library/cucumber_expression/Argument.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/cucumber_expression/Argument.cpp b/cucumber_cpp/library/cucumber_expression/Argument.cpp index 9ddbcc6c..2745c277 100644 --- a/cucumber_cpp/library/cucumber_expression/Argument.cpp +++ b/cucumber_cpp/library/cucumber_expression/Argument.cpp @@ -2,9 +2,11 @@ #include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include +#include #include #include #include +#include #include namespace cucumber_cpp::library::cucumber_expression @@ -17,7 +19,7 @@ namespace cucumber_cpp::library::cucumber_expression std::vector Argument::BuildArguments(const cucumber::messages::group& group, std::span parameters) { if (group.children.size() != parameters.size()) - throw std::runtime_error("Mismatch between number of groups and parameters"); + throw std::runtime_error(std::format("Mismatch between number of groups ({}) and parameters ({})", group.children.size(), parameters.size())); std::size_t index{ 0 }; auto converted = parameters | std::views::transform([&group, &index](const Parameter& parameter) -> Argument From 2c52a4a6d6cec646521485a13922f222595e3936 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 5 Jan 2026 11:16:50 +0000 Subject: [PATCH 066/196] refactor: streamline hook assembly in AssembleTestSteps for improved performance --- .../library/assemble/AssembleTestSuites.cpp | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 3b1c1ba5..180d0a13 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -40,18 +40,6 @@ namespace cucumber_cpp::library::assemble return pair.second.has_value(); } - void AssembleBeforeHooks(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) - { - for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::before, pickleSource.pickle->tags)) - testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); - } - - void AssembleAfterHooks(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) - { - for (const auto& hookId : supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags) | std::views::reverse) - testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); - } - void AssembleSteps(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { for (const auto& step : pickleSource.pickle->steps) @@ -79,9 +67,18 @@ namespace cucumber_cpp::library::assemble void AssembleTestSteps(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { - AssembleBeforeHooks(supportCodeLibrary, pickleSource, testCase, idGenerator); + auto beforeHooks = supportCodeLibrary.hookRegistry.FindIds(HookType::before, pickleSource.pickle->tags); + auto afterHooks = supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags); + + testCase.test_steps.reserve(beforeHooks.size() + pickleSource.pickle->steps.size() + afterHooks.size()); + + for (const auto& hookId : beforeHooks) + testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); + AssembleSteps(supportCodeLibrary, pickleSource, testCase, idGenerator); - AssembleAfterHooks(supportCodeLibrary, pickleSource, testCase, idGenerator); + + for (const auto& hookId : afterHooks | std::views::reverse) + testCase.test_steps.emplace_back(hookId, idGenerator->next_id()); } } @@ -103,8 +100,6 @@ namespace cucumber_cpp::library::assemble .test_run_started_id = std::make_optional(testRunStartedId) }; - testCase.test_steps.reserve(pickleSource.pickle->steps.size() * 2); // steps + hooks - AssembleTestSteps(supportCodeLibrary, pickleSource, testCase, idGenerator); broadcaster.BroadcastEvent(cucumber::messages::envelope{ .test_case = testCase }); From 4782695af74a2d7a142d6125a0b879af0dafef73 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 5 Jan 2026 14:48:58 +0000 Subject: [PATCH 067/196] refactor: update test script and application files to improve formatter handling and command parsing --- cucumber_cpp/acceptance_test/test.bats | 62 ++++++------- cucumber_cpp/library/Application.cpp | 109 ++++++++++++----------- cucumber_cpp/library/Application.hpp | 16 +--- cucumber_cpp/library/api/Formatters.cpp | 16 ++-- cucumber_cpp/library/api/Formatters.hpp | 21 +++-- cucumber_cpp/library/api/RunCucumber.cpp | 3 +- cucumber_cpp/library/api/RunCucumber.hpp | 3 +- 7 files changed, 119 insertions(+), 111 deletions(-) diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index 031c403a..7bbe2555 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -1,7 +1,7 @@ #!/usr/bin/env bats setup_file() { - acceptance_test=$(find / -name "cucumber_cpp.acceptance_test" -print -quit) + acceptance_test=$(find . -name "cucumber_cpp.acceptance_test" -print -quit) export acceptance_test } @@ -15,70 +15,70 @@ teardown() { } @test "Successful test" { - run $acceptance_test run --format summary --tags "@result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Parse tag expression" { - run $acceptance_test run --format summary --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Failed tests" { - run $acceptance_test run --format summary --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure } @test "Undefined tests" { - run $acceptance_test run --format summary --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "UNDEFINED Given a missing step" assert_output --partial "SKIPPED Then this should be skipped" } @test "No tests" { - run $acceptance_test run --format summary --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features assert_success } @test "All features in a folder" { - run $acceptance_test run --format summary cucumber_cpp/acceptance_test/features/subfolder + run $acceptance_test --format summary cucumber_cpp/acceptance_test/features/subfolder assert_success assert_output --partial "2 scenarios" assert_output --partial "2 passed" } @test "Missing mandatory feature argument" { - run $acceptance_test run --format summary + run $acceptance_test --format summary assert_failure assert_output --partial "paths is required" } # @test "Missing mandatory report argument" { -# run $acceptance_test run --format summary cucumber_cpp/acceptance_test/features +# run $acceptance_test --format summary cucumber_cpp/acceptance_test/features # assert_failure # assert_output --partial "--report is required" # } @test "Missing mandatory custom argument" { - run $acceptance_test.custom run --format summary cucumber_cpp/acceptance_test/features + run $acceptance_test.custom --format summary cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "--required is required" } @test "Second feature file does not overwrite success with an undefined status" { - run $acceptance_test run --format summary --tags "@undefinedsuccess and @result:success" -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature + run $acceptance_test --format summary --tags "@undefinedsuccess and @result:success" -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature assert_success } # @test "Valid reporters only" { -# run $acceptance_test run --format summary cucumber_cpp/acceptance_test/features +# run $acceptance_test --format summary cucumber_cpp/acceptance_test/features # assert_failure # assert_output --partial "--report: 'doesnotexist' is not a reporter" # } @test "Run Program hooks" { - run $acceptance_test run --format summary --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_ALL" @@ -86,7 +86,7 @@ teardown() { } @test "Run Scenario hooks" { - run $acceptance_test run --format summary --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -97,7 +97,7 @@ teardown() { } @test "Run Step hooks" { - run $acceptance_test run --format summary --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "HOOK_BEFORE_SCENARIO" @@ -108,7 +108,7 @@ teardown() { } @test "Run Scenario and Step hooks" { - run $acceptance_test run --format summary --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -119,70 +119,70 @@ teardown() { } @test "Dry run with known failing steps" { - run $acceptance_test run --format summary --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test run --format summary --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features assert_success } @test "Dry run with known missing steps" { - run $acceptance_test run --format summary --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test run --format summary --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features assert_success assert_output --partial "UNDEFINED Given a missing step" } @test "Test the and keyword" { - run $acceptance_test run --format summary --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--and--" } @test "Test the but keyword" { - run $acceptance_test run --format summary --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--but--" } @test "Test the asterisk keyword" { - run $acceptance_test run --format summary --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features assert_output --partial "print: --when--" assert_output --partial "print: --asterisk--" assert_success } @test "Test passing scenario after failed scenario reports feature as failed" { - run $acceptance_test run --format summary --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "2 scenarios" assert_output --partial "1 passed" } @test "Test failing hook before results in error" { - run $acceptance_test run --format summary --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED Before" } @test "Test failing hook after results in error" { - run $acceptance_test run --format summary --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED After" } @test "Test throwing hook results in error" { - run $acceptance_test run --format summary --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED Before" } @test "Test error program hook results in error and skipped steps" { - run $acceptance_test.custom run --format summary --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features + run $acceptance_test.custom --format summary --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "HOOK_BEFORE_ALL" assert_output --partial "HOOK_AFTER_ALL" @@ -191,21 +191,21 @@ teardown() { } @test "Test unicode" { - run $acceptance_test run --format summary --tags "@unicode" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@unicode" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "1 scenario" assert_output --partial "1 passed" } # @test "Test unused step reporting" { -# run $acceptance_test run --format summary --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features +# run $acceptance_test --format summary --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features # assert_success # assert_output --regexp ".*The following steps have not been used:.*this step is not being used.*" # refute_output --regexp ".*The following steps have not been used:.*this step is being used.*" # } @test "Test unused steps by default not reported" { - run $acceptance_test run --format summary --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "The following steps have not been used:" } diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 53e958bb..8ac6253a 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -2,6 +2,7 @@ #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/Errors.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" @@ -14,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -29,6 +31,7 @@ #include #include #include +#include #include namespace cucumber_cpp::library @@ -70,28 +73,25 @@ namespace cucumber_cpp::library return files; } - } - // ReportHandlerValidator::ReportHandlerValidator(const report::Reporters& reporters) - // : CLI::Validator("ReportHandler", [&reporters, cachedAvailableReporters = std::optional>{}](const std::string& str) mutable - // { - // if (!cachedAvailableReporters) - // cachedAvailableReporters = reporters.AvailableReporters(); + struct RemoveDefaultEventListener + { + ~RemoveDefaultEventListener() + { + listeners.Append(defaultEventListener); + } - // if (std::ranges::find(*cachedAvailableReporters, str) == cachedAvailableReporters->end()) - // return std::string{ "'" + str + "' is not a reporter" }; - // else - // return std::string{}; - // }) - // {} + private: + testing::TestEventListeners& listeners{ testing::UnitTest::GetInstance()->listeners() }; + testing::TestEventListener* defaultEventListener{ listeners.Release(listeners.default_result_printer()) }; + }; + } Application::Application(std::shared_ptr contextStorageFactory, bool removeDefaultGoogleTestListener) : contextStorageFactory{ contextStorageFactory } , removeDefaultGoogleTestListener{ removeDefaultGoogleTestListener } { - cli.require_subcommand(1); - - runCommand = cli.add_subcommand("run")->parse_complete_callback([this] + cli.parse_complete_callback([this] { RunFeatures(); }); @@ -99,6 +99,28 @@ namespace cucumber_cpp::library int Application::Run(int argc, const char* const* argv) { + const auto formattersSet = formatters.GetAvailableFormatterNames(); + const auto formatterDescription = std::format("{{{}}}", Join(formattersSet | std::views::transform([](const auto& pair) + { + if (pair.second) + return std::format("{}<<:output>>", pair.first); + else + return pair.first; + }), + ",")); + + CLI::Validator formatValidator{ [&formattersSet](const std::string& str) -> std::string + { + const auto colon = str.find(':'); + const auto formatter = str.substr(0, colon); + const auto iter = std::ranges::find(formattersSet, formatter, &std::pair::first); + if (iter == formattersSet.end()) + return std::format("'{}' is not a valid formatter", formatter); + else + return ""; + }, + formatterDescription }; + try { const std::map orderingMap{ @@ -106,36 +128,24 @@ namespace cucumber_cpp::library { "reverse", support::RunOptions::Ordering::reverse }, }; - runCommand->add_flag("-d,--dry-run", options.dryRun, "Perform a dry run without executing steps"); - runCommand->add_flag("--fail-fast", options.failFast, "Stop execution on first failure"); - runCommand->add_option("--format", options.format, "specify the output format, optionally supply PATH to redirect formatter output. Available formats:\n"); - runCommand->add_option("--format-options", options.formatOptions, "provide options for formatters"); - runCommand->add_option("--language", options.language, "Default langauge for feature files, eg 'en'")->default_str(options.language); - runCommand->add_option("--order", options.ordering, "Run scenarios in specificed order")->transform(CLI::CheckedTransformer(orderingMap, CLI::ignore_case)); - auto* retryOpt = runCommand->add_option("--retry", options.retry, "Number of times to retry failed scenarios")->default_val(options.retry); - runCommand->add_option("--retry-tag-filter", options.retryTagFilter, "Only retry scenarios matching this tag expression")->needs(retryOpt); - runCommand->add_flag("--strict,!--no-strict", options.strict, "Fail if there are pending steps")->default_val(options.strict); - - CLI::deprecate_option(runCommand->add_option("--tag", options.tags, "Cucumber tag expression"), "-t,--tags"); - runCommand->add_option("-t,--tags", options.tags, "Cucumber tag expression"); - - CLI::deprecate_option(runCommand->add_option("-f,--feature", options.paths, "Paths to where your feature files are")->check(CLI::ExistingPath), "paths"); - runCommand->add_option("paths", options.paths, "Paths to where your feature files are")->required()->check(CLI::ExistingPath); + cli.add_flag("-d,--dry-run", options.dryRun, "Perform a dry run without executing steps"); + cli.add_flag("--fail-fast", options.failFast, "Stop execution on first failure"); + cli.add_option("--format", options.format, "specify the output format, optionally supply PATH to redirect formatter output.")->check(formatValidator); + cli.add_option("--format-options", options.formatOptions, "provide options for formatters"); + cli.add_option("--language", options.language, "Default langauge for feature files, eg 'en'")->default_str(options.language); + cli.add_option("--order", options.ordering, "Run scenarios in specificed order")->transform(CLI::CheckedTransformer(orderingMap, CLI::ignore_case)); + auto* retryOpt = cli.add_option("--retry", options.retry, "Number of times to retry failed scenarios")->default_val(options.retry); + cli.add_option("--retry-tag-filter", options.retryTagFilter, "Only retry scenarios matching this tag expression")->needs(retryOpt); + cli.add_flag("--strict,!--no-strict", options.strict, "Fail if there are pending steps")->default_val(options.strict); - // runCommand->add_option("-r,--report", options.reporters, "Name of the report generator: ")->required()->group("report generation"); //->check(reportHandlerValidator); - // runCommand->add_option("--outputfolder", options.outputfolder, "Specifies the output folder for generated report files")->group("report generation"); - // runCommand->add_option("--reportfile", options.reportfile, "Specifies the output name for generated report files")->group("report generation"); - // runCommand->add_flag("--unused", options.printStepsNotUsed, "Show step definitions that were not used"); + CLI::deprecate_option(cli.add_option("--tag", options.tags, "Cucumber tag expression"), "-t,--tags"); + cli.add_option("-t,--tags", options.tags, "Cucumber tag expression"); - // reporters.Add("console", std::make_unique()); - // reporters.Add("junit-xml", std::make_unique(options.outputfolder, options.reportfile)); + CLI::deprecate_option(cli.add_option("-f,--feature", options.paths, "Paths to where your feature files are")->check(CLI::ExistingPath), "paths"); + cli.add_option("paths", options.paths, "Paths to where your feature files are")->required()->check(CLI::ExistingPath); ProgramContext().InsertRef(options); - // const auto reportDescription = runCommand->get_option("--report")->get_description(); - // const auto joinedReporters = reportDescription; // + Join(reporters.AvailableReporters(), ", "); - - // runCommand->get_option("--report")->description(joinedReporters); cli.parse(argc, argv); } catch (const CLI::ParseError& e) @@ -168,7 +178,7 @@ namespace cucumber_cpp::library CLI::App& Application::CliParser() { - return *runCommand; + return cli; } Context& Application::ProgramContext() @@ -181,10 +191,10 @@ namespace cucumber_cpp::library return parameterRegistry; } - // void Application::AddReportHandler(const std::string& name, std::unique_ptr&& reporter) - // { - // reporters.Add(name, std::move(reporter)); - // } + api::Formatters& Application::Formatters() + { + return formatters; + } void Application::RunFeatures() { @@ -203,15 +213,8 @@ namespace cucumber_cpp::library }, }; - auto& listeners = testing::UnitTest::GetInstance()->listeners(); - auto* defaultEventListener = listeners.Release(listeners.default_result_printer()); - + RemoveDefaultEventListener removeDefaultListenerExceptionSafe; runPassed = api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, options.format, options.formatOptions); - - listeners.Append(defaultEventListener); - - // if (options.printStepsNotUsed) - // PrintStepsNotUsed(stepRegistry); } void Application::PrintStepsNotUsed(const StepRegistry& stepRegistry) const diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 98ef822a..336fbbdf 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -23,21 +24,16 @@ namespace cucumber_cpp::library { - // struct ReportHandlerValidator : public CLI::Validator - // { - // explicit ReportHandlerValidator(const report::Reporters& reporters); - // }; - struct Application { struct Options { - std::set paths{}; + std::set> paths{}; bool dryRun{ false }; bool failFast{ false }; - std::set format{}; + std::set> format{}; std::string formatOptions{}; std::string language{ "en" }; @@ -59,8 +55,7 @@ namespace cucumber_cpp::library CLI::App& CliParser(); Context& ProgramContext(); cucumber_expression::ParameterRegistry& ParameterRegistration(); - - // void AddReportHandler(const std::string& name, std::unique_ptr&& reporter); + api::Formatters& Formatters(); private: void DryRunFeatures(); @@ -72,7 +67,6 @@ namespace cucumber_cpp::library Options options; CLI::App cli; - CLI::App* runCommand; std::shared_ptr contextStorageFactory; std::unique_ptr programContext{ std::make_unique(contextStorageFactory) }; @@ -82,8 +76,6 @@ namespace cucumber_cpp::library util::Broadcaster broadcaster; - // ReportHandlerValidator reportHandlerValidator; - cucumber_expression::ParameterRegistry parameterRegistry{}; bool removeDefaultGoogleTestListener; support::StopWatchHighResolutionClock stopwatchHighResolutionClock; diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index 634b4d78..9674f457 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -5,13 +5,14 @@ #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include #include #include #include #include #include #include -#include +#include namespace cucumber_cpp::library::api { @@ -21,18 +22,21 @@ namespace cucumber_cpp::library::api RegisterFormatter("summary"); } - std::vector Formatters::GetAvailableFormatterNames() const + std::set> Formatters::GetAvailableFormatterNames() const { - auto values = availableFormatters | std::views::keys; - return { values.begin(), values.end() }; + auto view = availableFormatters | std::views::transform([](const auto& pair) -> std::pair + { + return { pair.first, pair.second.hasOutput }; + }); + return { view.begin(), view.end() }; } - std::list> Formatters::EnableFormatters(const std::set& format, const std::string& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) + std::list> Formatters::EnableFormatters(const std::set>& format, const std::string& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) { std::list> activeFormatters; for (const auto& formatterName : format) - activeFormatters.emplace_back(availableFormatters.at(formatterName)(supportCodeLibrary, query, eventDataCollector, output)); + activeFormatters.emplace_back(availableFormatters.at(formatterName).factory(supportCodeLibrary, query, eventDataCollector, output)); return activeFormatters; } diff --git a/cucumber_cpp/library/api/Formatters.hpp b/cucumber_cpp/library/api/Formatters.hpp index 6fd85342..2665908b 100644 --- a/cucumber_cpp/library/api/Formatters.hpp +++ b/cucumber_cpp/library/api/Formatters.hpp @@ -13,23 +13,29 @@ #include #include #include -#include +#include namespace cucumber_cpp::library::api { + struct RegisteredFormatter + { + std::function(support::SupportCodeLibrary&, Query&, const formatter::helper::EventDataCollector&, std::ostream&)> factory; + bool hasOutput{ false }; + }; + struct Formatters { Formatters(); template - void RegisterFormatter(const std::string& name); + void RegisterFormatter(const std::string& name, bool hasOutput = false); - std::vector GetAvailableFormatterNames() const; + std::set> GetAvailableFormatterNames() const; - [[nodiscard]] std::list> EnableFormatters(const std::set& format, const std::string& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); + [[nodiscard]] std::list> EnableFormatters(const std::set>& format, const std::string& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); private: - std::map(support::SupportCodeLibrary&, Query&, const formatter::helper::EventDataCollector&, std::ostream&)>> availableFormatters; + std::map availableFormatters; }; //////////////////// @@ -37,12 +43,13 @@ namespace cucumber_cpp::library::api //////////////////// template - void Formatters::RegisterFormatter(const std::string& name) + void Formatters::RegisterFormatter(const std::string& name, bool hasOutput) { availableFormatters.try_emplace(name, [](support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) { return std::make_unique(supportCodeLibrary, query, eventDataCollector, output); - }); + }, + hasOutput); } } diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index aabce1d2..43cf4c51 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -20,6 +20,7 @@ #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include #include #include @@ -124,7 +125,7 @@ namespace cucumber_cpp::library::api } } - bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set& format, const std::string& formatOptions) + bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set>& format, const std::string& formatOptions) { cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); diff --git a/cucumber_cpp/library/api/RunCucumber.hpp b/cucumber_cpp/library/api/RunCucumber.hpp index f8d45a11..1362ead2 100644 --- a/cucumber_cpp/library/api/RunCucumber.hpp +++ b/cucumber_cpp/library/api/RunCucumber.hpp @@ -6,12 +6,13 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include #include namespace cucumber_cpp::library::api { - bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set& format, const std::string& formatOptions); + bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set>& format, const std::string& formatOptions); } #endif From ef11c11e88f6da21a3420311fe69357ef5d7f9cc Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 09:33:21 +0000 Subject: [PATCH 068/196] refactor: enhance tests for TreeRegexp to clarify limitations of std::regex support --- .../test/TestTreeRegexp.cpp | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp index ade690e5..bec78ae8 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestTreeRegexp.cpp @@ -61,17 +61,32 @@ namespace cucumber_cpp::library::cucumber_expression TEST(TestTreeRegexp, DISABLED_ignores_positive_lookbehind_as_a_non_capturing_group) { + // std::regex does not support positive lookbehind TreeRegexp treeRegexp{ R"__(a(.+)(?<=c)$)__" }; + const auto group = *treeRegexp.MatchToGroup("abc"); + EXPECT_THAT(group.value.value(), testing::StrEq("abc")); + EXPECT_THAT(group.children.size(), 1); + EXPECT_THAT(group.children[0].value.value(), testing::StrEq("bc")); } TEST(TestTreeRegexp, DISABLED_ignores_negative_lookbehind_as_a_non_capturing_group) { + // std::regex does not support negative lookbehind TreeRegexp treeRegexp{ R"__(a(.+?)(?b)c)__" }; + const auto group = *treeRegexp.MatchToGroup("abc"); + EXPECT_THAT(group.value.value(), testing::StrEq("abc")); + EXPECT_THAT(group.children.size(), 1); + EXPECT_THAT(group.children[0].value.value(), testing::StrEq("b")); } TEST(TestTreeRegexp, matches_optional_group) @@ -136,7 +151,8 @@ namespace cucumber_cpp::library::cucumber_expression TEST(TestTreeRegexp, DISABLED_works_with_case_insensitive_flag) { - TreeRegexp treeRegexp{ R"__(HELLO/)__" }; + // std::regex does not support inline case insensitive flag + TreeRegexp treeRegexp{ R"__(HELLO/i)__" }; const auto group = *treeRegexp.MatchToGroup("hello"); EXPECT_THAT(group.value.value(), testing::StrEq("hello")); } @@ -151,8 +167,11 @@ namespace cucumber_cpp::library::cucumber_expression TEST(TestTreeRegexp, DISABLED_empty_look_ahead) { + // std::regex does not support positive lookbehind TreeRegexp treeRegexp{ R"__((?<=))__" }; const auto group = *treeRegexp.MatchToGroup(""); + EXPECT_THAT(group.value.value(), testing::StrEq("")); + EXPECT_THAT(group.children.size(), testing::Eq(0)); } TEST(TestTreeRegexp, does_not_consider_parenthesis_in_character_class_as_group) From c1d2a0b7e900e7a538a58301d3d9ba542394efe9 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 09:33:33 +0000 Subject: [PATCH 069/196] refactor: rename ExtractStep to ExtractSteps for clarity and consistency --- .../library/formatter/helper/GherkinDocumentParser.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp index 6e5a5097..7572e805 100644 --- a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp +++ b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp @@ -14,13 +14,13 @@ namespace cucumber_cpp::library::formatter::helper { namespace { - std::vector ExtractStep(const std::variant tests) + const std::vector& ExtractSteps(const std::variant& scenarioOrBackground) { - return std::visit([](const auto& item) + return std::visit([](const auto& item) -> const std::vector& { return item.steps; }, - tests); + scenarioOrBackground); } std::vector ExtractScenarioFromRuleChild(const cucumber::messages::rule_child& child) @@ -79,7 +79,7 @@ namespace cucumber_cpp::library::formatter::helper { auto steps = gherkinDocument.feature->children | std::views::transform(ExtractStepContainers) | std::views::join | - std::views::transform(ExtractStep) | std::views::join; + std::views::transform(ExtractSteps) | std::views::join; GherkinStepMap map; From 0ff58a4b4ce95fe772257bf16dea663b347007a2 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 09:34:29 +0000 Subject: [PATCH 070/196] refactor: rename FormatMessage to FormatTestFailureMessage for clarity --- .../test/TestExpression.cpp | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index 7db7d63b..980c82e1 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -35,7 +35,7 @@ namespace cucumber_cpp::library::cucumber_expression return testdata; } - std::string FormatMessage(const std::string& file, const YAML::Node& node, const Expression& expression) + std::string FormatTestFailureMessage(const std::string& file, const YAML::Node& node, const Expression& expression) { return std::format("file: {}\n" "failed to match {}\n" @@ -82,7 +82,7 @@ namespace cucumber_cpp::library::cucumber_expression const auto arguments = expression.MatchToArguments(testdata["text"].as()); - ASSERT_THAT(matchOpt, testing::IsTrue()) << FormatMessage(file, testdata, expression); + ASSERT_THAT(matchOpt, testing::IsTrue()) << FormatTestFailureMessage(file, testdata, expression); const auto& match = *matchOpt; for (std::size_t i = 0; i < testdata["expected_args"].size(); ++i) @@ -90,41 +90,41 @@ namespace cucumber_cpp::library::cucumber_expression const auto& argument = match[i]; if (argument.Name() == "") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "int") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "float") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "word") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "string") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "bigdecimal") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "biginteger") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "byte") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "short") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "long") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "double") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else FAIL() << "Unknown type: " << argument.Name() << " for:\n" - << FormatMessage(file, testdata, expression); + << FormatTestFailureMessage(file, testdata, expression); } } } From aa57ace472fd3004fa108bb1b1cd88e3357b99dc Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 09:39:48 +0000 Subject: [PATCH 071/196] chore: update SonarQube scan action to version 7.0.0 --- .github/workflows/static-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 5099c554..db8cfa40 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -60,7 +60,7 @@ jobs: run: | cp .build/Coverage/compile_commands.json compile_commands.json - - uses: sonarsource/sonarqube-scan-action@fd88b7d7ccbaefd23d8f36f73b59db7a3d246602 # v6.0.0 + - uses: sonarsource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} From 1eaefa6300739f948e458898d7b9866766cd40e6 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 10:17:36 +0000 Subject: [PATCH 072/196] refactor: add debug output steps to static analysis workflow for better visibility --- .github/workflows/static-analysis.yml | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index db8cfa40..ea80ecd0 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -22,16 +22,25 @@ jobs: env: SONAR_SERVER_URL: "https://sonarcloud.io" steps: + - run: du -sh / + - run: du -sh . + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 # Disable shallow clone to enable blame information persist-credentials: false + - run: du -sh / + - run: du -sh . + - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2.20 with: key: ${{ github.job }} max-size: 2G + - run: du -sh / + - run: du -sh . + - name: Build for coverage uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8 with: @@ -42,28 +51,46 @@ jobs: env: GTEST_OUTPUT: "xml:${{ github.workspace }}/testresults/" + - run: du -sh / + - run: du -sh . + - name: Run acceptance tests run: | bats --formatter junit cucumber_cpp/acceptance_test/test.bats | tee test-report.xml + - run: du -sh / + - run: du -sh . + - name: Collect coverage run: | gcovr --sonarqube=coverage.xml --exclude-lines-by-pattern '.*assert\(.*\);|.*really_assert\(.*\);|.*std::abort();' --exclude-unreachable-branches --exclude-throw-branches -j "$(nproc)" --exclude=.*/example/.* --exclude=.*/external/.* --exclude=.*/test/.* + - run: du -sh / + - run: du -sh . + - uses: philips-software/sonarqube-issue-conversion@9e9958764ba5fd1d302b039779dc902bedfa4d01 # v1.2.0 with: input: ${{ github.workspace }}/testresults/*.xml output: execution.xml transformation: gtest-to-generic-execution + - run: du -sh / + - run: du -sh . + - name: Convert results run: | cp .build/Coverage/compile_commands.json compile_commands.json + - run: du -sh / + - run: du -sh . + - uses: sonarsource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} + - run: du -sh / + - run: du -sh . + codeql: name: CodeQL runs-on: ubuntu-latest From 7231defc571de7da6b1384bfb60b37bdbadc6f1d Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 10:26:39 +0000 Subject: [PATCH 073/196] refactor: update static analysis workflow to include exit codes for directory size checks --- .github/workflows/static-analysis.yml | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index ea80ecd0..d5005d16 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -22,24 +22,24 @@ jobs: env: SONAR_SERVER_URL: "https://sonarcloud.io" steps: - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 # Disable shallow clone to enable blame information persist-credentials: false - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2.20 with: key: ${{ github.job }} max-size: 2G - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 - name: Build for coverage uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8 @@ -51,22 +51,22 @@ jobs: env: GTEST_OUTPUT: "xml:${{ github.workspace }}/testresults/" - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 - name: Run acceptance tests run: | bats --formatter junit cucumber_cpp/acceptance_test/test.bats | tee test-report.xml - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 - name: Collect coverage run: | gcovr --sonarqube=coverage.xml --exclude-lines-by-pattern '.*assert\(.*\);|.*really_assert\(.*\);|.*std::abort();' --exclude-unreachable-branches --exclude-throw-branches -j "$(nproc)" --exclude=.*/example/.* --exclude=.*/external/.* --exclude=.*/test/.* - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 - uses: philips-software/sonarqube-issue-conversion@9e9958764ba5fd1d302b039779dc902bedfa4d01 # v1.2.0 with: @@ -74,22 +74,22 @@ jobs: output: execution.xml transformation: gtest-to-generic-execution - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 - name: Convert results run: | cp .build/Coverage/compile_commands.json compile_commands.json - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 - uses: sonarsource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - run: du -sh / - - run: du -sh . + - run: du -sh / | exit 0 + - run: du -sh . | exit 0 codeql: name: CodeQL From 5f59bca0ad74ebdabaa07d1e5ea492455766a717 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 10:39:39 +0000 Subject: [PATCH 074/196] refactor: update static analysis workflow to use logical OR for exit codes --- .github/workflows/static-analysis.yml | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index d5005d16..86f4001c 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -22,24 +22,24 @@ jobs: env: SONAR_SERVER_URL: "https://sonarcloud.io" steps: - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 # Disable shallow clone to enable blame information persist-credentials: false - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2.20 with: key: ${{ github.job }} max-size: 2G - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 - name: Build for coverage uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8 @@ -51,22 +51,22 @@ jobs: env: GTEST_OUTPUT: "xml:${{ github.workspace }}/testresults/" - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 - name: Run acceptance tests run: | bats --formatter junit cucumber_cpp/acceptance_test/test.bats | tee test-report.xml - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 - name: Collect coverage run: | gcovr --sonarqube=coverage.xml --exclude-lines-by-pattern '.*assert\(.*\);|.*really_assert\(.*\);|.*std::abort();' --exclude-unreachable-branches --exclude-throw-branches -j "$(nproc)" --exclude=.*/example/.* --exclude=.*/external/.* --exclude=.*/test/.* - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 - uses: philips-software/sonarqube-issue-conversion@9e9958764ba5fd1d302b039779dc902bedfa4d01 # v1.2.0 with: @@ -74,22 +74,22 @@ jobs: output: execution.xml transformation: gtest-to-generic-execution - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 - name: Convert results run: | cp .build/Coverage/compile_commands.json compile_commands.json - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 - uses: sonarsource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - run: du -sh / | exit 0 - - run: du -sh . | exit 0 + - run: du -sh / || exit 0 + - run: du -sh . || exit 0 codeql: name: CodeQL From 04b8aa43503f4fe0fdd747584b8b5b053ad7a511 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 10:40:47 +0000 Subject: [PATCH 075/196] refactor: update static analysis workflow to use 'true' for exit codes instead of 'exit 0' --- .github/workflows/static-analysis.yml | 36 +++++++++++++-------------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 86f4001c..9e36615a 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -22,24 +22,24 @@ jobs: env: SONAR_SERVER_URL: "https://sonarcloud.io" steps: - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 # Disable shallow clone to enable blame information persist-credentials: false - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2.20 with: key: ${{ github.job }} max-size: 2G - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true - name: Build for coverage uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8 @@ -51,22 +51,22 @@ jobs: env: GTEST_OUTPUT: "xml:${{ github.workspace }}/testresults/" - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true - name: Run acceptance tests run: | bats --formatter junit cucumber_cpp/acceptance_test/test.bats | tee test-report.xml - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true - name: Collect coverage run: | gcovr --sonarqube=coverage.xml --exclude-lines-by-pattern '.*assert\(.*\);|.*really_assert\(.*\);|.*std::abort();' --exclude-unreachable-branches --exclude-throw-branches -j "$(nproc)" --exclude=.*/example/.* --exclude=.*/external/.* --exclude=.*/test/.* - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true - uses: philips-software/sonarqube-issue-conversion@9e9958764ba5fd1d302b039779dc902bedfa4d01 # v1.2.0 with: @@ -74,22 +74,22 @@ jobs: output: execution.xml transformation: gtest-to-generic-execution - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true - name: Convert results run: | cp .build/Coverage/compile_commands.json compile_commands.json - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true - uses: sonarsource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - run: du -sh / || exit 0 - - run: du -sh . || exit 0 + - run: du -sh / || true + - run: du -sh . || true codeql: name: CodeQL From c23eabf9a87f1f55ac70e654b7ae4e3c5453d8cb Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 10:59:00 +0000 Subject: [PATCH 076/196] refactor: update test commands to use 'pretty' format for better output readability --- cucumber_cpp/acceptance_test/test.bats | 60 +++++++++++++------------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index 7bbe2555..ac1f71ae 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -15,70 +15,70 @@ teardown() { } @test "Successful test" { - run $acceptance_test --format summary --tags "@result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Parse tag expression" { - run $acceptance_test --format summary --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Failed tests" { - run $acceptance_test --format summary --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure } @test "Undefined tests" { - run $acceptance_test --format summary --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "UNDEFINED Given a missing step" assert_output --partial "SKIPPED Then this should be skipped" } @test "No tests" { - run $acceptance_test --format summary --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features assert_success } @test "All features in a folder" { - run $acceptance_test --format summary cucumber_cpp/acceptance_test/features/subfolder + run $acceptance_test --format summary pretty cucumber_cpp/acceptance_test/features/subfolder assert_success assert_output --partial "2 scenarios" assert_output --partial "2 passed" } @test "Missing mandatory feature argument" { - run $acceptance_test --format summary + run $acceptance_test --format summary pretty assert_failure assert_output --partial "paths is required" } # @test "Missing mandatory report argument" { -# run $acceptance_test --format summary cucumber_cpp/acceptance_test/features +# run $acceptance_test --format summary pretty cucumber_cpp/acceptance_test/features # assert_failure # assert_output --partial "--report is required" # } @test "Missing mandatory custom argument" { - run $acceptance_test.custom --format summary cucumber_cpp/acceptance_test/features + run $acceptance_test.custom --format summary pretty cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "--required is required" } @test "Second feature file does not overwrite success with an undefined status" { - run $acceptance_test --format summary --tags "@undefinedsuccess and @result:success" -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature + run $acceptance_test --format summary pretty --tags "@undefinedsuccess and @result:success" -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature assert_success } # @test "Valid reporters only" { -# run $acceptance_test --format summary cucumber_cpp/acceptance_test/features +# run $acceptance_test --format summary pretty cucumber_cpp/acceptance_test/features # assert_failure # assert_output --partial "--report: 'doesnotexist' is not a reporter" # } @test "Run Program hooks" { - run $acceptance_test --format summary --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_ALL" @@ -86,7 +86,7 @@ teardown() { } @test "Run Scenario hooks" { - run $acceptance_test --format summary --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -97,7 +97,7 @@ teardown() { } @test "Run Step hooks" { - run $acceptance_test --format summary --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "HOOK_BEFORE_SCENARIO" @@ -108,7 +108,7 @@ teardown() { } @test "Run Scenario and Step hooks" { - run $acceptance_test --format summary --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -119,70 +119,70 @@ teardown() { } @test "Dry run with known failing steps" { - run $acceptance_test --format summary --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test --format summary --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features assert_success } @test "Dry run with known missing steps" { - run $acceptance_test --format summary --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test --format summary --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features assert_success assert_output --partial "UNDEFINED Given a missing step" } @test "Test the and keyword" { - run $acceptance_test --format summary --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--and--" } @test "Test the but keyword" { - run $acceptance_test --format summary --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--but--" } @test "Test the asterisk keyword" { - run $acceptance_test --format summary --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features assert_output --partial "print: --when--" assert_output --partial "print: --asterisk--" assert_success } @test "Test passing scenario after failed scenario reports feature as failed" { - run $acceptance_test --format summary --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "2 scenarios" assert_output --partial "1 passed" } @test "Test failing hook before results in error" { - run $acceptance_test --format summary --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED Before" } @test "Test failing hook after results in error" { - run $acceptance_test --format summary --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED After" } @test "Test throwing hook results in error" { - run $acceptance_test --format summary --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED Before" } @test "Test error program hook results in error and skipped steps" { - run $acceptance_test.custom --format summary --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features + run $acceptance_test.custom --format summary pretty --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "HOOK_BEFORE_ALL" assert_output --partial "HOOK_AFTER_ALL" @@ -191,21 +191,21 @@ teardown() { } @test "Test unicode" { - run $acceptance_test --format summary --tags "@unicode" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@unicode" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "1 scenario" assert_output --partial "1 passed" } # @test "Test unused step reporting" { -# run $acceptance_test --format summary --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features +# run $acceptance_test --format summary pretty --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features # assert_success # assert_output --regexp ".*The following steps have not been used:.*this step is not being used.*" # refute_output --regexp ".*The following steps have not been used:.*this step is being used.*" # } @test "Test unused steps by default not reported" { - run $acceptance_test --format summary --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "The following steps have not been used:" } From 51fd45e8730166adbdc5b780328607db3b400580 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 11:26:23 +0000 Subject: [PATCH 077/196] refactor: add coverage clean step and duplicate directory size checks to static analysis workflow --- .github/workflows/static-analysis.yml | 5 +++++ .gitignore | 3 +++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 9e36615a..19526bf4 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -84,6 +84,11 @@ jobs: - run: du -sh / || true - run: du -sh . || true + - run: cmake --build --preset Coverage --target clean + + - run: du -sh / || true + - run: du -sh . || true + - uses: sonarsource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} diff --git a/.gitignore b/.gitignore index 2da3bbc9..398c432a 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ megalinter-reports/ # compatibility generated files actual.ndjson expected.ndjson + +# bats task generated files +test-report.xml From b9adf82855f79ce37f5879d708598d28de8bdfaf Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 11:48:31 +0000 Subject: [PATCH 078/196] refactor: update test cases to correctly handle mandatory arguments and format options --- cucumber_cpp/acceptance_test/test.bats | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index ac1f71ae..239ea416 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -42,26 +42,20 @@ teardown() { } @test "All features in a folder" { - run $acceptance_test --format summary pretty cucumber_cpp/acceptance_test/features/subfolder + run $acceptance_test --format summary pretty -- cucumber_cpp/acceptance_test/features/subfolder assert_success assert_output --partial "2 scenarios" assert_output --partial "2 passed" } -@test "Missing mandatory feature argument" { - run $acceptance_test --format summary pretty +@test "Missing mandatory paths argument" { + run $acceptance_test --format summary pretty -- assert_failure assert_output --partial "paths is required" } -# @test "Missing mandatory report argument" { -# run $acceptance_test --format summary pretty cucumber_cpp/acceptance_test/features -# assert_failure -# assert_output --partial "--report is required" -# } - @test "Missing mandatory custom argument" { - run $acceptance_test.custom --format summary pretty cucumber_cpp/acceptance_test/features + run $acceptance_test.custom --format summary pretty -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "--required is required" } @@ -71,11 +65,11 @@ teardown() { assert_success } -# @test "Valid reporters only" { -# run $acceptance_test --format summary pretty cucumber_cpp/acceptance_test/features -# assert_failure -# assert_output --partial "--report: 'doesnotexist' is not a reporter" -# } +@test "Valid reporters only" { + run $acceptance_test --format doesnotexist -- cucumber_cpp/acceptance_test/features + assert_failure + assert_output --partial "--format: 'doesnotexist' is not a valid formatter" +} @test "Run Program hooks" { run $acceptance_test --format summary pretty --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features From 794c5414d5db45fd4601e587dae79c0a88cf0997 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 11:48:47 +0000 Subject: [PATCH 079/196] refactor: remove tee command from bats task to simplify output handling --- .vscode/tasks.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 9008a9df..afed183e 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -6,7 +6,7 @@ { "label": "bats", "type": "shell", - "command": "bats --formatter junit cucumber_cpp/acceptance_test/test.bats | tee test-report.xml", + "command": "bats --formatter junit cucumber_cpp/acceptance_test/test.bats", "problemMatcher": [] } ] From 8c18af3ae7b91ae74f25de0df50b75d97c2d1337 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 11:48:50 +0000 Subject: [PATCH 080/196] refactor: remove unnecessary trailing commas in launch.json arguments --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 5a03502a..08cab272 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -51,7 +51,7 @@ "--tag", "${input:tag}", "--", - "${input:features}", + "${input:features}" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", @@ -84,7 +84,7 @@ "--format", "summary", "--", - "${input:features}", + "${input:features}" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", From 58799496fbbee4a48e04074535632b70ed8fea57 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 13:11:53 +0000 Subject: [PATCH 081/196] refactor: replace strong ordering operators with SourceLocationOrder for improved clarity --- .../library/support/SupportCodeLibrary.cpp | 32 ++----------------- .../library/support/SupportCodeLibrary.hpp | 10 +++--- 2 files changed, 7 insertions(+), 35 deletions(-) diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index d7e49bd2..875f4878 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -3,7 +3,6 @@ #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" -#include #include #include #include @@ -13,38 +12,11 @@ #include #include -namespace std -{ - std::strong_ordering operator<=>(const std::source_location& left, const std::source_location& right) - { - return std::forward_as_tuple(left.file_name(), left.line()) <=> std::forward_as_tuple(right.file_name(), right.line()); - } -} - namespace cucumber_cpp::library::support { - - std::strong_ordering operator<=>(const HookEntry& left, const HookEntry& right) - { - return left.sourceLocation <=> right.sourceLocation; - } - - std::strong_ordering operator<=>(const StepStringRegistration::Entry& left, const StepStringRegistration::Entry& right) - { - return left.sourceLocation <=> right.sourceLocation; - } - - std::strong_ordering operator<=>(const Entry& left, const Entry& right) + bool SourceLocationOrder::operator()(const std::source_location& lhs, const std::source_location& rhs) const { - constexpr static auto sourceLocationVisitor = [](const auto& entry) - { - return entry.sourceLocation; - }; - - const auto& leftSource = std::visit(sourceLocationVisitor, left); - const auto& rightSource = std::visit(sourceLocationVisitor, right); - - return leftSource <=> rightSource; + return std::forward_as_tuple(lhs.file_name(), lhs.line()) < std::forward_as_tuple(rhs.file_name(), rhs.line()); } DefinitionRegistration& DefinitionRegistration::Instance() diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp index 11be8f08..8429cd3d 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.hpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -64,9 +64,6 @@ namespace cucumber_cpp::library::support std::string id{ "unassigned" }; }; - std::strong_ordering operator<=>(const HookEntry& left, const HookEntry& right); - std::strong_ordering operator<=>(const StepStringRegistration::Entry& left, const StepStringRegistration::Entry& right); - struct SupportCodeLibrary { HookRegistry& hookRegistry; @@ -77,7 +74,10 @@ namespace cucumber_cpp::library::support using Entry = std::variant; - std::strong_ordering operator<=>(const Entry& left, const Entry& right); + struct SourceLocationOrder + { + bool operator()(const std::source_location& lhs, const std::source_location& rhs) const; + }; struct DefinitionRegistration { @@ -106,7 +106,7 @@ namespace cucumber_cpp::library::support std::size_t Register(GlobalHook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation); std::size_t Register(std::string_view matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); - std::map> registry; + std::map registry; }; ////////////////////////// From 5fd1def27c2156bdf8d51e0059e7aba3551eb5e2 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 13:12:05 +0000 Subject: [PATCH 082/196] refactor: simplify character class and group handling in regex parsing --- .../cucumber_expression/TreeRegexp.cpp | 37 +++++++++++++++---- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp index 5b3a11f1..471c209f 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp @@ -113,22 +113,47 @@ namespace cucumber_cpp::library::cucumber_expression bool escaping{ false }; bool charClass{ false }; + const auto isCharClassOpening = [&escaping](char c) + { + return c == '[' && !escaping; + }; + + const auto isCharClassClosing = [&escaping](char c) + { + return c == ']' && !escaping; + }; + + const auto isGroupOpening = [&escaping, &charClass](char c) + { + return c == '(' && !escaping && !charClass; + }; + + const auto isGroupClosing = [&escaping, &charClass](char c) + { + return c == ')' && !escaping && !charClass; + }; + + const auto isEscaping = [&escaping](char c) + { + return c == '\\' && !escaping; + }; + for (std::size_t i = 0; i < pattern.size(); ++i) { const char c = pattern[i]; - if (c == '[' && !escaping) + if (isCharClassOpening(c)) charClass = true; - else if (c == ']' && !escaping) + else if (isCharClassClosing(c)) charClass = false; - else if (c == '(' && !escaping && !charClass) + else if (isGroupOpening(c)) { groupStartStack.emplace_back(i); auto& groupBuilder = stack.emplace_back(); if (IsNonCapturing(pattern, i)) groupBuilder.SetNonCapturing(); } - else if (c == ')' && !escaping && !charClass) + else if (isGroupClosing(c)) { if (stack.empty()) throw std::runtime_error("Empty stack"); @@ -148,12 +173,10 @@ namespace cucumber_cpp::library::cucumber_expression stack.back().Add(groupBuilder); } else - { groupBuilder.MoveChildrenTo(stack.back()); - } } - escaping = c == '\\' && !escaping; + escaping = isEscaping(c); } if (stack.empty()) From 0e1a68b7b061a600578d40034f0fc8b6a7a2f388 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 13:14:34 +0000 Subject: [PATCH 083/196] refactor: use EXIT_SUCCESS and EXIT_FAILURE for exit codes in GetExitCode method --- cucumber_cpp/library/Application.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 8ac6253a..4c54e282 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -238,6 +239,6 @@ namespace cucumber_cpp::library int Application::GetExitCode() const { - return runPassed ? 0 : 1; + return runPassed ? EXIT_SUCCESS : EXIT_FAILURE; } } From 3819881c2ecc4b23ef0c4013040df5d390e3569d Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 14:28:23 +0000 Subject: [PATCH 084/196] refactor: move operator+ overloads for Lineage into the class definition and update operator+= signatures for consistency --- cucumber_cpp/library/Query.cpp | 48 ++--------------------------- cucumber_cpp/library/Query.hpp | 56 +++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 56 deletions(-) diff --git a/cucumber_cpp/library/Query.cpp b/cucumber_cpp/library/Query.cpp index 0450ba9e..2f1741ea 100644 --- a/cucumber_cpp/library/Query.cpp +++ b/cucumber_cpp/library/Query.cpp @@ -40,48 +40,6 @@ namespace cucumber_cpp::library { - Lineage operator+(Lineage lineage, std::shared_ptr gherkinDocument) - { - lineage.gherkinDocument = gherkinDocument; - return std::move(lineage); - } - - Lineage operator+(Lineage lineage, std::shared_ptr feature) - { - lineage.feature = feature; - return std::move(lineage); - } - - Lineage operator+(Lineage lineage, std::shared_ptr rule) - { - lineage.rule = rule; - return std::move(lineage); - } - - Lineage operator+(Lineage lineage, std::shared_ptr scenario) - { - lineage.scenario = scenario; - return std::move(lineage); - } - - Lineage operator+(Lineage lineage, std::shared_ptr examples) - { - lineage.examples = examples; - return std::move(lineage); - } - - Lineage operator+(Lineage lineage, std::shared_ptr tableRow) - { - lineage.tableRow = tableRow; - return std::move(lineage); - } - - Lineage operator+(Lineage lineage, std::uint32_t featureIndex) - { - lineage.featureIndex = featureIndex; - return std::move(lineage); - } - std::string Lineage::GetUniqueFeatureName() const { return std::format("{}/{}", feature->name, featureIndex); @@ -365,7 +323,7 @@ namespace cucumber_cpp::library undefinedParameterTypes.emplace_front(undefinedParameterType); } - void Query::operator+=(std::pair feature) + void Query::operator+=(const std::pair& feature) { auto featurePtr = std::make_shared(feature.first); @@ -385,7 +343,7 @@ namespace cucumber_cpp::library } } - void Query::operator+=(std::pair scenario) + void Query::operator+=(const std::pair& scenario) { auto scenarioPtr = std::make_shared(scenario.first); lineageByUri[*scenario.second.gherkinDocument->uri] = scenario.second; @@ -414,7 +372,7 @@ namespace cucumber_cpp::library *this += scenarioPtr->steps; } - void Query::operator+=(std::pair rule) + void Query::operator+=(const std::pair& rule) { auto rulePtr = std::make_shared(rule.first); diff --git a/cucumber_cpp/library/Query.hpp b/cucumber_cpp/library/Query.hpp index 414f8c8a..100f1a19 100644 --- a/cucumber_cpp/library/Query.hpp +++ b/cucumber_cpp/library/Query.hpp @@ -59,15 +59,49 @@ namespace cucumber_cpp::library std::shared_ptr tableRow; std::uint32_t featureIndex{ 0 }; - }; - Lineage operator+(Lineage lineage, std::shared_ptr gherkinDocument); - Lineage operator+(Lineage lineage, std::shared_ptr feature); - Lineage operator+(Lineage lineage, std::shared_ptr rule); - Lineage operator+(Lineage lineage, std::shared_ptr scenario); - Lineage operator+(Lineage lineage, std::shared_ptr examples); - Lineage operator+(Lineage lineage, std::shared_ptr tableRow); - Lineage operator+(Lineage lineage, std::uint32_t featureIndex); + friend Lineage operator+(Lineage lineage, std::shared_ptr gherkinDocument) + { + lineage.gherkinDocument = gherkinDocument; + return std::move(lineage); + } + + friend Lineage operator+(Lineage lineage, std::shared_ptr feature) + { + lineage.feature = feature; + return std::move(lineage); + } + + friend Lineage operator+(Lineage lineage, std::shared_ptr rule) + { + lineage.rule = rule; + return std::move(lineage); + } + + friend Lineage operator+(Lineage lineage, std::shared_ptr scenario) + { + lineage.scenario = scenario; + return std::move(lineage); + } + + friend Lineage operator+(Lineage lineage, std::shared_ptr examples) + { + lineage.examples = examples; + return std::move(lineage); + } + + friend Lineage operator+(Lineage lineage, std::shared_ptr tableRow) + { + lineage.tableRow = tableRow; + return std::move(lineage); + } + + friend Lineage operator+(Lineage lineage, std::uint32_t featureIndex) + { + lineage.featureIndex = featureIndex; + return std::move(lineage); + } + }; struct Query : util::Broadcaster @@ -128,9 +162,9 @@ namespace cucumber_cpp::library void operator+=(const cucumber::messages::suggestion& suggestion); void operator+=(const cucumber::messages::undefined_parameter_type& undefinedParameterType); - void operator+=(std::pair feature); - void operator+=(std::pair scenario); - void operator+=(std::pair rule); + void operator+=(const std::pair& feature); + void operator+=(const std::pair& scenario); + void operator+=(const std::pair& rule); void operator+=(std::span steps); void operator+=(const cucumber::messages::parameter_type& parameterType); From 86b0bad92999cbffc574cb0576d423125d476d4a Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 14:28:31 +0000 Subject: [PATCH 085/196] refactor: update SonarQube issue ignore rules to include additional criteria --- sonar-project.properties | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/sonar-project.properties b/sonar-project.properties index b5f7fdd1..b0e8c28f 100644 --- a/sonar-project.properties +++ b/sonar-project.properties @@ -31,7 +31,13 @@ sonar.cpd.exclusions=${env.SONAR_DUPLICATION_EXCLUSIONS} # Project specific ignored rules # NOTE: Please update scan details in SCA document # when making updates here! -sonar.issue.ignore.multicriteria=e1 +sonar.issue.ignore.multicriteria=e1,e2 + +# Variables should not be shadowed [cpp:S1117] +# +# We allow shadowing of variables in case they need to be stored for later use. +sonar.issue.ignore.multicriteria.e1.ruleKey=cpp:S1117 +sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.?pp # The "Rule-of-Zero" should be followed [cpp:S4963] # @@ -40,5 +46,5 @@ sonar.issue.ignore.multicriteria=e1 # should be relaxed and rule of five should be followed. # S3624 does exactly this: When the "Rule-of-Zero" is not applicable, the "Rule-of-Five" should be followed # See https://community.sonarsource.com/t/how-to-fix-a-the-rule-of-zero-should-be-followed/20656/6 -sonar.issue.ignore.multicriteria.e1.ruleKey=cpp:S4963 -sonar.issue.ignore.multicriteria.e1.resourceKey=**/*.?pp +sonar.issue.ignore.multicriteria.e2.ruleKey=cpp:S4963 +sonar.issue.ignore.multicriteria.e2.resourceKey=**/*.?pp From f1e4d6501aeb675c5d046fac3a43a9f59a1f8fcb Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 15:05:56 +0000 Subject: [PATCH 086/196] refactor: remove unnecessary blank line at the end of Parameter.cpp --- cucumber_cpp/library/Parameter.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cucumber_cpp/library/Parameter.cpp b/cucumber_cpp/library/Parameter.cpp index 037bb98d..fd704942 100644 --- a/cucumber_cpp/library/Parameter.cpp +++ b/cucumber_cpp/library/Parameter.cpp @@ -20,5 +20,4 @@ namespace cucumber_cpp::library { return customParameters; } - } From 60af5a5aa62d7ada5c9cd78a73241349e32c7973 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 15:06:08 +0000 Subject: [PATCH 087/196] refactor: use default comparator for orderingMap in Application.cpp --- cucumber_cpp/library/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 4c54e282..973c92f5 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -124,7 +124,7 @@ namespace cucumber_cpp::library try { - const std::map orderingMap{ + const std::map> orderingMap{ { "defined", support::RunOptions::Ordering::defined }, { "reverse", support::RunOptions::Ordering::reverse }, }; From 4b546b1a649ae97f41188611a3384d690953295b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 15:06:12 +0000 Subject: [PATCH 088/196] refactor: update launch arguments to use --tags instead of --tag --- .vscode/launch.json | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 08cab272..46ed6118 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -42,13 +42,12 @@ "request": "launch", "program": "${command:cmake.launchTargetPath}", "args": [ - "run", "--com", "COMx", "--nordic", "--format", "summary", - "--tag", + "--tags", "${input:tag}", "--", "${input:features}" @@ -77,7 +76,6 @@ "request": "launch", "program": "${command:cmake.launchTargetPath}", "args": [ - "run", "--com", "COMx", "--nordic", From 6e50738ced9468265029ab091a202c317ba77def Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 6 Jan 2026 15:06:31 +0000 Subject: [PATCH 089/196] refactor: update formatter registration to use nlohmann::json for format options --- cucumber_cpp/library/api/CMakeLists.txt | 2 -- cucumber_cpp/library/api/Formatters.cpp | 9 +++++---- cucumber_cpp/library/api/Formatters.hpp | 13 +++++++------ cucumber_cpp/library/api/RunCucumber.cpp | 4 +++- cucumber_cpp/library/formatter/CMakeLists.txt | 3 +-- cucumber_cpp/library/formatter/Formatter.cpp | 4 +++- cucumber_cpp/library/formatter/Formatter.hpp | 4 +++- cucumber_cpp/library/formatter/PrettyPrinter.hpp | 2 ++ cucumber_cpp/library/formatter/SummaryFormatter.hpp | 2 ++ 9 files changed, 26 insertions(+), 17 deletions(-) diff --git a/cucumber_cpp/library/api/CMakeLists.txt b/cucumber_cpp/library/api/CMakeLists.txt index 7dc4fca3..4464aa2d 100644 --- a/cucumber_cpp/library/api/CMakeLists.txt +++ b/cucumber_cpp/library/api/CMakeLists.txt @@ -14,8 +14,6 @@ target_include_directories(cucumber_cpp.library.api PUBLIC ) target_link_libraries(cucumber_cpp.library.api PUBLIC - # cucumber_cpp.library - # cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression ) diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index 9674f457..9e39a9a1 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -5,6 +5,7 @@ #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "nlohmann/json_fwd.hpp" #include #include #include @@ -18,8 +19,8 @@ namespace cucumber_cpp::library::api { Formatters::Formatters() { - RegisterFormatter("pretty"); - RegisterFormatter("summary"); + RegisterFormatter(); + RegisterFormatter(); } std::set> Formatters::GetAvailableFormatterNames() const @@ -31,12 +32,12 @@ namespace cucumber_cpp::library::api return { view.begin(), view.end() }; } - std::list> Formatters::EnableFormatters(const std::set>& format, const std::string& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) + std::list> Formatters::EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) { std::list> activeFormatters; for (const auto& formatterName : format) - activeFormatters.emplace_back(availableFormatters.at(formatterName).factory(supportCodeLibrary, query, eventDataCollector, output)); + activeFormatters.emplace_back(availableFormatters.at(formatterName).factory(supportCodeLibrary, query, eventDataCollector, formatOptions, output)); return activeFormatters; } diff --git a/cucumber_cpp/library/api/Formatters.hpp b/cucumber_cpp/library/api/Formatters.hpp index 2665908b..67b33bb1 100644 --- a/cucumber_cpp/library/api/Formatters.hpp +++ b/cucumber_cpp/library/api/Formatters.hpp @@ -5,6 +5,7 @@ #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "nlohmann/json_fwd.hpp" #include #include #include @@ -19,7 +20,7 @@ namespace cucumber_cpp::library::api { struct RegisteredFormatter { - std::function(support::SupportCodeLibrary&, Query&, const formatter::helper::EventDataCollector&, std::ostream&)> factory; + std::function(support::SupportCodeLibrary&, Query&, const formatter::helper::EventDataCollector&, const nlohmann::json& formatOptions, std::ostream&)> factory; bool hasOutput{ false }; }; @@ -28,11 +29,11 @@ namespace cucumber_cpp::library::api Formatters(); template - void RegisterFormatter(const std::string& name, bool hasOutput = false); + void RegisterFormatter(bool hasOutput = false); std::set> GetAvailableFormatterNames() const; - [[nodiscard]] std::list> EnableFormatters(const std::set>& format, const std::string& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); + [[nodiscard]] std::list> EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); private: std::map availableFormatters; @@ -43,11 +44,11 @@ namespace cucumber_cpp::library::api //////////////////// template - void Formatters::RegisterFormatter(const std::string& name, bool hasOutput) + void Formatters::RegisterFormatter(bool hasOutput) { - availableFormatters.try_emplace(name, [](support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) + availableFormatters.try_emplace(T::name, [](support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& output) { - return std::make_unique(supportCodeLibrary, query, eventDataCollector, output); + return std::make_unique(supportCodeLibrary, query, eventDataCollector, formatOptions, output); }, hasOutput); } diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 43cf4c51..570d3a53 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -20,6 +20,7 @@ #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "nlohmann/json_fwd.hpp" #include #include #include @@ -143,7 +144,8 @@ namespace cucumber_cpp::library::api formatter::helper::EventDataCollector eventDataCollector{ broadcaster }; Query query{ broadcaster }; - const auto activeFormatters = formatters.EnableFormatters(format, formatOptions, supportCodeLibrary, query, eventDataCollector); + const auto formatOptionsJson = formatOptions.empty() ? nlohmann::json::object() : nlohmann::json::parse(formatOptions); + const auto activeFormatters = formatters.EnableFormatters(format, formatOptionsJson, supportCodeLibrary, query, eventDataCollector); const auto pickleSources = CollectPickles(options.sources, idGenerator, broadcaster); diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index 644fa4a5..a426e7e8 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -16,11 +16,10 @@ target_include_directories(cucumber_cpp.library.formatter PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter PUBLIC - # cucumber_cpp.library - # cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression cucumber_cpp.library.formatter.helper cpp-terminal + nlohmann_json ) add_subdirectory(helper) diff --git a/cucumber_cpp/library/formatter/Formatter.cpp b/cucumber_cpp/library/formatter/Formatter.cpp index 52a4aaaf..8dfdff72 100644 --- a/cucumber_cpp/library/formatter/Formatter.cpp +++ b/cucumber_cpp/library/formatter/Formatter.cpp @@ -4,12 +4,13 @@ #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "nlohmann/json_fwd.hpp" #include #include namespace cucumber_cpp::library::formatter { - Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, Query& query, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream) + Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, Query& query, const helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& outputStream) : util::Listener{ query, [this](const cucumber::messages::envelope& envelope) { OnEnvelope(envelope); @@ -17,6 +18,7 @@ namespace cucumber_cpp::library::formatter , supportCodeLibrary{ supportCodeLibrary } , query{ query } , eventDataCollector{ eventDataCollector } + , formatOptions{ formatOptions } , outputStream{ outputStream } { } diff --git a/cucumber_cpp/library/formatter/Formatter.hpp b/cucumber_cpp/library/formatter/Formatter.hpp index 52bdba1e..2ca0232c 100644 --- a/cucumber_cpp/library/formatter/Formatter.hpp +++ b/cucumber_cpp/library/formatter/Formatter.hpp @@ -6,6 +6,7 @@ #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "nlohmann/json_fwd.hpp" #include #include #include @@ -15,7 +16,7 @@ namespace cucumber_cpp::library::formatter struct Formatter : util::Listener { - Formatter(support::SupportCodeLibrary& supportCodeLibrary, Query& query, const helper::EventDataCollector& eventDataCollector, std::ostream& outputStream = std::cout); + Formatter(support::SupportCodeLibrary& supportCodeLibrary, Query& query, const helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& outputStream = std::cout); virtual ~Formatter() = default; protected: @@ -24,6 +25,7 @@ namespace cucumber_cpp::library::formatter support::SupportCodeLibrary& supportCodeLibrary; Query& query; const helper::EventDataCollector& eventDataCollector; + const nlohmann::json& formatOptions; std::ostream& outputStream; }; } diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.hpp b/cucumber_cpp/library/formatter/PrettyPrinter.hpp index 7dc4e91f..fe83e083 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.hpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.hpp @@ -32,6 +32,8 @@ namespace cucumber_cpp::library::formatter { using Formatter::Formatter; + constexpr static auto name = "pretty"; + private: void OnEnvelope(const cucumber::messages::envelope& envelope) override; diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.hpp b/cucumber_cpp/library/formatter/SummaryFormatter.hpp index fb85e878..049147c7 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.hpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.hpp @@ -16,6 +16,8 @@ namespace cucumber_cpp::library::formatter { using Formatter::Formatter; + constexpr static auto name = "summary"; + private: void OnEnvelope(const cucumber::messages::envelope& envelope) override; void LogSummary(const cucumber::messages::duration& testRunDuration); From c82fa495886fb7236ef8bcab6067fa3b4fb2e3f8 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 7 Jan 2026 09:39:11 +0000 Subject: [PATCH 090/196] refactor: introduce FormatterOption struct for improved formatter handling --- cucumber_cpp/library/Application.cpp | 8 ++++---- cucumber_cpp/library/api/Formatters.cpp | 26 ++++++++++++++++++++++++- cucumber_cpp/library/api/Formatters.hpp | 12 +++++++++++- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 973c92f5..dea777f3 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -112,11 +112,11 @@ namespace cucumber_cpp::library CLI::Validator formatValidator{ [&formattersSet](const std::string& str) -> std::string { - const auto colon = str.find(':'); - const auto formatter = str.substr(0, colon); - const auto iter = std::ranges::find(formattersSet, formatter, &std::pair::first); + const api::FormatterOption option{ str }; + const auto iter = std::ranges::find(formattersSet, option.name, &std::pair::first); + if (iter == formattersSet.end()) - return std::format("'{}' is not a valid formatter", formatter); + return std::format("'{}' is not a valid formatter", option.name); else return ""; }, diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index 9e39a9a1..951dbfa2 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -6,17 +6,28 @@ #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "nlohmann/json_fwd.hpp" +#include +#include #include +#include #include #include #include #include #include #include +#include #include namespace cucumber_cpp::library::api { + FormatterOption::FormatterOption(std::string_view str) + { + const auto colon = str.find(':'); + name = str.substr(0, colon); + output = colon == std::string::npos ? "" : str.substr(colon + 1); + } + Formatters::Formatters() { RegisterFormatter(); @@ -37,7 +48,20 @@ namespace cucumber_cpp::library::api std::list> activeFormatters; for (const auto& formatterName : format) - activeFormatters.emplace_back(availableFormatters.at(formatterName).factory(supportCodeLibrary, query, eventDataCollector, formatOptions, output)); + { + const FormatterOption option{ formatterName }; + + if (option.output.empty()) + activeFormatters.emplace_back(availableFormatters.at(option.name).factory(supportCodeLibrary, query, eventDataCollector, formatOptions, output)); + else + { + const auto absolutePath = std::filesystem::absolute(std::filesystem::path{ option.output }).string(); + if (!customOutputFiles.contains(absolutePath)) + customOutputFiles.emplace(absolutePath, std::make_unique(absolutePath)); + + activeFormatters.emplace_back(availableFormatters.at(option.name).factory(supportCodeLibrary, query, eventDataCollector, formatOptions, *customOutputFiles.at(absolutePath))); + } + } return activeFormatters; } diff --git a/cucumber_cpp/library/api/Formatters.hpp b/cucumber_cpp/library/api/Formatters.hpp index 67b33bb1..e1ac7f17 100644 --- a/cucumber_cpp/library/api/Formatters.hpp +++ b/cucumber_cpp/library/api/Formatters.hpp @@ -14,10 +14,19 @@ #include #include #include +#include #include namespace cucumber_cpp::library::api { + struct FormatterOption + { + FormatterOption(std::string_view str); + + std::string name; + std::string output; + }; + struct RegisteredFormatter { std::function(support::SupportCodeLibrary&, Query&, const formatter::helper::EventDataCollector&, const nlohmann::json& formatOptions, std::ostream&)> factory; @@ -36,7 +45,8 @@ namespace cucumber_cpp::library::api [[nodiscard]] std::list> EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); private: - std::map availableFormatters; + std::map> availableFormatters; + std::map, std::less<>> customOutputFiles; }; //////////////////// From b2155bf30fc124b2e353576d1618dff61b4b5260 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 7 Jan 2026 14:01:36 +0000 Subject: [PATCH 091/196] refactor: enhance compatibility handling and streamline step registration --- compatibility/CMakeLists.txt | 7 +++++ compatibility/attachments/attachments.cpp | 6 ++-- compatibility/compatibility.cpp | 25 +++++++++++------ cucumber_cpp/library/Application.cpp | 19 ------------- cucumber_cpp/library/Application.hpp | 1 - cucumber_cpp/library/StepRegistry.cpp | 18 +++--------- cucumber_cpp/library/StepRegistry.hpp | 2 -- .../library/support/SupportCodeLibrary.cpp | 28 ++++++++++--------- .../library/support/SupportCodeLibrary.hpp | 25 +++++++++++++++-- 9 files changed, 69 insertions(+), 62 deletions(-) diff --git a/compatibility/CMakeLists.txt b/compatibility/CMakeLists.txt index 11237dbd..6810a891 100644 --- a/compatibility/CMakeLists.txt +++ b/compatibility/CMakeLists.txt @@ -37,6 +37,13 @@ function(add_compatibility_kit name) KIT_NDJSON_FILE="${CMAKE_CURRENT_SOURCE_DIR}/${name}/${name}.ndjson" $<$:SKIP_TEST> ) + + add_custom_command( + TARGET ${libname} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + ${CMAKE_SOURCE_DIR}/compatibility/${name} + $/compatibility/${name} + ) endfunction() file(GLOB kits RELATIVE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/*) diff --git a/compatibility/attachments/attachments.cpp b/compatibility/attachments/attachments.cpp index 8b607c5e..a1ae80df 100644 --- a/compatibility/attachments/attachments.cpp +++ b/compatibility/attachments/attachments.cpp @@ -1,14 +1,16 @@ #include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" -#include #include +#include +#include +#include #include #include #include namespace { - const std::filesystem::path currentCompileDir = std::filesystem::path{ std::source_location::current().file_name() }.parent_path(); + const std::filesystem::path currentCompileDir = std::filesystem::current_path() / "compatibility" / "attachments"; } WHEN(R"(the string {string} is attached as {string})", (const std::string& text, const std::string& mediaType)) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index f7eaaa2e..c939075f 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -52,11 +52,19 @@ namespace compatibility Devkit LoadDevkit() { + std::cout << "LoadDevkit:\n"; + std::cout << "KIT_FOLDER: " << KIT_FOLDER << "\n"; + std::cout << "KIT_NDJSON_FILE: " << KIT_NDJSON_FILE << "\n"; + std::cout << "cwd: " << std::filesystem::current_path() << "\n"; + + std::cout << "paths: " << std::filesystem::current_path() / "compatibility" / KIT_STRING << "\n"; + std::cout << "ndjsonfile: " << std::filesystem::current_path() / "compatibility" / KIT_STRING / (std::string{ KIT_STRING } + ".ndjson") << "\n"; + return { - .paths = { KIT_FOLDER }, + .paths = { std::filesystem::current_path() / "compatibility" / KIT_STRING }, .tagExpression = "", .retry = 0, - .ndjsonFile = KIT_NDJSON_FILE + .ndjsonFile = std::filesystem::current_path() / "compatibility" / KIT_STRING / (std::string{ KIT_STRING } + ".ndjson"), }; } @@ -97,16 +105,17 @@ namespace compatibility json[key] = std::regex_replace(json[key].get(), std::regex(R"(\r\n)"), "\n"); ++jsonIter; } - else if (key == "uri") + else if (key == "uri" && (value.get().find("samples") != std::string::npos || value.get().find("compatibility") != std::string::npos)) { - auto uri = value.get(); + jsonIter = json.erase(jsonIter); + // auto uri = value.get(); - uri = std::regex_replace(uri, std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); - uri = std::regex_replace(uri, std::regex(R"(\.ts$)"), ".cpp"); + // uri = std::regex_replace(uri, std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); + // uri = std::regex_replace(uri, std::regex(R"(\.ts$)"), ".cpp"); - json[key] = std::filesystem::canonical(uri).string(); + // json[key] = std::filesystem::canonical(uri).string(); - ++jsonIter; + // ++jsonIter; } else ++jsonIter; diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index dea777f3..f59dab62 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -218,25 +218,6 @@ namespace cucumber_cpp::library runPassed = api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, options.format, options.formatOptions); } - void Application::PrintStepsNotUsed(const StepRegistry& stepRegistry) const - { - auto isUnused = [](const StepRegistry::EntryView& entry) - { - return entry.used == 0; - }; - - auto unusedSteps = stepRegistry.List() | std::views::filter(isUnused); - - if (std::ranges::empty(unusedSteps)) - std::cout << "\nAll steps have been used."; - else - { - std::cout << "\nThe following steps have not been used:"; - for (const auto& entry : unusedSteps) - std::cout << "\n - " << std::visit(cucumber_expression::SourceVisitor{}, entry.stepRegex); - } - } - int Application::GetExitCode() const { return runPassed ? EXIT_SUCCESS : EXIT_FAILURE; diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 336fbbdf..487d9ef5 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -60,7 +60,6 @@ namespace cucumber_cpp::library private: void DryRunFeatures(); void RunFeatures(); - void PrintStepsNotUsed(const StepRegistry& stepRegistry) const; [[nodiscard]] int GetExitCode() const; diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/StepRegistry.cpp index 1a44fa6c..8d3864ff 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/StepRegistry.cpp @@ -34,8 +34,10 @@ namespace cucumber_cpp::library void StepRegistry::LoadSteps() { - for (const auto& matcher : support::DefinitionRegistration::Instance().GetSteps()) - Register(matcher.id, matcher.regex, matcher.type, matcher.factory, matcher.sourceLocation); + support::DefinitionRegistration::Instance().ForEachRegisteredStep([this](const StepStringRegistration::Entry& entry) + { + Register(entry.id, entry.regex, entry.type, entry.factory, entry.sourceLocation); + }); } [[nodiscard]] std::pair, std::vector>> StepRegistry::FindDefinitions(const std::string& expression) const @@ -62,18 +64,6 @@ namespace cucumber_cpp::library return registry.size(); } - std::vector StepRegistry::List() const - { - std::vector list; - - list.reserve(registry.size()); - - for (const auto& entry : registry) - list.emplace_back(entry.regex, entry.used); - - return list; - } - StepFactory StepRegistry::GetFactoryById(const std::string& id) const { return idToDefinitionMap.at(id)->factory; diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/StepRegistry.hpp index 47416a22..3c202e98 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/StepRegistry.hpp @@ -106,8 +106,6 @@ namespace cucumber_cpp::library [[nodiscard]] std::size_t Size() const; - [[nodiscard]] std::vector List() const; - StepFactory GetFactoryById(const std::string& id) const; Definition GetDefinitionById(const std::string& id) const; diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index 875f4878..df93c76f 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -4,6 +4,7 @@ #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" #include +#include #include #include #include @@ -36,19 +37,6 @@ namespace cucumber_cpp::library::support std::visit(assignGenerator, item); } - std::vector DefinitionRegistration::GetSteps() - { - auto allSteps = registry | std::views::values | std::views::filter([](const Entry& entry) - { - return std::holds_alternative(entry); - }) | - std::views::transform([](const Entry& entry) - { - return std::get(entry); - }); - return { allSteps.begin(), allSteps.end() }; - } - std::vector DefinitionRegistration::GetHooks() { auto allSteps = registry | std::views::values | std::views::filter([](const Entry& entry) @@ -62,21 +50,35 @@ namespace cucumber_cpp::library::support return { allSteps.begin(), allSteps.end() }; } + namespace + { + void PrintContents(std::string_view type, std::source_location sourceLocation, const std::map& registry) + { + std::cout << std::format("Added ({}): {}:{}\n", type, sourceLocation.file_name(), sourceLocation.line()); + std::cout << "Registry contents:\n"; + for (const auto& [key, item] : registry) + std::cout << std::format(" {}:{}\n", key.file_name(), key.line()); + } + } + std::size_t DefinitionRegistration::Register(Hook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation) { registry.emplace(sourceLocation, HookEntry{ hookType, hook, factory, sourceLocation }); + PrintContents("Hook", sourceLocation, registry); return registry.size(); } std::size_t DefinitionRegistration::Register(GlobalHook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation) { registry.emplace(sourceLocation, HookEntry{ hookType, hook, factory, sourceLocation }); + PrintContents("GlobalHook", sourceLocation, registry); return registry.size(); } std::size_t DefinitionRegistration::Register(std::string_view matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) { registry.emplace(sourceLocation, StepStringRegistration::Entry{ stepType, std::string{ matcher }, factory, sourceLocation }); + PrintContents("Step", sourceLocation, registry); return registry.size(); } } diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp index 8429cd3d..bd364ae1 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.hpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -7,12 +7,11 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" -#include #include #include -#include #include #include +#include #include #include #include @@ -89,7 +88,9 @@ namespace cucumber_cpp::library::support void LoadIds(cucumber::gherkin::id_generator_ptr idGenerator); - std::vector GetSteps(); + template + void ForEachRegisteredStep(const T& func); + std::vector GetHooks(); template @@ -113,6 +114,24 @@ namespace cucumber_cpp::library::support // implementation // ////////////////////////// + template + void DefinitionRegistration::ForEachRegisteredStep(const T& func) + { + auto allSteps = registry | + std::views::values | + std::views::filter([](const Entry& entry) + { + return std::holds_alternative(entry); + }) | + std::views::transform([](const Entry& entry) + { + return std::get(entry); + }); + + for (const auto& step : allSteps) + func(step); + } + template std::size_t DefinitionRegistration::Register(Hook hook, HookType hookType, std::source_location sourceLocation) { From f91df55bf0a3de4fe658b46360a938dc42ad28ee Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 7 Jan 2026 14:41:46 +0000 Subject: [PATCH 092/196] refactor: simplify LoadDevkit function and improve path handling --- compatibility/CMakeLists.txt | 7 ------- compatibility/attachments/attachments.cpp | 3 ++- compatibility/compatibility.cpp | 25 ++++++++--------------- 3 files changed, 10 insertions(+), 25 deletions(-) diff --git a/compatibility/CMakeLists.txt b/compatibility/CMakeLists.txt index 6810a891..11237dbd 100644 --- a/compatibility/CMakeLists.txt +++ b/compatibility/CMakeLists.txt @@ -37,13 +37,6 @@ function(add_compatibility_kit name) KIT_NDJSON_FILE="${CMAKE_CURRENT_SOURCE_DIR}/${name}/${name}.ndjson" $<$:SKIP_TEST> ) - - add_custom_command( - TARGET ${libname} POST_BUILD - COMMAND ${CMAKE_COMMAND} -E copy_directory - ${CMAKE_SOURCE_DIR}/compatibility/${name} - $/compatibility/${name} - ) endfunction() file(GLOB kits RELATIVE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/*) diff --git a/compatibility/attachments/attachments.cpp b/compatibility/attachments/attachments.cpp index a1ae80df..76eaa31b 100644 --- a/compatibility/attachments/attachments.cpp +++ b/compatibility/attachments/attachments.cpp @@ -5,12 +5,13 @@ #include #include #include +#include #include #include namespace { - const std::filesystem::path currentCompileDir = std::filesystem::current_path() / "compatibility" / "attachments"; + const std::filesystem::path currentCompileDir = std::filesystem::path{ std::source_location::current().file_name() }.parent_path(); } WHEN(R"(the string {string} is attached as {string})", (const std::string& text, const std::string& mediaType)) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index c939075f..f7eaaa2e 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -52,19 +52,11 @@ namespace compatibility Devkit LoadDevkit() { - std::cout << "LoadDevkit:\n"; - std::cout << "KIT_FOLDER: " << KIT_FOLDER << "\n"; - std::cout << "KIT_NDJSON_FILE: " << KIT_NDJSON_FILE << "\n"; - std::cout << "cwd: " << std::filesystem::current_path() << "\n"; - - std::cout << "paths: " << std::filesystem::current_path() / "compatibility" / KIT_STRING << "\n"; - std::cout << "ndjsonfile: " << std::filesystem::current_path() / "compatibility" / KIT_STRING / (std::string{ KIT_STRING } + ".ndjson") << "\n"; - return { - .paths = { std::filesystem::current_path() / "compatibility" / KIT_STRING }, + .paths = { KIT_FOLDER }, .tagExpression = "", .retry = 0, - .ndjsonFile = std::filesystem::current_path() / "compatibility" / KIT_STRING / (std::string{ KIT_STRING } + ".ndjson"), + .ndjsonFile = KIT_NDJSON_FILE }; } @@ -105,17 +97,16 @@ namespace compatibility json[key] = std::regex_replace(json[key].get(), std::regex(R"(\r\n)"), "\n"); ++jsonIter; } - else if (key == "uri" && (value.get().find("samples") != std::string::npos || value.get().find("compatibility") != std::string::npos)) + else if (key == "uri") { - jsonIter = json.erase(jsonIter); - // auto uri = value.get(); + auto uri = value.get(); - // uri = std::regex_replace(uri, std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); - // uri = std::regex_replace(uri, std::regex(R"(\.ts$)"), ".cpp"); + uri = std::regex_replace(uri, std::regex(R"(samples\/[^\/]+)"), KIT_FOLDER); + uri = std::regex_replace(uri, std::regex(R"(\.ts$)"), ".cpp"); - // json[key] = std::filesystem::canonical(uri).string(); + json[key] = std::filesystem::canonical(uri).string(); - // ++jsonIter; + ++jsonIter; } else ++jsonIter; From 2e421e57f3013dea07c5abf2f4f7d09d4d86b133 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 7 Jan 2026 15:37:53 +0000 Subject: [PATCH 093/196] refactor: mark FormatterOption constructor as explicit --- cucumber_cpp/library/api/Formatters.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/api/Formatters.hpp b/cucumber_cpp/library/api/Formatters.hpp index e1ac7f17..6588bc56 100644 --- a/cucumber_cpp/library/api/Formatters.hpp +++ b/cucumber_cpp/library/api/Formatters.hpp @@ -21,7 +21,7 @@ namespace cucumber_cpp::library::api { struct FormatterOption { - FormatterOption(std::string_view str); + explicit FormatterOption(std::string_view str); std::string name; std::string output; From 6338f25a775b157beabe344af6c350734e5f45ad Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 7 Jan 2026 15:37:59 +0000 Subject: [PATCH 094/196] refactor: implement group parsing logic in TreeRegexp and enhance structure --- .../cucumber_expression/TreeRegexp.cpp | 135 ++++++++++-------- .../cucumber_expression/TreeRegexp.hpp | 1 + 2 files changed, 75 insertions(+), 61 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp index 471c209f..029f7eb2 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.cpp @@ -25,6 +25,69 @@ namespace cucumber_cpp::library::cucumber_expression return pattern[pos + 3] == '=' || pattern[pos + 3] == '!'; } + + void StartGroup(std::deque& stack, std::deque& groupStartStack, std::string_view pattern, std::size_t patternIndex) + { + groupStartStack.emplace_back(patternIndex); + auto& groupBuilder = stack.emplace_back(); + if (IsNonCapturing(pattern, patternIndex)) + groupBuilder.SetNonCapturing(); + } + + void FinalizeGroup(std::deque& stack, std::deque& groupStartStack, std::string_view pattern, std::size_t patternIndex) + { + if (stack.empty()) + throw std::runtime_error("Empty stack"); + + auto groupBuilder = stack.back(); + stack.pop_back(); + + auto groupStart = groupStartStack.empty() ? 0 : groupStartStack.back(); + groupStart += 1; + + if (!groupStartStack.empty()) + groupStartStack.pop_back(); + + if (groupBuilder.IsCapturing()) + { + groupBuilder.SetPattern(pattern.substr(groupStart, patternIndex - groupStart)); + stack.back().Add(groupBuilder); + } + else + groupBuilder.MoveChildrenTo(stack.back()); + } + + struct PatternGroupParser + { + enum class State + { + nonGroup, + groupStart, + groupClose + }; + + State Parse(char c) + { + State state{}; + + if (c == '[' && !escaping) + charClass = true; + else if (c == ']' && !escaping) + charClass = false; + else if (c == '(' && !escaping && !charClass) + state = State::groupStart; + else if (c == ')' && !escaping && !charClass) + state = State::groupClose; + + escaping = (c == '\\' && !escaping); + + return state; + } + + private: + bool escaping{ false }; + bool charClass{ false }; + }; } void GroupBuilder::Add(GroupBuilder groupBuilder) @@ -80,8 +143,6 @@ namespace cucumber_cpp::library::cucumber_expression }; } - ///////////////////////////////////////////////////////// - TreeRegexp::TreeRegexp(std::string_view pattern) : rootGroupBuilder{ CreateGroupBuilder(pattern) } , regex{ std::string(pattern) } @@ -107,76 +168,27 @@ namespace cucumber_cpp::library::cucumber_expression { std::deque stack; std::deque groupStartStack; + PatternGroupParser patternParser; stack.emplace_back(); - bool escaping{ false }; - bool charClass{ false }; - - const auto isCharClassOpening = [&escaping](char c) - { - return c == '[' && !escaping; - }; - - const auto isCharClassClosing = [&escaping](char c) - { - return c == ']' && !escaping; - }; - - const auto isGroupOpening = [&escaping, &charClass](char c) - { - return c == '(' && !escaping && !charClass; - }; - - const auto isGroupClosing = [&escaping, &charClass](char c) - { - return c == ')' && !escaping && !charClass; - }; - - const auto isEscaping = [&escaping](char c) - { - return c == '\\' && !escaping; - }; - for (std::size_t i = 0; i < pattern.size(); ++i) { const char c = pattern[i]; - if (isCharClassOpening(c)) - charClass = true; - else if (isCharClassClosing(c)) - charClass = false; - else if (isGroupOpening(c)) + switch (patternParser.Parse(c)) { - groupStartStack.emplace_back(i); - auto& groupBuilder = stack.emplace_back(); - if (IsNonCapturing(pattern, i)) - groupBuilder.SetNonCapturing(); - } - else if (isGroupClosing(c)) - { - if (stack.empty()) - throw std::runtime_error("Empty stack"); - - auto groupBuilder = stack.back(); - stack.pop_back(); - - auto groupStart = groupStartStack.empty() ? 0 : groupStartStack.back(); - groupStart += 1; + case PatternGroupParser::State::groupStart: + StartGroup(stack, groupStartStack, pattern, i); + break; - if (!groupStartStack.empty()) - groupStartStack.pop_back(); + case PatternGroupParser::State::groupClose: + FinalizeGroup(stack, groupStartStack, pattern, i); + break; - if (groupBuilder.IsCapturing()) - { - groupBuilder.SetPattern(pattern.substr(groupStart, i - groupStart)); - stack.back().Add(groupBuilder); - } - else - groupBuilder.MoveChildrenTo(stack.back()); + case PatternGroupParser::State::nonGroup: + break; } - - escaping = isEscaping(c); } if (stack.empty()) @@ -184,4 +196,5 @@ namespace cucumber_cpp::library::cucumber_expression return stack.back(); } + } diff --git a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp index d5768f82..453e4299 100644 --- a/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp +++ b/cucumber_cpp/library/cucumber_expression/TreeRegexp.hpp @@ -43,6 +43,7 @@ namespace cucumber_cpp::library::cucumber_expression private: GroupBuilder CreateGroupBuilder(std::string_view pattern); + GroupBuilder rootGroupBuilder; std::regex regex; }; From 8e6e1f469baffe8720a70792b2eade7883377fa0 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 09:27:53 +0000 Subject: [PATCH 095/196] refactor: move ParameterRegistration instance to header for better visibility --- cucumber_cpp/library/Parameter.cpp | 1 - cucumber_cpp/library/Parameter.hpp | 4 ++++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/Parameter.cpp b/cucumber_cpp/library/Parameter.cpp index fd704942..1d241398 100644 --- a/cucumber_cpp/library/Parameter.cpp +++ b/cucumber_cpp/library/Parameter.cpp @@ -12,7 +12,6 @@ namespace cucumber_cpp::library ParameterRegistration& ParameterRegistration::Instance() { - static ParameterRegistration instance; return instance; } diff --git a/cucumber_cpp/library/Parameter.hpp b/cucumber_cpp/library/Parameter.hpp index c748eae0..b5355ecf 100644 --- a/cucumber_cpp/library/Parameter.hpp +++ b/cucumber_cpp/library/Parameter.hpp @@ -47,8 +47,12 @@ namespace cucumber_cpp::library private: std::set customParameters; + + static ParameterRegistration instance; }; + inline ParameterRegistration ParameterRegistration::instance; + //////////////////// // Implementation // //////////////////// From ccb2404070e9c7f9786688e5906cbf96a6232b4b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 09:31:40 +0000 Subject: [PATCH 096/196] refactor: replace emplace with try_emplace for custom output file handling --- cucumber_cpp/library/api/Formatters.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index 951dbfa2..e9be4628 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -57,7 +57,7 @@ namespace cucumber_cpp::library::api { const auto absolutePath = std::filesystem::absolute(std::filesystem::path{ option.output }).string(); if (!customOutputFiles.contains(absolutePath)) - customOutputFiles.emplace(absolutePath, std::make_unique(absolutePath)); + customOutputFiles.try_emplace(absolutePath, std::make_unique(absolutePath)); activeFormatters.emplace_back(availableFormatters.at(option.name).factory(supportCodeLibrary, query, eventDataCollector, formatOptions, *customOutputFiles.at(absolutePath))); } From cffd389b8ffc0c90d9aa08f84f8cda550466065b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 09:50:47 +0000 Subject: [PATCH 097/196] refactor: remove unused includes from Body.cpp --- cucumber_cpp/library/Body.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/cucumber_cpp/library/Body.cpp b/cucumber_cpp/library/Body.cpp index 14153f06..be15ac81 100644 --- a/cucumber_cpp/library/Body.cpp +++ b/cucumber_cpp/library/Body.cpp @@ -13,9 +13,6 @@ #include #include #include -#include -#include -#include namespace cucumber_cpp::library { From 8c8dc9d4165dbb1117b32b4db48f22c660246ceb Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 09:51:22 +0000 Subject: [PATCH 098/196] refactor: simplify event listener management and enhance parameter emission functions --- cucumber_cpp/library/Application.cpp | 13 ---- cucumber_cpp/library/api/RunCucumber.cpp | 79 +++++++++++++----------- 2 files changed, 44 insertions(+), 48 deletions(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index f59dab62..d3d72cd9 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -74,18 +74,6 @@ namespace cucumber_cpp::library return files; } - - struct RemoveDefaultEventListener - { - ~RemoveDefaultEventListener() - { - listeners.Append(defaultEventListener); - } - - private: - testing::TestEventListeners& listeners{ testing::UnitTest::GetInstance()->listeners() }; - testing::TestEventListener* defaultEventListener{ listeners.Release(listeners.default_result_printer()) }; - }; } Application::Application(std::shared_ptr contextStorageFactory, bool removeDefaultGoogleTestListener) @@ -214,7 +202,6 @@ namespace cucumber_cpp::library }, }; - RemoveDefaultEventListener removeDefaultListenerExceptionSafe; runPassed = api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, options.format, options.formatOptions); } diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 570d3a53..48a960f3 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -34,7 +34,7 @@ namespace cucumber_cpp::library::api { namespace { - void EmitParameters(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) + void EmitParameters(const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { for (const auto& [name, parameter] : supportCodeLibrary.parameterRegistry.GetParameters()) { @@ -58,13 +58,13 @@ namespace cucumber_cpp::library::api } } - void EmitUndefinedParameters(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) + void EmitUndefinedParameters(const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { for (const auto& parameter : supportCodeLibrary.undefinedParameters.definitions) broadcaster.BroadcastEvent({ .undefined_parameter_type = parameter }); } - void EmitStepDefinitions(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) + void EmitStepDefinitions(const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { for (const auto& stepDefinition : supportCodeLibrary.stepRegistry.StepDefinitions()) { @@ -84,7 +84,7 @@ namespace cucumber_cpp::library::api } } - void EmitTestCaseHooks(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) + void EmitTestCaseHooks(const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::before); @@ -97,7 +97,7 @@ namespace cucumber_cpp::library::api broadcaster.BroadcastEvent({ .hook = std::move(hook) }); } - void EmitTestRunHooks(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) + void EmitTestRunHooks(const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::beforeAll); @@ -110,7 +110,7 @@ namespace cucumber_cpp::library::api broadcaster.BroadcastEvent({ .hook = std::move(hook) }); } - void EmitSupportCodeMessages(support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) + void EmitSupportCodeMessages(const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, cucumber::gherkin::id_generator_ptr idGenerator) { EmitParameters(supportCodeLibrary, broadcaster, idGenerator); @@ -124,10 +124,45 @@ namespace cucumber_cpp::library::api EmitTestCaseHooks(supportCodeLibrary, broadcaster); EmitTestRunHooks(supportCodeLibrary, broadcaster); } + + auto FilterByTagExpression(const support::RunOptions::Sources& sources) + { + return [&sources](const support::PickleSource& pickle) + { + return sources.tagExpression->Evaluate(pickle.pickle->tags); + }; + } + + std::list OrderPickles(const support::RunOptions::Sources& sources, auto pickles) + { + const auto createOrderedPickleList = [](auto ordered) -> std::list + { + return { ordered.begin(), ordered.end() }; + }; + + if (sources.ordering == support::RunOptions::Ordering::defined) + return createOrderedPickleList(pickles); + else + return createOrderedPickleList(pickles | std::views::reverse); + }; + + struct RemoveDefaultEventListener + { + ~RemoveDefaultEventListener() + { + listeners.Append(defaultEventListener); + } + + private: + testing::TestEventListeners& listeners{ testing::UnitTest::GetInstance()->listeners() }; + testing::TestEventListener* defaultEventListener{ listeners.Release(listeners.default_result_printer()) }; + }; } bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set>& format, const std::string& formatOptions) { + RemoveDefaultEventListener removeDefaultListenerExceptionSafe; + cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); support::UndefinedParameters undefinedParameters; @@ -148,37 +183,11 @@ namespace cucumber_cpp::library::api const auto activeFormatters = formatters.EnableFormatters(format, formatOptionsJson, supportCodeLibrary, query, eventDataCollector); const auto pickleSources = CollectPickles(options.sources, idGenerator, broadcaster); - - const auto pickleFilter = [&options](const support::PickleSource& pickle) - { - return options.sources.tagExpression->Evaluate(pickle.pickle->tags); - }; - auto filteredPicklesView = pickleSources | std::views::filter(pickleFilter); - - const auto createOrderedPickleList = [](auto ordered) -> std::list - { - return { ordered.begin(), ordered.end() }; - }; - const auto orderPickles = [&](auto pickles) -> std::list - { - if (options.sources.ordering == support::RunOptions::Ordering::defined) - return createOrderedPickleList(pickles); - else - return createOrderedPickleList(pickles | std::views::reverse); - }; - std::list orderedPickles = orderPickles(filteredPicklesView); + const auto orderedPickles = OrderPickles(options.sources, pickleSources | std::views::filter(FilterByTagExpression(options.sources))); EmitSupportCodeMessages(supportCodeLibrary, broadcaster, idGenerator); - auto runtime = runtime::MakeRuntime(options.runtime, broadcaster, orderedPickles, supportCodeLibrary, idGenerator, programContext); - - auto& listeners = testing::UnitTest::GetInstance()->listeners(); - auto* defaultEventListener = listeners.Release(listeners.default_result_printer()); - - auto result = runtime->Run(); - - listeners.Append(defaultEventListener); - - return result; + const auto runtime = runtime::MakeRuntime(options.runtime, broadcaster, orderedPickles, supportCodeLibrary, idGenerator, programContext); + return runtime->Run(); } } From c8a25e4390bb5f12101f5f872c1ed9db9a647fa3 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 09:56:21 +0000 Subject: [PATCH 099/196] refactor: improve AssembleSteps function signature and optimize assembled test suite map handling --- cucumber_cpp/library/assemble/AssembleTestSuites.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index 180d0a13..edfda7b9 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -11,6 +11,7 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include #include #include @@ -40,7 +41,7 @@ namespace cucumber_cpp::library::assemble return pair.second.has_value(); } - void AssembleSteps(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) + void AssembleSteps(const support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { for (const auto& step : pickleSource.pickle->steps) { @@ -89,7 +90,7 @@ namespace cucumber_cpp::library::assemble cucumber::gherkin::id_generator_ptr idGenerator) { std::list testUris; - std::map assembledTestSuiteMap; + std::map> assembledTestSuiteMap; for (const auto& pickleSource : sourcedPickles) { @@ -107,7 +108,7 @@ namespace cucumber_cpp::library::assemble if (!assembledTestSuiteMap.contains(pickleSource.gherkinDocument->uri.value())) { testUris.emplace_back(pickleSource.gherkinDocument->uri.value()); - assembledTestSuiteMap.emplace(pickleSource.gherkinDocument->uri.value(), *pickleSource.gherkinDocument); + assembledTestSuiteMap.try_emplace(pickleSource.gherkinDocument->uri.value(), *pickleSource.gherkinDocument); } assembledTestSuiteMap.at(pickleSource.gherkinDocument->uri.value()).testCases.emplace_back(*pickleSource.pickle, testCase); From a47f823126c15eb24c06730836e620b6b749b875 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 10:41:31 +0000 Subject: [PATCH 100/196] refactor: optimize Argument constructor by using std::move for group parameter --- cucumber_cpp/library/cucumber_expression/Argument.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/cucumber_expression/Argument.cpp b/cucumber_cpp/library/cucumber_expression/Argument.cpp index 2745c277..a8ed1e7f 100644 --- a/cucumber_cpp/library/cucumber_expression/Argument.cpp +++ b/cucumber_cpp/library/cucumber_expression/Argument.cpp @@ -7,12 +7,13 @@ #include #include #include +#include #include namespace cucumber_cpp::library::cucumber_expression { Argument::Argument(cucumber::messages::group group, const Parameter& parameter) - : group{ group } + : group{ std::move(group) } , parameter{ parameter } {} From 4902aaa8550a2f5002d9b8818cf2f23c0e697000 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 11:22:45 +0000 Subject: [PATCH 101/196] refactor: implement custom signal handler for SIGABRT --- cucumber_cpp/library/api/RunCucumber.cpp | 26 ++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 48a960f3..07ad617f 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -12,8 +12,6 @@ #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/Gherkin.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" -#include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/runtime/MakeRuntime.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" @@ -21,8 +19,11 @@ #include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "nlohmann/json_fwd.hpp" +#include +#include #include #include +#include #include #include #include @@ -157,11 +158,32 @@ namespace cucumber_cpp::library::api testing::TestEventListeners& listeners{ testing::UnitTest::GetInstance()->listeners() }; testing::TestEventListener* defaultEventListener{ listeners.Release(listeners.default_result_printer()) }; }; + + void signal_handler(int signal) + { + if (signal == SIGABRT) + std::cerr << "SIGABRT received\n"; + else + std::cerr << "Unexpected signal " << signal << " received\n"; + std::_Exit(EXIT_FAILURE); + } + + struct OverrideAbortSignalHandler + { + ~OverrideAbortSignalHandler() + { + std::signal(SIGABRT, original); + } + + using signal_handler_t = void (*)(int); + signal_handler_t original{ std::signal(SIGABRT, signal_handler) }; + }; } bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set>& format, const std::string& formatOptions) { RemoveDefaultEventListener removeDefaultListenerExceptionSafe; + OverrideAbortSignalHandler overrideSignalHandler; cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); From 3a7dac9f176a4e92a29a2bb20d8c79b1ae2fb691 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 15:45:50 +0000 Subject: [PATCH 102/196] refactor: update CMakeLists to use consistent library naming and improve build configuration --- CMakeLists.txt | 2 +- cucumber_cpp/acceptance_test/hooks/CMakeLists.txt | 2 +- cucumber_cpp/acceptance_test/steps/CMakeLists.txt | 2 +- cucumber_cpp/library/CMakeLists.txt | 2 +- cucumber_cpp/library/api/CMakeLists.txt | 4 +++- cucumber_cpp/library/api/RunCucumber.cpp | 1 - cucumber_cpp/library/api/RunCucumber.hpp | 2 +- cucumber_cpp/library/assemble/CMakeLists.txt | 3 ++- cucumber_cpp/library/cucumber_expression/CMakeLists.txt | 3 +-- .../library/cucumber_expression/ParameterRegistry.cpp | 4 +--- .../library/cucumber_expression/test/CMakeLists.txt | 5 +++-- cucumber_cpp/library/engine/CMakeLists.txt | 4 ++-- cucumber_cpp/library/engine/ExecutionContext.cpp | 2 +- cucumber_cpp/library/engine/Step.cpp | 5 +---- cucumber_cpp/library/engine/test/CMakeLists.txt | 1 + cucumber_cpp/library/formatter/CMakeLists.txt | 6 ++++-- cucumber_cpp/library/formatter/helper/CMakeLists.txt | 4 +++- cucumber_cpp/library/runtime/CMakeLists.txt | 4 +++- cucumber_cpp/library/runtime/MakeRuntime.cpp | 2 +- cucumber_cpp/library/runtime/MakeRuntime.hpp | 2 +- cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp | 2 +- cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp | 2 +- cucumber_cpp/library/runtime/TestCaseRunner.cpp | 3 +-- cucumber_cpp/library/runtime/TestCaseRunner.hpp | 2 +- cucumber_cpp/library/runtime/Worker.cpp | 2 +- cucumber_cpp/library/runtime/Worker.hpp | 2 +- cucumber_cpp/library/support/CMakeLists.txt | 6 ++++-- cucumber_cpp/library/tag_expression/CMakeLists.txt | 2 +- cucumber_cpp/library/util/CMakeLists.txt | 2 +- 29 files changed, 44 insertions(+), 39 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6ec81b44..fb6a6387 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,7 +21,7 @@ option(CCR_FETCH_DEPS "Fetch dependencies via FetchContent." On ) option(CCR_BUILD_TESTS "Enable build of the tests" ${CCR_DEFAULTOPT}) option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements" Off) -set(BUILD_SHARED_LIBS Off CACHE STRING "") +set(CMAKE_POSITION_INDEPENDENT_CODE ON) if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) add_compile_options(-fsanitize=address) diff --git a/cucumber_cpp/acceptance_test/hooks/CMakeLists.txt b/cucumber_cpp/acceptance_test/hooks/CMakeLists.txt index ee263a66..2412ab7e 100644 --- a/cucumber_cpp/acceptance_test/hooks/CMakeLists.txt +++ b/cucumber_cpp/acceptance_test/hooks/CMakeLists.txt @@ -7,5 +7,5 @@ target_sources(cucumber_cpp.acceptance_test.hooks PRIVATE ) target_link_libraries(cucumber_cpp.acceptance_test.hooks PRIVATE - cucumber_cpp.library + cucumber_cpp ) diff --git a/cucumber_cpp/acceptance_test/steps/CMakeLists.txt b/cucumber_cpp/acceptance_test/steps/CMakeLists.txt index a5185d8c..25d017d4 100644 --- a/cucumber_cpp/acceptance_test/steps/CMakeLists.txt +++ b/cucumber_cpp/acceptance_test/steps/CMakeLists.txt @@ -7,5 +7,5 @@ target_sources(cucumber_cpp.acceptance_test.steps PRIVATE ) target_link_libraries(cucumber_cpp.acceptance_test.steps PRIVATE - cucumber_cpp.library + cucumber_cpp ) diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index 2cedc8d2..cfa87239 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -1,6 +1,6 @@ set(CMAKE_COMPILE_WARNING_AS_ERROR On) -add_library(cucumber_cpp.library STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library PRIVATE Application.cpp diff --git a/cucumber_cpp/library/api/CMakeLists.txt b/cucumber_cpp/library/api/CMakeLists.txt index 4464aa2d..96fababb 100644 --- a/cucumber_cpp/library/api/CMakeLists.txt +++ b/cucumber_cpp/library/api/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.api STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.api ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.api PRIVATE Formatters.cpp @@ -14,6 +14,8 @@ target_include_directories(cucumber_cpp.library.api PUBLIC ) target_link_libraries(cucumber_cpp.library.api PUBLIC + GTest::gtest + GTest::gmock cucumber_cpp.library.cucumber_expression ) diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 07ad617f..8b9d834a 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -5,7 +5,6 @@ #include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" diff --git a/cucumber_cpp/library/api/RunCucumber.hpp b/cucumber_cpp/library/api/RunCucumber.hpp index 1362ead2..34ac1976 100644 --- a/cucumber_cpp/library/api/RunCucumber.hpp +++ b/cucumber_cpp/library/api/RunCucumber.hpp @@ -1,7 +1,7 @@ #ifndef API_RUN_CUCUMBER_HPP #define API_RUN_CUCUMBER_HPP -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Types.hpp" diff --git a/cucumber_cpp/library/assemble/CMakeLists.txt b/cucumber_cpp/library/assemble/CMakeLists.txt index d245deeb..53525a7c 100644 --- a/cucumber_cpp/library/assemble/CMakeLists.txt +++ b/cucumber_cpp/library/assemble/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.assemble STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.assemble ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.assemble PRIVATE AssembledTestCase.hpp @@ -14,6 +14,7 @@ target_include_directories(cucumber_cpp.library.assemble PUBLIC target_link_libraries(cucumber_cpp.library.assemble PUBLIC # cucumber_cpp.library # cucumber_cpp.library.util + GTest::gtest cucumber_cpp.library.cucumber_expression ) diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index 283bfe28..ce67f181 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.cucumber_expression STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.cucumber_expression ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.cucumber_expression PRIVATE Argument.cpp @@ -28,7 +28,6 @@ target_include_directories(cucumber_cpp.library.cucumber_expression PUBLIC target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC cucumber_gherkin_lib - cucumber_cpp.library ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index d2203dfb..9166ae0b 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber/messages/group.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Parameter.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include #include @@ -11,9 +11,7 @@ #include #include #include -#include #include -#include #include namespace cucumber_cpp::library::cucumber_expression diff --git a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt index ed8f7629..da802baa 100644 --- a/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/test/CMakeLists.txt @@ -2,10 +2,11 @@ add_executable(cucumber_cpp.library.cucumber_expression.test) add_test(NAME cucumber_cpp.library.cucumber_expression.test COMMAND cucumber_cpp.library.cucumber_expression.test) target_link_libraries(cucumber_cpp.library.cucumber_expression.test PUBLIC - gmock_main + GTest::gmock_main + GTest::gmock + cucumber_cpp.library cucumber_cpp.library.cucumber_expression yaml-cpp::yaml-cpp - GTest::gmock ) target_sources(cucumber_cpp.library.cucumber_expression.test PRIVATE diff --git a/cucumber_cpp/library/engine/CMakeLists.txt b/cucumber_cpp/library/engine/CMakeLists.txt index 20cdc12b..44b92a37 100644 --- a/cucumber_cpp/library/engine/CMakeLists.txt +++ b/cucumber_cpp/library/engine/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.engine STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.engine ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.engine PRIVATE ExecutionContext.cpp @@ -15,7 +15,7 @@ target_include_directories(cucumber_cpp.library.engine PUBLIC target_link_libraries(cucumber_cpp.library.engine PUBLIC base64 - cucumber_cpp.library + # cucumber_cpp.library cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression cucumber_cpp.library.support diff --git a/cucumber_cpp/library/engine/ExecutionContext.cpp b/cucumber_cpp/library/engine/ExecutionContext.cpp index 8f2201cb..8a90a72c 100644 --- a/cucumber_cpp/library/engine/ExecutionContext.cpp +++ b/cucumber_cpp/library/engine/ExecutionContext.cpp @@ -4,7 +4,7 @@ #include "cucumber/messages/attachment_content_encoding.hpp" #include "cucumber/messages/test_run_hook_started.hpp" #include "cucumber/messages/test_step_started.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index abd67624..5a3738b6 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -1,13 +1,10 @@ #include "cucumber_cpp/library/engine/Step.hpp" #include "cucumber/messages/pickle_doc_string.hpp" -#include "cucumber/messages/pickle_table_row.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber/messages/pickle_table.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include -#include -#include #include namespace cucumber_cpp::library::engine diff --git a/cucumber_cpp/library/engine/test/CMakeLists.txt b/cucumber_cpp/library/engine/test/CMakeLists.txt index efb2fdf9..e224f962 100644 --- a/cucumber_cpp/library/engine/test/CMakeLists.txt +++ b/cucumber_cpp/library/engine/test/CMakeLists.txt @@ -2,6 +2,7 @@ add_executable(cucumber_cpp.library.engine.test) add_test(NAME cucumber_cpp.library.engine.test COMMAND cucumber_cpp.library.engine.test) target_link_libraries(cucumber_cpp.library.engine.test PUBLIC + cucumber_cpp.library cucumber_cpp.library.engine gmock_main # cucumber_cpp.library.engine.test_helper diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index a426e7e8..65e3080c 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.formatter STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.formatter ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.formatter PRIVATE Formatter.cpp @@ -16,9 +16,11 @@ target_include_directories(cucumber_cpp.library.formatter PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter PUBLIC + GTest::gtest + GTest::gmock cucumber_cpp.library.cucumber_expression cucumber_cpp.library.formatter.helper - cpp-terminal + cpp-terminal::cpp-terminal nlohmann_json ) diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index 10f8eafd..4245e74e 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.formatter.helper STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.formatter.helper ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.formatter.helper PRIVATE EventDataCollector.cpp @@ -28,6 +28,8 @@ target_include_directories(cucumber_cpp.library.formatter.helper PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter.helper PUBLIC + GTest::gtest + GTest::gmock # cucumber_cpp.library # cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression diff --git a/cucumber_cpp/library/runtime/CMakeLists.txt b/cucumber_cpp/library/runtime/CMakeLists.txt index 05272f1d..6c8f834b 100644 --- a/cucumber_cpp/library/runtime/CMakeLists.txt +++ b/cucumber_cpp/library/runtime/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.runtime STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.runtime ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.runtime PRIVATE Coordinator.cpp @@ -18,6 +18,8 @@ target_include_directories(cucumber_cpp.library.runtime PUBLIC ) target_link_libraries(cucumber_cpp.library.runtime PUBLIC + GTest::gtest + GTest::gmock # cucumber_cpp.library # cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression diff --git a/cucumber_cpp/library/runtime/MakeRuntime.cpp b/cucumber_cpp/library/runtime/MakeRuntime.cpp index 3e2b46dc..04b88d83 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.cpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.cpp @@ -1,6 +1,6 @@ #include "cucumber_cpp/library/runtime/MakeRuntime.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/runtime/Coordinator.hpp" #include "cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" diff --git a/cucumber_cpp/library/runtime/MakeRuntime.hpp b/cucumber_cpp/library/runtime/MakeRuntime.hpp index 59ac9e09..3b3f5953 100644 --- a/cucumber_cpp/library/runtime/MakeRuntime.hpp +++ b/cucumber_cpp/library/runtime/MakeRuntime.hpp @@ -2,7 +2,7 @@ #define RUNTIME_MAKE_RUNTIME_HPP #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp index 0fefeb5d..c78f0cd9 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp" #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" #include "cucumber_cpp/library/runtime/Worker.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" diff --git a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp index 4da486ac..0321b3ec 100644 --- a/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp +++ b/cucumber_cpp/library/runtime/SerialRuntimeAdapter.hpp @@ -2,7 +2,7 @@ #define RUNTIME_SERIAL_RUNTIME_ADAPTER_HPP #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index 4daa4733..756c9146 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -5,7 +5,6 @@ #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/pickle_table_row.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/suggestion.hpp" #include "cucumber/messages/test_case.hpp" @@ -16,8 +15,8 @@ #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber/messages/test_step_started.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/support/Duration.hpp" diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.hpp b/cucumber_cpp/library/runtime/TestCaseRunner.hpp index e1c9454c..d959d56e 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.hpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.hpp @@ -11,8 +11,8 @@ #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber/messages/test_step_started.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index 71674251..4b227a93 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -8,8 +8,8 @@ #include "cucumber/messages/test_run_hook_started.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" diff --git a/cucumber_cpp/library/runtime/Worker.hpp b/cucumber_cpp/library/runtime/Worker.hpp index 916e2414..5f6c547e 100644 --- a/cucumber_cpp/library/runtime/Worker.hpp +++ b/cucumber_cpp/library/runtime/Worker.hpp @@ -6,7 +6,7 @@ #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/CucumberCpp.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index e230c73c..9365805c 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.support STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.support ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.support PRIVATE Duration.hpp @@ -17,7 +17,9 @@ target_include_directories(cucumber_cpp.library.support PUBLIC ) target_link_libraries(cucumber_cpp.library.support PUBLIC - cucumber_cpp.library + GTest::gtest + GTest::gmock + # cucumber_cpp.library cucumber_cpp.library.util ) diff --git a/cucumber_cpp/library/tag_expression/CMakeLists.txt b/cucumber_cpp/library/tag_expression/CMakeLists.txt index 8454d1cd..47076a8d 100644 --- a/cucumber_cpp/library/tag_expression/CMakeLists.txt +++ b/cucumber_cpp/library/tag_expression/CMakeLists.txt @@ -1,6 +1,6 @@ set(CMAKE_COMPILE_WARNING_AS_ERROR On) -add_library(cucumber_cpp.library.tag_expression STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.tag_expression ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.tag_expression PRIVATE Model.cpp diff --git a/cucumber_cpp/library/util/CMakeLists.txt b/cucumber_cpp/library/util/CMakeLists.txt index c3f0ceb0..d2053a5b 100644 --- a/cucumber_cpp/library/util/CMakeLists.txt +++ b/cucumber_cpp/library/util/CMakeLists.txt @@ -1,4 +1,4 @@ -add_library(cucumber_cpp.library.util STATIC ${CCR_EXCLUDE_FROM_ALL}) +add_library(cucumber_cpp.library.util ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.util PRIVATE Broadcaster.cpp From e6c718b190b7c457897734c96c54437bc7c60033 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 16:04:59 +0000 Subject: [PATCH 103/196] refactor: replace ConverterTypeMap with support::ConverterTypeMap and update related includes --- cucumber_cpp/library/BodyMacro.hpp | 3 +- cucumber_cpp/library/CMakeLists.txt | 1 + cucumber_cpp/library/Parameter.hpp | 5 ++-- .../library/cucumber_expression/Argument.hpp | 3 +- .../cucumber_expression/CMakeLists.txt | 1 + .../cucumber_expression/ParameterRegistry.hpp | 19 ++----------- .../support/ParameterConversionTypeMap.hpp | 28 +++++++++++++++++++ 7 files changed, 38 insertions(+), 22 deletions(-) create mode 100644 cucumber_cpp/library/support/ParameterConversionTypeMap.hpp diff --git a/cucumber_cpp/library/BodyMacro.hpp b/cucumber_cpp/library/BodyMacro.hpp index 3e2afcef..3c4f2a84 100644 --- a/cucumber_cpp/library/BodyMacro.hpp +++ b/cucumber_cpp/library/BodyMacro.hpp @@ -5,13 +5,14 @@ #include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" +#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include #include template T TransformArg(const cucumber::messages::step_match_argument& match) { - return cucumber_cpp::library::cucumber_expression::ConverterTypeMap::Instance().at(match.parameter_type_name.value_or(""))(match.group); + return cucumber_cpp::library::support::ConverterTypeMap::Instance().at(match.parameter_type_name.value_or(""))(match.group); } #define BODY_MATCHER(matcher, ...) matcher diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index cfa87239..e235be5c 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -37,6 +37,7 @@ target_link_libraries(cucumber_cpp.library PUBLIC cucumber_cpp.library.tag_expression cucumber_cpp.library.util cucumber_cpp.library.formatter + cucumber_cpp.library.support CLI11 ) diff --git a/cucumber_cpp/library/Parameter.hpp b/cucumber_cpp/library/Parameter.hpp index b5355ecf..f3758dda 100644 --- a/cucumber_cpp/library/Parameter.hpp +++ b/cucumber_cpp/library/Parameter.hpp @@ -4,8 +4,7 @@ // IWYU pragma: private, include "cucumber_cpp/CucumberCpp.hpp" // IWYU pragma: friend cucumber_cpp/.* -#include "cucumber/messages/group.hpp" -#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include #include #include @@ -62,7 +61,7 @@ namespace cucumber_cpp::library { customParameters.emplace(params, customParameters.size() + 1, location); - cucumber_expression::ConverterTypeMap::Instance()[params.name] = Transformer::Transform; + support::ConverterTypeMap::Instance()[params.name] = Transformer::Transform; return customParameters.size(); } diff --git a/cucumber_cpp/library/cucumber_expression/Argument.hpp b/cucumber_cpp/library/cucumber_expression/Argument.hpp index a4bd55a5..c5468273 100644 --- a/cucumber_cpp/library/cucumber_expression/Argument.hpp +++ b/cucumber_cpp/library/cucumber_expression/Argument.hpp @@ -3,6 +3,7 @@ #include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include #include @@ -19,7 +20,7 @@ namespace cucumber_cpp::library::cucumber_expression template T GetValue() const { - return ConverterTypeMap::Instance().at(parameter.name)(group); + return support::ConverterTypeMap::Instance().at(parameter.name)(group); } cucumber::messages::group Group() const; diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index ce67f181..20fce78f 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -28,6 +28,7 @@ target_include_directories(cucumber_cpp.library.cucumber_expression PUBLIC target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC cucumber_gherkin_lib + cucumber_cpp.library.support ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp index 23528939..764fc877 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp @@ -4,6 +4,7 @@ #include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" +#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include #include #include @@ -97,22 +98,6 @@ namespace cucumber_cpp::library::cucumber_expression return iequals(s, "true") || iequals(s, "1") || iequals(s, "yes") || iequals(s, "on") || iequals(s, "enabled") || iequals(s, "active"); } - template - using TypeMap = std::map>; - - template - struct ConverterTypeMap - { - static std::map>& Instance(); - }; - - template - std::map>& ConverterTypeMap::Instance() - { - static std::map> typeMap; - return typeMap; - } - struct Parameter { std::string name; @@ -168,7 +153,7 @@ namespace cucumber_cpp::library::cucumber_expression AddParameter(parameter); - ConverterTypeMap::Instance().emplace(parameter.name, converter); + support::ConverterTypeMap::Instance().emplace(parameter.name, converter); } } diff --git a/cucumber_cpp/library/support/ParameterConversionTypeMap.hpp b/cucumber_cpp/library/support/ParameterConversionTypeMap.hpp new file mode 100644 index 00000000..663c99ab --- /dev/null +++ b/cucumber_cpp/library/support/ParameterConversionTypeMap.hpp @@ -0,0 +1,28 @@ +#ifndef SUPPORT_PARAMETER_CONVERSION_TYPE_MAP_HPP +#define SUPPORT_PARAMETER_CONVERSION_TYPE_MAP_HPP + +#include "cucumber/messages/group.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::support +{ + template + using TypeMap = std::map>; + + template + struct ConverterTypeMap + { + static std::map>& Instance(); + }; + + template + std::map>& ConverterTypeMap::Instance() + { + static std::map> typeMap; + return typeMap; + } +} + +#endif From c46a929224d2769196541fcb50acb5cfbc2ebbe3 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 22:22:26 +0000 Subject: [PATCH 104/196] refactor: remove unused GTest dependencies and clean up CMakeLists files --- cucumber_cpp/library/Body.hpp | 9 --------- cucumber_cpp/library/api/CMakeLists.txt | 1 - cucumber_cpp/library/assemble/CMakeLists.txt | 3 --- cucumber_cpp/library/formatter/CMakeLists.txt | 2 -- cucumber_cpp/library/formatter/helper/CMakeLists.txt | 4 ---- cucumber_cpp/library/runtime/CMakeLists.txt | 4 ---- cucumber_cpp/library/support/CMakeLists.txt | 3 --- 7 files changed, 26 deletions(-) diff --git a/cucumber_cpp/library/Body.hpp b/cucumber_cpp/library/Body.hpp index a5d2b836..aab8214a 100644 --- a/cucumber_cpp/library/Body.hpp +++ b/cucumber_cpp/library/Body.hpp @@ -1,19 +1,10 @@ #ifndef CUCUMBER_CPP_BODY_HPP #define CUCUMBER_CPP_BODY_HPP -#include "cucumber/messages/exception.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_step_result.hpp" -#include "cucumber/messages/test_step_result_status.hpp" #include -#include #include -#include -#include -#include -#include -#include -#include #include #include #include diff --git a/cucumber_cpp/library/api/CMakeLists.txt b/cucumber_cpp/library/api/CMakeLists.txt index 96fababb..911c0cc4 100644 --- a/cucumber_cpp/library/api/CMakeLists.txt +++ b/cucumber_cpp/library/api/CMakeLists.txt @@ -15,7 +15,6 @@ target_include_directories(cucumber_cpp.library.api PUBLIC target_link_libraries(cucumber_cpp.library.api PUBLIC GTest::gtest - GTest::gmock cucumber_cpp.library.cucumber_expression ) diff --git a/cucumber_cpp/library/assemble/CMakeLists.txt b/cucumber_cpp/library/assemble/CMakeLists.txt index 53525a7c..296aaf60 100644 --- a/cucumber_cpp/library/assemble/CMakeLists.txt +++ b/cucumber_cpp/library/assemble/CMakeLists.txt @@ -12,9 +12,6 @@ target_include_directories(cucumber_cpp.library.assemble PUBLIC ) target_link_libraries(cucumber_cpp.library.assemble PUBLIC - # cucumber_cpp.library - # cucumber_cpp.library.util - GTest::gtest cucumber_cpp.library.cucumber_expression ) diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index 65e3080c..cebb4f6f 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -16,8 +16,6 @@ target_include_directories(cucumber_cpp.library.formatter PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter PUBLIC - GTest::gtest - GTest::gmock cucumber_cpp.library.cucumber_expression cucumber_cpp.library.formatter.helper cpp-terminal::cpp-terminal diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index 4245e74e..51a29eae 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -28,10 +28,6 @@ target_include_directories(cucumber_cpp.library.formatter.helper PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter.helper PUBLIC - GTest::gtest - GTest::gmock - # cucumber_cpp.library - # cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression ) diff --git a/cucumber_cpp/library/runtime/CMakeLists.txt b/cucumber_cpp/library/runtime/CMakeLists.txt index 6c8f834b..e55ab6f5 100644 --- a/cucumber_cpp/library/runtime/CMakeLists.txt +++ b/cucumber_cpp/library/runtime/CMakeLists.txt @@ -18,10 +18,6 @@ target_include_directories(cucumber_cpp.library.runtime PUBLIC ) target_link_libraries(cucumber_cpp.library.runtime PUBLIC - GTest::gtest - GTest::gmock - # cucumber_cpp.library - # cucumber_cpp.library.util cucumber_cpp.library.cucumber_expression ) diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index 9365805c..7b3ef5bb 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -17,9 +17,6 @@ target_include_directories(cucumber_cpp.library.support PUBLIC ) target_link_libraries(cucumber_cpp.library.support PUBLIC - GTest::gtest - GTest::gmock - # cucumber_cpp.library cucumber_cpp.library.util ) From f65ebd1aea10f76e0c0f9c7c7dbc86d41286e8c4 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 8 Jan 2026 22:58:00 +0000 Subject: [PATCH 105/196] refactor: remove GTest dependencies and clean up RunCucumber implementation --- cucumber_cpp/library/api/CMakeLists.txt | 1 - cucumber_cpp/library/api/RunCucumber.cpp | 14 -------------- 2 files changed, 15 deletions(-) diff --git a/cucumber_cpp/library/api/CMakeLists.txt b/cucumber_cpp/library/api/CMakeLists.txt index 911c0cc4..cdf9d4ee 100644 --- a/cucumber_cpp/library/api/CMakeLists.txt +++ b/cucumber_cpp/library/api/CMakeLists.txt @@ -14,7 +14,6 @@ target_include_directories(cucumber_cpp.library.api PUBLIC ) target_link_libraries(cucumber_cpp.library.api PUBLIC - GTest::gtest cucumber_cpp.library.cucumber_expression ) diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 8b9d834a..a5cf9056 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -21,7 +21,6 @@ #include #include #include -#include #include #include #include @@ -146,18 +145,6 @@ namespace cucumber_cpp::library::api return createOrderedPickleList(pickles | std::views::reverse); }; - struct RemoveDefaultEventListener - { - ~RemoveDefaultEventListener() - { - listeners.Append(defaultEventListener); - } - - private: - testing::TestEventListeners& listeners{ testing::UnitTest::GetInstance()->listeners() }; - testing::TestEventListener* defaultEventListener{ listeners.Release(listeners.default_result_printer()) }; - }; - void signal_handler(int signal) { if (signal == SIGABRT) @@ -181,7 +168,6 @@ namespace cucumber_cpp::library::api bool RunCucumber(const support::RunOptions& options, cucumber_expression::ParameterRegistry& parameterRegistry, Context& programContext, util::Broadcaster& broadcaster, Formatters& formatters, const std::set>& format, const std::string& formatOptions) { - RemoveDefaultEventListener removeDefaultListenerExceptionSafe; OverrideAbortSignalHandler overrideSignalHandler; cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); From f95d9249d93adf76171771c94f7886e97bb1cd06 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 9 Jan 2026 11:35:39 +0000 Subject: [PATCH 106/196] refactor: remove Parameter class and update related implementations to use support::DefinitionRegistration --- cucumber_cpp/library/CMakeLists.txt | 1 - cucumber_cpp/library/Parameter.cpp | 22 ----- cucumber_cpp/library/Parameter.hpp | 84 +++---------------- .../cucumber_expression/ParameterRegistry.cpp | 5 +- .../library/support/SupportCodeLibrary.cpp | 13 +++ .../library/support/SupportCodeLibrary.hpp | 39 +++++++++ 6 files changed, 66 insertions(+), 98 deletions(-) delete mode 100644 cucumber_cpp/library/Parameter.cpp diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index e235be5c..45a3bfbb 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -13,7 +13,6 @@ target_sources(cucumber_cpp.library PRIVATE HookRegistry.cpp HookRegistry.hpp Hooks.hpp - Parameter.cpp Parameter.hpp Query.cpp Query.hpp diff --git a/cucumber_cpp/library/Parameter.cpp b/cucumber_cpp/library/Parameter.cpp deleted file mode 100644 index 1d241398..00000000 --- a/cucumber_cpp/library/Parameter.cpp +++ /dev/null @@ -1,22 +0,0 @@ -#include "cucumber_cpp/CucumberCpp.hpp" -#include -#include -#include - -namespace cucumber_cpp::library -{ - std::strong_ordering ParameterEntry::operator<=>(const ParameterEntry& other) const - { - return std::tie(params.name, params.regex) <=> std::tie(other.params.name, other.params.regex); - } - - ParameterRegistration& ParameterRegistration::Instance() - { - return instance; - } - - const std::set& ParameterRegistration::GetRegisteredParameters() const - { - return customParameters; - } -} diff --git a/cucumber_cpp/library/Parameter.hpp b/cucumber_cpp/library/Parameter.hpp index f3758dda..f7444cef 100644 --- a/cucumber_cpp/library/Parameter.hpp +++ b/cucumber_cpp/library/Parameter.hpp @@ -4,81 +4,21 @@ // IWYU pragma: private, include "cucumber_cpp/CucumberCpp.hpp" // IWYU pragma: friend cucumber_cpp/.* -#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library -{ - struct ParameterEntryParams - { - std::string name; - std::string regex; - bool useForSnippets; - }; - - struct ParameterEntry - { - ParameterEntryParams params; - - std::size_t localId; - - std::source_location location; - - std::strong_ordering operator<=>(const ParameterEntry& other) const; - }; - - struct ParameterRegistration - { - private: - ParameterRegistration() = default; - - public: - static ParameterRegistration& Instance(); - - template - std::size_t Register(ParameterEntryParams params, std::source_location location = std::source_location::current()); - - const std::set& GetRegisteredParameters() const; - - private: - std::set customParameters; - - static ParameterRegistration instance; - }; - - inline ParameterRegistration ParameterRegistration::instance; - - //////////////////// - // Implementation // - //////////////////// - - template - std::size_t ParameterRegistration::Register(ParameterEntryParams params, std::source_location location) - { - customParameters.emplace(params, customParameters.size() + 1, location); - - support::ConverterTypeMap::Instance()[params.name] = Transformer::Transform; - - return customParameters.size(); - } -} +#include "cucumber/messages/group.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #define PARAMETER_STRUCT CONCAT(ParameterImpl, __LINE__) -#define PARAMETER(Type, ...) \ - namespace \ - { \ - struct PARAMETER_STRUCT \ - { \ - static Type Transform(const cucumber::messages::group& group); \ - static const std::size_t ID; \ - }; \ - } \ - const std::size_t PARAMETER_STRUCT::ID = cucumber_cpp::library::ParameterRegistration::Instance().Register({ __VA_ARGS__ }); \ +#define PARAMETER(Type, ...) \ + namespace \ + { \ + struct PARAMETER_STRUCT \ + { \ + static Type Transform(const cucumber::messages::group& group); \ + static const std::size_t ID; \ + }; \ + } \ + const std::size_t PARAMETER_STRUCT::ID = cucumber_cpp::library::support::DefinitionRegistration::Instance().Register({ __VA_ARGS__ }); \ Type PARAMETER_STRUCT::Transform(const cucumber::messages::group& group) #endif diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index 9166ae0b..1e408c9c 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -1,8 +1,7 @@ - #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber/messages/group.hpp" -#include "cucumber_cpp/library/Parameter.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include #include #include @@ -68,7 +67,7 @@ namespace cucumber_cpp::library::cucumber_expression // extension AddBuiltinParameter("bool", { wordRegex }, CreateStreamConverter()); - const auto& parameterRegistration = cucumber_cpp::library::ParameterRegistration::Instance(); + const auto& parameterRegistration = cucumber_cpp::library::support::DefinitionRegistration::Instance(); for (const auto& parameter : parameterRegistration.GetRegisteredParameters()) AddParameter(Parameter{ parameter.params.name, { std::string(parameter.params.regex) }, false, parameter.params.useForSnippets, parameter.location }); } diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index df93c76f..a0111585 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -3,9 +3,12 @@ #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include #include +#include #include #include +#include #include #include #include @@ -15,6 +18,11 @@ namespace cucumber_cpp::library::support { + std::strong_ordering ParameterEntry::operator<=>(const ParameterEntry& other) const + { + return std::tie(params.name, params.regex) <=> std::tie(other.params.name, other.params.regex); + } + bool SourceLocationOrder::operator()(const std::source_location& lhs, const std::source_location& rhs) const { return std::forward_as_tuple(lhs.file_name(), lhs.line()) < std::forward_as_tuple(rhs.file_name(), rhs.line()); @@ -50,6 +58,11 @@ namespace cucumber_cpp::library::support return { allSteps.begin(), allSteps.end() }; } + const std::set>& DefinitionRegistration::GetRegisteredParameters() const + { + return customParameters; + } + namespace { void PrintContents(std::string_view type, std::source_location sourceLocation, const std::map& registry) diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp index bd364ae1..d572fb1f 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.hpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -6,12 +6,16 @@ #include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" +#include #include #include +#include #include #include #include +#include #include #include #include @@ -63,6 +67,24 @@ namespace cucumber_cpp::library::support std::string id{ "unassigned" }; }; + struct ParameterEntryParams + { + std::string name; + std::string regex; + bool useForSnippets; + }; + + struct ParameterEntry + { + ParameterEntryParams params; + + std::size_t localId; + + std::source_location location; + + std::strong_ordering operator<=>(const ParameterEntry& other) const; + }; + struct SupportCodeLibrary { HookRegistry& hookRegistry; @@ -93,6 +115,8 @@ namespace cucumber_cpp::library::support std::vector GetHooks(); + const std::set>& GetRegisteredParameters() const; + template static std::size_t Register(Hook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); @@ -102,12 +126,16 @@ namespace cucumber_cpp::library::support template static std::size_t Register(std::string_view matcher, engine::StepType stepType, std::source_location sourceLocation = std::source_location::current()); + template + static std::size_t Register(ParameterEntryParams params, std::source_location location = std::source_location::current()); + private: std::size_t Register(Hook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation); std::size_t Register(GlobalHook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation); std::size_t Register(std::string_view matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); std::map registry; + std::set> customParameters; }; ////////////////////////// @@ -149,6 +177,17 @@ namespace cucumber_cpp::library::support { return Instance().Register(matcher, stepType, StepBodyFactory, sourceLocation); } + + template + std::size_t DefinitionRegistration::Register(ParameterEntryParams params, std::source_location location) + { + auto& instance = Instance(); + instance.customParameters.emplace(params, instance.customParameters.size() + 1, location); + + ConverterTypeMap::Instance()[params.name] = Transformer::Transform; + + return instance.customParameters.size(); + } } #endif From bb8ceed63ffc2a19c835aed6fea1fb9ce2b99504 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 9 Jan 2026 11:52:04 +0000 Subject: [PATCH 107/196] refactor: remove unused Body include from Worker.cpp --- cucumber_cpp/library/runtime/Worker.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index 4b227a93..fbf2c649 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -8,7 +8,6 @@ #include "cucumber/messages/test_run_hook_started.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" From 4ac0d91cdb3c0c6baa19b2c5f23be3cfe71f857b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 9 Jan 2026 13:48:22 +0000 Subject: [PATCH 108/196] movesupport code to the support library --- cucumber_cpp/library/Application.cpp | 2 +- cucumber_cpp/library/Application.hpp | 2 +- cucumber_cpp/library/BodyMacro.hpp | 6 +++--- cucumber_cpp/library/CMakeLists.txt | 6 ------ cucumber_cpp/library/Hooks.hpp | 20 +++++++++---------- cucumber_cpp/library/Steps.hpp | 2 +- cucumber_cpp/library/api/RunCucumber.cpp | 16 +++++++-------- .../library/assemble/AssembleTestSuites.cpp | 10 +++++----- .../library/runtime/TestCaseRunner.cpp | 16 +++++++-------- .../library/runtime/TestCaseRunner.hpp | 10 +++++----- cucumber_cpp/library/runtime/Worker.cpp | 10 +++++----- cucumber_cpp/library/{ => support}/Body.cpp | 4 ++-- cucumber_cpp/library/{ => support}/Body.hpp | 2 +- cucumber_cpp/library/support/CMakeLists.txt | 11 +++++++++- .../library/{ => support}/HookRegistry.cpp | 4 ++-- .../library/{ => support}/HookRegistry.hpp | 4 ++-- .../library/{ => support}/StepRegistry.cpp | 4 ++-- .../library/{ => support}/StepRegistry.hpp | 4 ++-- .../library/support/SupportCodeLibrary.cpp | 4 ++-- .../library/support/SupportCodeLibrary.hpp | 4 ++-- cucumber_cpp/library/test/TestSteps.cpp | 2 +- 21 files changed, 73 insertions(+), 70 deletions(-) rename cucumber_cpp/library/{ => support}/Body.cpp (97%) rename cucumber_cpp/library/{ => support}/Body.hpp (97%) rename cucumber_cpp/library/{ => support}/HookRegistry.cpp (98%) rename cucumber_cpp/library/{ => support}/HookRegistry.hpp (97%) rename cucumber_cpp/library/{ => support}/StepRegistry.cpp (98%) rename cucumber_cpp/library/{ => support}/StepRegistry.hpp (98%) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index d3d72cd9..466734f4 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -1,12 +1,12 @@ #include "cucumber_cpp/library/Application.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/Errors.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" #include diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 487d9ef5..8f1075e9 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -5,10 +5,10 @@ // IWYU pragma: friend cucumber_cpp/.* #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Duration.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" diff --git a/cucumber_cpp/library/BodyMacro.hpp b/cucumber_cpp/library/BodyMacro.hpp index 3c4f2a84..3316f654 100644 --- a/cucumber_cpp/library/BodyMacro.hpp +++ b/cucumber_cpp/library/BodyMacro.hpp @@ -2,9 +2,9 @@ #define CUCUMBER_CPP_BODYMACRO_HPP #include "cucumber/messages/step_match_argument.hpp" -#include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" +#include "cucumber_cpp/library/support/Body.hpp" #include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include #include @@ -26,7 +26,7 @@ T TransformArg(const cucumber::messages::step_match_argument& match) #define BODY(matcher, type, targs, registration, base) \ namespace \ { \ - struct BODY_STRUCT : cucumber_cpp::library::Body \ + struct BODY_STRUCT : cucumber_cpp::library::support::Body \ , base \ { \ /* Workaround namespaces in `base`. For example `base` = Foo::Bar. */ \ @@ -36,7 +36,7 @@ T TransformArg(const cucumber::messages::step_match_argument& match) \ void Execute(const cucumber::messages::step_match_arguments_list& args) override \ { \ - cucumber_cpp::library::SetUpTearDownWrapper wrapper{ *this }; \ + cucumber_cpp::library::support::SetUpTearDownWrapper wrapper{ *this }; \ ExecuteWithArgs(args, static_cast(nullptr)); \ } \ \ diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index 45a3bfbb..964dbd68 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -5,21 +5,15 @@ add_library(cucumber_cpp.library ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library PRIVATE Application.cpp Application.hpp - Body.cpp - Body.hpp BodyMacro.hpp Context.hpp Errors.hpp - HookRegistry.cpp - HookRegistry.hpp Hooks.hpp Parameter.hpp Query.cpp Query.hpp Rtrim.cpp Rtrim.hpp - StepRegistry.cpp - StepRegistry.hpp Steps.hpp ) diff --git a/cucumber_cpp/library/Hooks.hpp b/cucumber_cpp/library/Hooks.hpp index 03f4d66e..3d73562b 100644 --- a/cucumber_cpp/library/Hooks.hpp +++ b/cucumber_cpp/library/Hooks.hpp @@ -5,49 +5,49 @@ // IWYU pragma: friend cucumber_cpp/.* #include "cucumber_cpp/library/BodyMacro.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::support::DefinitionRegistration::Register, cucumber_cpp::library::HookBase) +#define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::support::DefinitionRegistration::Register, cucumber_cpp::library::support::HookBase) #define HOOK_BEFORE_ALL(...) \ HOOK_( \ (cucumber_cpp::library::support::GlobalHook{ __VA_ARGS__ }), \ - cucumber_cpp::library::HookType::beforeAll) + cucumber_cpp::library::support::HookType::beforeAll) #define HOOK_AFTER_ALL(...) \ HOOK_( \ (cucumber_cpp::library::support::GlobalHook{ __VA_ARGS__ }), \ - cucumber_cpp::library::HookType::afterAll) + cucumber_cpp::library::support::HookType::afterAll) #define HOOK_BEFORE_FEATURE(...) \ HOOK_( \ (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ - cucumber_cpp::library::HookType::beforeFeature) + cucumber_cpp::library::support::HookType::beforeFeature) #define HOOK_AFTER_FEATURE(...) \ HOOK_( \ (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ - cucumber_cpp::library::HookType::afterFeature) + cucumber_cpp::library::support::HookType::afterFeature) #define HOOK_BEFORE_SCENARIO(...) \ HOOK_( \ (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ - cucumber_cpp::library::HookType::before) + cucumber_cpp::library::support::HookType::before) #define HOOK_AFTER_SCENARIO(...) \ HOOK_( \ (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ - cucumber_cpp::library::HookType::after) + cucumber_cpp::library::support::HookType::after) #define HOOK_BEFORE_STEP(...) \ HOOK_( \ (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ - cucumber_cpp::library::HookType::beforeStep) + cucumber_cpp::library::support::HookType::beforeStep) #define HOOK_AFTER_STEP(...) \ HOOK_( \ (cucumber_cpp::library::support::Hook{ __VA_ARGS__ }), \ - cucumber_cpp::library::HookType::afterStep) + cucumber_cpp::library::support::HookType::afterStep) #endif diff --git a/cucumber_cpp/library/Steps.hpp b/cucumber_cpp/library/Steps.hpp index 2eb7f727..4c2ba329 100644 --- a/cucumber_cpp/library/Steps.hpp +++ b/cucumber_cpp/library/Steps.hpp @@ -5,8 +5,8 @@ // IWYU pragma: friend cucumber_cpp/.* #include "cucumber_cpp/library/BodyMacro.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/engine/Step.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #define STEP_(matcher, type, args, fixture) BODY(matcher, type, args, cucumber_cpp::library::support::DefinitionRegistration::Register, fixture) diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index a5cf9056..88b9a90f 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -5,14 +5,14 @@ #include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/Query.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/Gherkin.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/runtime/MakeRuntime.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" @@ -85,12 +85,12 @@ namespace cucumber_cpp::library::api void EmitTestCaseHooks(const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { - auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::before); + auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(support::HookType::before); for (auto& hook : beforeAllHooks) broadcaster.BroadcastEvent({ .hook = std::move(hook) }); - auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::after); + auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(support::HookType::after); for (auto& hook : afterAllHooks) broadcaster.BroadcastEvent({ .hook = std::move(hook) }); @@ -98,12 +98,12 @@ namespace cucumber_cpp::library::api void EmitTestRunHooks(const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster) { - auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::beforeAll); + auto beforeAllHooks = supportCodeLibrary.hookRegistry.HooksByType(support::HookType::beforeAll); for (auto& hook : beforeAllHooks) broadcaster.BroadcastEvent({ .hook = std::move(hook) }); - auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(HookType::afterAll); + auto afterAllHooks = supportCodeLibrary.hookRegistry.HooksByType(support::HookType::afterAll); for (auto& hook : afterAllHooks) broadcaster.BroadcastEvent({ .hook = std::move(hook) }); @@ -173,8 +173,8 @@ namespace cucumber_cpp::library::api cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); support::UndefinedParameters undefinedParameters; - StepRegistry stepRegistry{ parameterRegistry, undefinedParameters, idGenerator }; - HookRegistry hookRegistry{ idGenerator }; + support::StepRegistry stepRegistry{ parameterRegistry, undefinedParameters, idGenerator }; + support::HookRegistry hookRegistry{ idGenerator }; support::SupportCodeLibrary supportCodeLibrary{ .hookRegistry = hookRegistry, diff --git a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp index edfda7b9..524fb29f 100644 --- a/cucumber_cpp/library/assemble/AssembleTestSuites.cpp +++ b/cucumber_cpp/library/assemble/AssembleTestSuites.cpp @@ -3,11 +3,11 @@ #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_case.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" #include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -29,7 +29,7 @@ namespace cucumber_cpp::library::assemble { auto TransformToMatch(const std::string& text) { - return [&text](const StepRegistry::Definition& definition) -> std::pair>> + return [&text](const support::StepRegistry::Definition& definition) -> std::pair>> { const auto match = std::visit(cucumber_expression::MatchVisitor{ text }, definition.regex); return { definition.id, match }; @@ -68,8 +68,8 @@ namespace cucumber_cpp::library::assemble void AssembleTestSteps(support::SupportCodeLibrary& supportCodeLibrary, const support::PickleSource& pickleSource, cucumber::messages::test_case& testCase, cucumber::gherkin::id_generator_ptr idGenerator) { - auto beforeHooks = supportCodeLibrary.hookRegistry.FindIds(HookType::before, pickleSource.pickle->tags); - auto afterHooks = supportCodeLibrary.hookRegistry.FindIds(HookType::after, pickleSource.pickle->tags); + auto beforeHooks = supportCodeLibrary.hookRegistry.FindIds(support::HookType::before, pickleSource.pickle->tags); + auto afterHooks = supportCodeLibrary.hookRegistry.FindIds(support::HookType::after, pickleSource.pickle->tags); testCase.test_steps.reserve(beforeHooks.size() + pickleSource.pickle->steps.size() + afterHooks.size()); diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index 756c9146..b1b4a601 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -15,11 +15,11 @@ #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber/messages/test_step_started.hpp" -#include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/support/Body.hpp" #include "cucumber_cpp/library/support/Duration.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -130,7 +130,7 @@ namespace cucumber_cpp::library::runtime return willRetry; } - cucumber::messages::test_step_result TestCaseRunner::RunHook(const HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted) + cucumber::messages::test_step_result TestCaseRunner::RunHook(const support::HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted) { if (ShouldSkipHook(isBeforeHook)) return { @@ -141,7 +141,7 @@ namespace cucumber_cpp::library::runtime return InvokeStep(hookDefinition.factory(broadcaster, testCaseContext, testStepStarted)); } - std::vector TestCaseRunner::RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted) + std::vector TestCaseRunner::RunStepHooks(const cucumber::messages::pickle_step& pickleStep, support::HookType hookType, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted) { auto ids = supportCodeLibrary.hookRegistry.FindIds(hookType, pickle.tags); std::vector results; @@ -192,7 +192,7 @@ namespace cucumber_cpp::library::runtime }; } - auto stepResults = RunStepHooks(pickleStep, HookType::beforeStep, testCaseContext, testStepStarted); + auto stepResults = RunStepHooks(pickleStep, support::HookType::beforeStep, testCaseContext, testStepStarted); if (util::GetWorstTestStepResult(stepResults).status != cucumber::messages::test_step_result_status::FAILED) { @@ -204,7 +204,7 @@ namespace cucumber_cpp::library::runtime stepResults.push_back(result); } - const auto afterStepHookResults = RunStepHooks(pickleStep, HookType::afterStep, testCaseContext, testStepStarted); + const auto afterStepHookResults = RunStepHooks(pickleStep, support::HookType::afterStep, testCaseContext, testStepStarted); stepResults.reserve(stepResults.size() + afterStepHookResults.size()); stepResults.insert(stepResults.end(), afterStepHookResults.begin(), afterStepHookResults.end()); @@ -218,7 +218,7 @@ namespace cucumber_cpp::library::runtime return finalStepResult; } - cucumber::messages::test_step_result TestCaseRunner::InvokeStep(std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args) + cucumber::messages::test_step_result TestCaseRunner::InvokeStep(std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args) { return body->ExecuteAndCatchExceptions(args); } diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.hpp b/cucumber_cpp/library/runtime/TestCaseRunner.hpp index d959d56e..2cc8d709 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.hpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.hpp @@ -11,9 +11,9 @@ #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber/messages/test_step_started.hpp" -#include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/support/Body.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include @@ -38,13 +38,13 @@ namespace cucumber_cpp::library::runtime bool RunAttempt(std::size_t attempt, bool moreAttemptsAvailable); - cucumber::messages::test_step_result RunHook(const HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); + cucumber::messages::test_step_result RunHook(const support::HookRegistry::Definition& hookDefinition, bool isBeforeHook, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); - std::vector RunStepHooks(const cucumber::messages::pickle_step& pickleStep, HookType hookType, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); + std::vector RunStepHooks(const cucumber::messages::pickle_step& pickleStep, support::HookType hookType, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); cucumber::messages::test_step_result RunStep(const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::test_step& testStep, Context& testCaseContext, cucumber::messages::test_step_started testStepStarted); - cucumber::messages::test_step_result InvokeStep(std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args = {}); + cucumber::messages::test_step_result InvokeStep(std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args = {}); cucumber::messages::test_step_result GetWorstStepResult() const; bool ShouldSkipHook(bool isBeforeHook); diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index fbf2c649..a84d0ebf 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -9,10 +9,10 @@ #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" #include "cucumber_cpp/library/assemble/AssembledTestCase.hpp" #include "cucumber_cpp/library/assemble/AssembledTestSuite.hpp" #include "cucumber_cpp/library/runtime/TestCaseRunner.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" @@ -90,7 +90,7 @@ namespace cucumber_cpp::library::runtime std::vector Worker::RunBeforeAllHooks() { std::vector results; - const auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::beforeAll); + const auto ids = supportCodeLibrary.hookRegistry.FindIds(support::HookType::beforeAll); for (const auto& id : ids) results.emplace_back(std::move(RunTestHook(id, programContext))); @@ -100,7 +100,7 @@ namespace cucumber_cpp::library::runtime std::vector Worker::RunAfterAllHooks() { std::vector results; - auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::afterAll); + auto ids = supportCodeLibrary.hookRegistry.FindIds(support::HookType::afterAll); for (const auto& id : ids | std::views::reverse) results.emplace_back(std::move(RunTestHook(id, programContext))); @@ -144,7 +144,7 @@ namespace cucumber_cpp::library::runtime std::vector Worker::RunBeforeTestSuiteHooks(const cucumber::messages::feature& feature, Context& context) { std::vector results; - const auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::beforeFeature, feature.tags); + const auto ids = supportCodeLibrary.hookRegistry.FindIds(support::HookType::beforeFeature, feature.tags); for (const auto& id : ids) results.emplace_back(std::move(RunTestHook(id, context))); @@ -157,7 +157,7 @@ namespace cucumber_cpp::library::runtime std::vector Worker::RunAfterTestSuiteHooks(const cucumber::messages::feature& feature, Context& context) { std::vector results; - const auto ids = supportCodeLibrary.hookRegistry.FindIds(HookType::afterFeature, feature.tags); + const auto ids = supportCodeLibrary.hookRegistry.FindIds(support::HookType::afterFeature, feature.tags); for (const auto& id : ids) results.emplace_back(std::move(RunTestHook(id, context))); diff --git a/cucumber_cpp/library/Body.cpp b/cucumber_cpp/library/support/Body.cpp similarity index 97% rename from cucumber_cpp/library/Body.cpp rename to cucumber_cpp/library/support/Body.cpp index be15ac81..866b68f1 100644 --- a/cucumber_cpp/library/Body.cpp +++ b/cucumber_cpp/library/support/Body.cpp @@ -1,4 +1,4 @@ -#include "cucumber_cpp/library/Body.hpp" +#include "cucumber_cpp/library/support/Body.hpp" #include "cucumber/messages/exception.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_step_result.hpp" @@ -14,7 +14,7 @@ #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::support { struct CucumberResultReporter : public testing::ScopedFakeTestPartResultReporter { diff --git a/cucumber_cpp/library/Body.hpp b/cucumber_cpp/library/support/Body.hpp similarity index 97% rename from cucumber_cpp/library/Body.hpp rename to cucumber_cpp/library/support/Body.hpp index aab8214a..fc80e30d 100644 --- a/cucumber_cpp/library/Body.hpp +++ b/cucumber_cpp/library/support/Body.hpp @@ -10,7 +10,7 @@ #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::support { using ExecuteArgs = std::variant, std::vector>; diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index 7b3ef5bb..5861c120 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -1,15 +1,22 @@ add_library(cucumber_cpp.library.support ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.support PRIVATE - Duration.hpp + Body.cpp + Body.hpp Duration.cpp + Duration.hpp + HookRegistry.cpp + HookRegistry.hpp Join.cpp Join.hpp + StepRegistry.cpp + StepRegistry.hpp SupportCodeLibrary.cpp SupportCodeLibrary.hpp Timestamp.cpp Timestamp.hpp Types.hpp + ) target_include_directories(cucumber_cpp.library.support PUBLIC @@ -17,6 +24,8 @@ target_include_directories(cucumber_cpp.library.support PUBLIC ) target_link_libraries(cucumber_cpp.library.support PUBLIC + GTest::gtest + GTest::gmock cucumber_cpp.library.util ) diff --git a/cucumber_cpp/library/HookRegistry.cpp b/cucumber_cpp/library/support/HookRegistry.cpp similarity index 98% rename from cucumber_cpp/library/HookRegistry.cpp rename to cucumber_cpp/library/support/HookRegistry.cpp index a0265e5b..67286f8f 100644 --- a/cucumber_cpp/library/HookRegistry.cpp +++ b/cucumber_cpp/library/support/HookRegistry.cpp @@ -1,5 +1,5 @@ -#include "cucumber_cpp/library/HookRegistry.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/hook_type.hpp" #include "cucumber/messages/location.hpp" @@ -23,7 +23,7 @@ #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::support { namespace { diff --git a/cucumber_cpp/library/HookRegistry.hpp b/cucumber_cpp/library/support/HookRegistry.hpp similarity index 97% rename from cucumber_cpp/library/HookRegistry.hpp rename to cucumber_cpp/library/support/HookRegistry.hpp index 44513ef6..2bdafa40 100644 --- a/cucumber_cpp/library/HookRegistry.hpp +++ b/cucumber_cpp/library/support/HookRegistry.hpp @@ -5,9 +5,9 @@ #include "cucumber/messages/hook.hpp" #include "cucumber/messages/pickle_tag.hpp" #include "cucumber/messages/tag.hpp" -#include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/support/Body.hpp" #include "cucumber_cpp/library/tag_expression/Model.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include @@ -23,7 +23,7 @@ #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::support { enum struct HookType { diff --git a/cucumber_cpp/library/StepRegistry.cpp b/cucumber_cpp/library/support/StepRegistry.cpp similarity index 98% rename from cucumber_cpp/library/StepRegistry.cpp rename to cucumber_cpp/library/support/StepRegistry.cpp index 8d3864ff..9cd5c823 100644 --- a/cucumber_cpp/library/StepRegistry.cpp +++ b/cucumber_cpp/library/support/StepRegistry.cpp @@ -1,4 +1,4 @@ -#include "cucumber_cpp/library/StepRegistry.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" #include "cucumber_cpp/library/cucumber_expression/Argument.hpp" @@ -23,7 +23,7 @@ #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::support { StepRegistry::StepRegistry(cucumber_expression::ParameterRegistry& parameterRegistry, support::UndefinedParameters& undefinedParameters, cucumber::gherkin::id_generator_ptr idGenerator) : parameterRegistry{ parameterRegistry } diff --git a/cucumber_cpp/library/StepRegistry.hpp b/cucumber_cpp/library/support/StepRegistry.hpp similarity index 98% rename from cucumber_cpp/library/StepRegistry.hpp rename to cucumber_cpp/library/support/StepRegistry.hpp index 3c202e98..cbe1c761 100644 --- a/cucumber_cpp/library/StepRegistry.hpp +++ b/cucumber_cpp/library/support/StepRegistry.hpp @@ -6,13 +6,13 @@ #include "cucumber/messages/pickle_table.hpp" #include "cucumber/messages/pickle_table_row.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" -#include "cucumber_cpp/library/Body.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/support/Body.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include @@ -33,7 +33,7 @@ #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::support { using StepFactory = std::unique_ptr (&)(util::Broadcaster& broadCaster, Context&, engine::StepOrHookStarted stepOrHookStarted, const std::optional&, const std::optional&); diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index a0111585..3c706c7d 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -1,8 +1,8 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include #include #include diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp index d572fb1f..92fc5f79 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.hpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -2,11 +2,11 @@ #define SUPPORT_SUPPORT_CODE_LIBRARY_HPP #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber_cpp/library/HookRegistry.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include #include diff --git a/cucumber_cpp/library/test/TestSteps.cpp b/cucumber_cpp/library/test/TestSteps.cpp index a4246490..61ef0323 100644 --- a/cucumber_cpp/library/test/TestSteps.cpp +++ b/cucumber_cpp/library/test/TestSteps.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/Query.hpp" -#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "gtest/gtest.h" #include #include From b77cf83e95e24927b587173b13c088ebfe94372d Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 9 Jan 2026 14:16:01 +0000 Subject: [PATCH 109/196] refactor: enable test subdirectory and add dummy test implementation --- cucumber_cpp/library/support/CMakeLists.txt | 2 +- cucumber_cpp/library/support/test/CMakeLists.txt | 12 ++++++++++++ cucumber_cpp/library/support/test/TestDummy.cpp | 8 ++++++++ 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 cucumber_cpp/library/support/test/CMakeLists.txt create mode 100644 cucumber_cpp/library/support/test/TestDummy.cpp diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index 5861c120..0c07dc49 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -30,6 +30,6 @@ target_link_libraries(cucumber_cpp.library.support PUBLIC ) if (CCR_BUILD_TESTS) - # add_subdirectory(test) + add_subdirectory(test) # add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/support/test/CMakeLists.txt b/cucumber_cpp/library/support/test/CMakeLists.txt new file mode 100644 index 00000000..c1ea2130 --- /dev/null +++ b/cucumber_cpp/library/support/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(cucumber_cpp.library.support.test) +add_test(NAME cucumber_cpp.library.support.test COMMAND cucumber_cpp.library.support.test) + +target_link_libraries(cucumber_cpp.library.support.test PUBLIC + gmock_main + cucumber_cpp.library.support + GTest::gmock +) + +target_sources(cucumber_cpp.library.support.test PRIVATE + TestDummy.cpp +) diff --git a/cucumber_cpp/library/support/test/TestDummy.cpp b/cucumber_cpp/library/support/test/TestDummy.cpp new file mode 100644 index 00000000..c81abaf1 --- /dev/null +++ b/cucumber_cpp/library/support/test/TestDummy.cpp @@ -0,0 +1,8 @@ +#include + +namespace cucumber_cpp::library::support +{ + TEST(Dummy, dummy) + { + } +} From fe318424d8b0bea7d879eeef7846b0fdc9e04aa5 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 9 Jan 2026 14:16:07 +0000 Subject: [PATCH 110/196] refactor: remove unused includes from Token.hpp --- cucumber_cpp/library/tag_expression/Token.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/cucumber_cpp/library/tag_expression/Token.hpp b/cucumber_cpp/library/tag_expression/Token.hpp index e1f24a21..a45e22a6 100644 --- a/cucumber_cpp/library/tag_expression/Token.hpp +++ b/cucumber_cpp/library/tag_expression/Token.hpp @@ -4,9 +4,7 @@ #include #include #include -#include #include -#include namespace cucumber_cpp::library::tag_expression { From f5cfead409a478ce46049d8da70b3bc877a9dff7 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 9 Jan 2026 14:49:28 +0000 Subject: [PATCH 111/196] refactor: update parameter registration and step handling to use support code library --- compatibility/compatibility.cpp | 3 +- cucumber_cpp/library/Application.hpp | 3 +- cucumber_cpp/library/BodyMacro.hpp | 3 +- cucumber_cpp/library/Hooks.hpp | 4 +- cucumber_cpp/library/Steps.hpp | 8 ++-- .../library/cucumber_expression/Argument.hpp | 3 +- .../cucumber_expression/CMakeLists.txt | 1 - .../cucumber_expression/ParameterRegistry.cpp | 13 ++++-- .../cucumber_expression/ParameterRegistry.hpp | 43 ++++++++++++++++--- .../test/TestExpression.cpp | 2 +- .../test/TestTransformation.cpp | 2 +- cucumber_cpp/library/engine/CMakeLists.txt | 3 +- .../library/engine/ExecutionContext.hpp | 28 ++---------- cucumber_cpp/library/engine/Hook.cpp | 12 ++++++ cucumber_cpp/library/engine/Hook.hpp | 28 ++++++++++++ cucumber_cpp/library/support/Body.cpp | 5 +-- cucumber_cpp/library/support/Body.hpp | 27 ++++++++++++ cucumber_cpp/library/support/CMakeLists.txt | 4 +- cucumber_cpp/library/support/HookRegistry.cpp | 4 -- cucumber_cpp/library/support/HookRegistry.hpp | 17 -------- .../support/ParameterConversionTypeMap.hpp | 26 +++++------ cucumber_cpp/library/support/StepRegistry.cpp | 4 +- cucumber_cpp/library/support/StepRegistry.hpp | 15 +++---- .../library/{engine => support}/StepType.hpp | 2 +- .../library/support/SupportCodeLibrary.cpp | 13 ++---- .../library/support/SupportCodeLibrary.hpp | 38 ++++------------ cucumber_cpp/library/test/TestSteps.cpp | 7 +-- 27 files changed, 179 insertions(+), 139 deletions(-) create mode 100644 cucumber_cpp/library/engine/Hook.cpp create mode 100644 cucumber_cpp/library/engine/Hook.hpp rename cucumber_cpp/library/{engine => support}/StepType.hpp (79%) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index f7eaaa2e..655bf82d 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -4,6 +4,7 @@ #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Duration.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" @@ -259,7 +260,7 @@ namespace compatibility }, }; - cucumber_cpp::library::cucumber_expression::ParameterRegistry parameterRegistry{}; + cucumber_cpp::library::cucumber_expression::ParameterRegistry parameterRegistry{ cucumber_cpp::library::support::DefinitionRegistration::Instance().GetRegisteredParameters() }; auto contextStorageFactory{ std::make_shared() }; auto programContext{ std::make_unique(contextStorageFactory) }; diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 8f1075e9..cebde23a 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -9,6 +9,7 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -75,7 +76,7 @@ namespace cucumber_cpp::library util::Broadcaster broadcaster; - cucumber_expression::ParameterRegistry parameterRegistry{}; + cucumber_expression::ParameterRegistry parameterRegistry{ cucumber_cpp::library::support::DefinitionRegistration::Instance().GetRegisteredParameters() }; bool removeDefaultGoogleTestListener; support::StopWatchHighResolutionClock stopwatchHighResolutionClock; support::TimestampGeneratorSystemClock timestampGeneratorSystemClock; diff --git a/cucumber_cpp/library/BodyMacro.hpp b/cucumber_cpp/library/BodyMacro.hpp index 3316f654..60c03570 100644 --- a/cucumber_cpp/library/BodyMacro.hpp +++ b/cucumber_cpp/library/BodyMacro.hpp @@ -5,14 +5,13 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/StringTo.hpp" #include "cucumber_cpp/library/support/Body.hpp" -#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include #include template T TransformArg(const cucumber::messages::step_match_argument& match) { - return cucumber_cpp::library::support::ConverterTypeMap::Instance().at(match.parameter_type_name.value_or(""))(match.group); + return cucumber_cpp::library::cucumber_expression::ConverterTypeMap::Instance().at(match.parameter_type_name.value_or(""))(match.group); } #define BODY_MATCHER(matcher, ...) matcher diff --git a/cucumber_cpp/library/Hooks.hpp b/cucumber_cpp/library/Hooks.hpp index 3d73562b..d895a1ef 100644 --- a/cucumber_cpp/library/Hooks.hpp +++ b/cucumber_cpp/library/Hooks.hpp @@ -5,10 +5,10 @@ // IWYU pragma: friend cucumber_cpp/.* #include "cucumber_cpp/library/BodyMacro.hpp" -#include "cucumber_cpp/library/support/HookRegistry.hpp" +#include "cucumber_cpp/library/engine/Hook.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::support::DefinitionRegistration::Register, cucumber_cpp::library::support::HookBase) +#define HOOK_(matcher, type) BODY(matcher, type, (), cucumber_cpp::library::support::DefinitionRegistration::Register, cucumber_cpp::library::engine::HookBase) #define HOOK_BEFORE_ALL(...) \ HOOK_( \ diff --git a/cucumber_cpp/library/Steps.hpp b/cucumber_cpp/library/Steps.hpp index 4c2ba329..4bcd7410 100644 --- a/cucumber_cpp/library/Steps.hpp +++ b/cucumber_cpp/library/Steps.hpp @@ -18,10 +18,10 @@ BODY_ARGS(__VA_ARGS__, (), ()), \ fixture) -#define GIVEN_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::engine::StepType::given, __VA_ARGS__) -#define WHEN_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::engine::StepType::when, __VA_ARGS__) -#define THEN_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::engine::StepType::then, __VA_ARGS__) -#define STEP_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::engine::StepType::any, __VA_ARGS__) +#define GIVEN_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::support::StepType::given, __VA_ARGS__) +#define WHEN_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::support::StepType::when, __VA_ARGS__) +#define THEN_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::support::StepType::then, __VA_ARGS__) +#define STEP_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::support::StepType::any, __VA_ARGS__) #define GIVEN(...) GIVEN_F(cucumber_cpp::library::engine::Step, __VA_ARGS__) #define WHEN(...) WHEN_F(cucumber_cpp::library::engine::Step, __VA_ARGS__) diff --git a/cucumber_cpp/library/cucumber_expression/Argument.hpp b/cucumber_cpp/library/cucumber_expression/Argument.hpp index c5468273..a4bd55a5 100644 --- a/cucumber_cpp/library/cucumber_expression/Argument.hpp +++ b/cucumber_cpp/library/cucumber_expression/Argument.hpp @@ -3,7 +3,6 @@ #include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include #include @@ -20,7 +19,7 @@ namespace cucumber_cpp::library::cucumber_expression template T GetValue() const { - return support::ConverterTypeMap::Instance().at(parameter.name)(group); + return ConverterTypeMap::Instance().at(parameter.name)(group); } cucumber::messages::group Group() const; diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index 20fce78f..ce67f181 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -28,7 +28,6 @@ target_include_directories(cucumber_cpp.library.cucumber_expression PUBLIC target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC cucumber_gherkin_lib - cucumber_cpp.library.support ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index 1e408c9c..54feb15d 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -3,6 +3,7 @@ #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include +#include #include #include #include @@ -10,7 +11,9 @@ #include #include #include +#include #include +#include #include namespace cucumber_cpp::library::cucumber_expression @@ -44,7 +47,12 @@ namespace cucumber_cpp::library::cucumber_expression } } - ParameterRegistry::ParameterRegistry() + std::strong_ordering CustomParameterEntry::operator<=>(const CustomParameterEntry& other) const + { + return std::tie(params.name, params.regex) <=> std::tie(other.params.name, other.params.regex); + } + + ParameterRegistry::ParameterRegistry(const std::set>& customParameters) { const static std::string integerNegativeRegex{ R"__(-?\d+)__" }; const static std::string integerPositiveRegex{ R"__(\d+)__" }; @@ -67,8 +75,7 @@ namespace cucumber_cpp::library::cucumber_expression // extension AddBuiltinParameter("bool", { wordRegex }, CreateStreamConverter()); - const auto& parameterRegistration = cucumber_cpp::library::support::DefinitionRegistration::Instance(); - for (const auto& parameter : parameterRegistration.GetRegisteredParameters()) + for (const auto& parameter : customParameters) AddParameter(Parameter{ parameter.params.name, { std::string(parameter.params.regex) }, false, parameter.params.useForSnippets, parameter.location }); } diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp index 764fc877..bd52ec46 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp @@ -2,12 +2,10 @@ #define CUCUMBER_EXPRESSION_PARAMETERREGISTRY_HPP #include "cucumber/messages/group.hpp" -#include "cucumber_cpp/library/cucumber_expression/Errors.hpp" -#include "cucumber_cpp/library/cucumber_expression/MatchRange.hpp" -#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include #include #include +#include #include #include #include @@ -15,6 +13,7 @@ #include #include #include +#include #include #include #include @@ -26,6 +25,24 @@ namespace cucumber_cpp::library::cucumber_expression { using namespace std::literals; + struct CustomParameterEntryParams + { + std::string name; + std::string regex; + bool useForSnippets; + }; + + struct CustomParameterEntry + { + CustomParameterEntryParams params; + + std::size_t localId; + + std::source_location location; + + std::strong_ordering operator<=>(const CustomParameterEntry& other) const; + }; + struct ConversionError : std::runtime_error { using std::runtime_error::runtime_error; @@ -107,9 +124,25 @@ namespace cucumber_cpp::library::cucumber_expression std::source_location location; }; + template + using TypeMap = std::map>; + + template + struct ConverterTypeMap + { + static std::map>& Instance(); + }; + + template + std::map>& ConverterTypeMap::Instance() + { + static std::map> typeMap; + return typeMap; + } + struct ParameterRegistry { - ParameterRegistry(); + explicit ParameterRegistry(const std::set>& customParameters); virtual ~ParameterRegistry() = default; @@ -153,7 +186,7 @@ namespace cucumber_cpp::library::cucumber_expression AddParameter(parameter); - support::ConverterTypeMap::Instance().emplace(parameter.name, converter); + ConverterTypeMap::Instance().emplace(parameter.name, converter); } } diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index 980c82e1..57a772d5 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -47,7 +47,7 @@ namespace cucumber_cpp::library::cucumber_expression struct TestExpression : testing::Test { - ParameterRegistry parameterRegistry{}; + ParameterRegistry parameterRegistry{ {} }; template std::optional Match(std::string expr, std::string text) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp b/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp index 8b51ed69..2295ab21 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp @@ -40,7 +40,7 @@ namespace cucumber_cpp::library::cucumber_expression TEST(TestTransformation, TestFromFiles) { std::filesystem::path testdataPath = "testdata/cucumber-expression/transformation"; - ParameterRegistry parameterRegistry{}; + ParameterRegistry parameterRegistry{ {} }; for (const auto& [file, testdata] : GetTestData(testdataPath)) { diff --git a/cucumber_cpp/library/engine/CMakeLists.txt b/cucumber_cpp/library/engine/CMakeLists.txt index 44b92a37..24b25d47 100644 --- a/cucumber_cpp/library/engine/CMakeLists.txt +++ b/cucumber_cpp/library/engine/CMakeLists.txt @@ -3,9 +3,10 @@ add_library(cucumber_cpp.library.engine ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.engine PRIVATE ExecutionContext.cpp ExecutionContext.hpp + Hook.cpp + Hook.hpp Step.cpp Step.hpp - StepType.hpp StringTo.hpp ) diff --git a/cucumber_cpp/library/engine/ExecutionContext.hpp b/cucumber_cpp/library/engine/ExecutionContext.hpp index bc332f9a..71ffa7ef 100644 --- a/cucumber_cpp/library/engine/ExecutionContext.hpp +++ b/cucumber_cpp/library/engine/ExecutionContext.hpp @@ -5,40 +5,18 @@ #include "cucumber/messages/test_run_hook_started.hpp" #include "cucumber/messages/test_step_started.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/support/Body.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include #include #include #include #include -#include #include namespace cucumber_cpp::library::engine { - struct StepSkipped : std::exception - { - StepSkipped(std::string message, std::source_location sourceLocation) - : message{ std::move(message) } - , sourceLocation{ sourceLocation } - { - } - - std::string message; - std::source_location sourceLocation; - }; - - struct StepPending : std::exception - { - StepPending(std::string message, std::source_location sourceLocation) - : message{ std::move(message) } - , sourceLocation{ sourceLocation } - { - } - - std::string message; - std::source_location sourceLocation; - }; + using StepSkipped = support::StepSkipped; + using StepPending = support::StepPending; struct AttachOptions { diff --git a/cucumber_cpp/library/engine/Hook.cpp b/cucumber_cpp/library/engine/Hook.cpp new file mode 100644 index 00000000..3a51685e --- /dev/null +++ b/cucumber_cpp/library/engine/Hook.cpp @@ -0,0 +1,12 @@ +#include "cucumber_cpp/library/engine/Hook.hpp" +#include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include + +namespace cucumber_cpp::library::engine +{ + HookBase::HookBase(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted) + : engine::ExecutionContext{ broadCaster, context, std::move(stepOrHookStarted) } + {} +} diff --git a/cucumber_cpp/library/engine/Hook.hpp b/cucumber_cpp/library/engine/Hook.hpp new file mode 100644 index 00000000..27340646 --- /dev/null +++ b/cucumber_cpp/library/engine/Hook.hpp @@ -0,0 +1,28 @@ +#ifndef ENGINE_HOOK_HPP +#define ENGINE_HOOK_HPP + +#include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" + +namespace cucumber_cpp::library::engine +{ + struct HookBase : engine::ExecutionContext + { + HookBase(util::Broadcaster& broadCaster, Context& context, StepOrHookStarted stepOrHookStarted); + + virtual ~HookBase() = default; + + virtual void SetUp() + { + /* nothing to do */ + } + + virtual void TearDown() + { + /* nothing to do */ + } + }; +} + +#endif diff --git a/cucumber_cpp/library/support/Body.cpp b/cucumber_cpp/library/support/Body.cpp index 866b68f1..4b366a24 100644 --- a/cucumber_cpp/library/support/Body.cpp +++ b/cucumber_cpp/library/support/Body.cpp @@ -3,7 +3,6 @@ #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/support/Duration.hpp" #include "gtest/gtest.h" #include @@ -56,13 +55,13 @@ namespace cucumber_cpp::library::support support::Stopwatch::Instance().Start(); Execute(args); } - catch (const engine::StepSkipped& e) + catch (const StepSkipped& e) { testStepResult.status = cucumber::messages::test_step_result_status::SKIPPED; if (!e.message.empty()) testStepResult.message = e.message; } - catch (const engine::StepPending& e) + catch (const StepPending& e) { testStepResult.status = cucumber::messages::test_step_result_status::PENDING; if (!e.message.empty()) diff --git a/cucumber_cpp/library/support/Body.hpp b/cucumber_cpp/library/support/Body.hpp index fc80e30d..8cc9ac03 100644 --- a/cucumber_cpp/library/support/Body.hpp +++ b/cucumber_cpp/library/support/Body.hpp @@ -5,8 +5,11 @@ #include "cucumber/messages/test_step_result.hpp" #include #include +#include +#include #include #include +#include #include #include @@ -19,6 +22,30 @@ namespace cucumber_cpp::library::support using std::runtime_error::runtime_error; }; + struct StepSkipped : std::exception + { + StepSkipped(std::string message, std::source_location sourceLocation) + : message{ std::move(message) } + , sourceLocation{ sourceLocation } + { + } + + std::string message; + std::source_location sourceLocation; + }; + + struct StepPending : std::exception + { + StepPending(std::string message, std::source_location sourceLocation) + : message{ std::move(message) } + , sourceLocation{ sourceLocation } + { + } + + std::string message; + std::source_location sourceLocation; + }; + struct Body { virtual ~Body() = default; diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index 0c07dc49..d65d9877 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -11,12 +11,12 @@ target_sources(cucumber_cpp.library.support PRIVATE Join.hpp StepRegistry.cpp StepRegistry.hpp + StepType.hpp SupportCodeLibrary.cpp SupportCodeLibrary.hpp Timestamp.cpp Timestamp.hpp Types.hpp - ) target_include_directories(cucumber_cpp.library.support PUBLIC @@ -26,6 +26,8 @@ target_include_directories(cucumber_cpp.library.support PUBLIC target_link_libraries(cucumber_cpp.library.support PUBLIC GTest::gtest GTest::gmock + cucumber_cpp.library.cucumber_expression + cucumber_cpp.library.tag_expression cucumber_cpp.library.util ) diff --git a/cucumber_cpp/library/support/HookRegistry.cpp b/cucumber_cpp/library/support/HookRegistry.cpp index 67286f8f..a78896ee 100644 --- a/cucumber_cpp/library/support/HookRegistry.cpp +++ b/cucumber_cpp/library/support/HookRegistry.cpp @@ -63,10 +63,6 @@ namespace cucumber_cpp::library::support }; } - HookBase::HookBase(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted) - : engine::ExecutionContext{ broadCaster, context, std::move(stepOrHookStarted) } - {} - HookRegistry::Definition::Definition(std::string id, HookType type, std::optional expression, std::optional name, HookFactory factory, std::source_location sourceLocation) : type{ type } , tagExpression{ tag_expression::Parse(expression.value_or("")) } diff --git a/cucumber_cpp/library/support/HookRegistry.hpp b/cucumber_cpp/library/support/HookRegistry.hpp index 2bdafa40..43a17919 100644 --- a/cucumber_cpp/library/support/HookRegistry.hpp +++ b/cucumber_cpp/library/support/HookRegistry.hpp @@ -37,23 +37,6 @@ namespace cucumber_cpp::library::support afterStep, }; - struct HookBase : engine::ExecutionContext - { - HookBase(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted); - - virtual ~HookBase() = default; - - virtual void SetUp() - { - /* nothing to do */ - } - - virtual void TearDown() - { - /* nothing to do */ - } - }; - using HookFactory = std::unique_ptr (&)(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted); template diff --git a/cucumber_cpp/library/support/ParameterConversionTypeMap.hpp b/cucumber_cpp/library/support/ParameterConversionTypeMap.hpp index 663c99ab..2cf7acc7 100644 --- a/cucumber_cpp/library/support/ParameterConversionTypeMap.hpp +++ b/cucumber_cpp/library/support/ParameterConversionTypeMap.hpp @@ -8,21 +8,21 @@ namespace cucumber_cpp::library::support { - template - using TypeMap = std::map>; + // template + // using TypeMap = std::map>; - template - struct ConverterTypeMap - { - static std::map>& Instance(); - }; + // template + // struct ConverterTypeMap + // { + // static std::map>& Instance(); + // }; - template - std::map>& ConverterTypeMap::Instance() - { - static std::map> typeMap; - return typeMap; - } + // template + // std::map>& ConverterTypeMap::Instance() + // { + // static std::map> typeMap; + // return typeMap; + // } } #endif diff --git a/cucumber_cpp/library/support/StepRegistry.cpp b/cucumber_cpp/library/support/StepRegistry.cpp index 9cd5c823..84f69f1a 100644 --- a/cucumber_cpp/library/support/StepRegistry.cpp +++ b/cucumber_cpp/library/support/StepRegistry.cpp @@ -7,7 +7,7 @@ #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/RegularExpression.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/support/StepType.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include @@ -79,7 +79,7 @@ namespace cucumber_cpp::library::support return registry; } - void StepRegistry::Register(std::string id, const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) + void StepRegistry::Register(std::string id, const std::string& matcher, StepType stepType, StepFactory factory, std::source_location sourceLocation) { try { diff --git a/cucumber_cpp/library/support/StepRegistry.hpp b/cucumber_cpp/library/support/StepRegistry.hpp index cbe1c761..1af1af30 100644 --- a/cucumber_cpp/library/support/StepRegistry.hpp +++ b/cucumber_cpp/library/support/StepRegistry.hpp @@ -4,15 +4,14 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/pickle_doc_string.hpp" #include "cucumber/messages/pickle_table.hpp" -#include "cucumber/messages/pickle_table_row.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/cucumber_expression/Argument.hpp" #include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/support/Body.hpp" +#include "cucumber_cpp/library/support/StepType.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include @@ -79,7 +78,7 @@ namespace cucumber_cpp::library::support std::size_t line; std::filesystem::path uri; - engine::StepType type; + StepType type; std::string pattern; cucumber_expression::Matcher regex; cucumber::messages::step_definition_pattern_type patternType; @@ -112,7 +111,7 @@ namespace cucumber_cpp::library::support const std::list& StepDefinitions() const; private: - void Register(std::string id, const std::string& matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); + void Register(std::string id, const std::string& matcher, StepType stepType, StepFactory factory, std::source_location sourceLocation); cucumber_expression::ParameterRegistry& parameterRegistry; support::UndefinedParameters& undefinedParameters; @@ -132,14 +131,14 @@ namespace cucumber_cpp::library::support struct Entry { - Entry(engine::StepType type, std::string regex, StepFactory factory, std::source_location sourceLocation) + Entry(StepType type, std::string regex, StepFactory factory, std::source_location sourceLocation) : type{ type } , regex{ std::move(regex) } , factory{ factory } , sourceLocation{ sourceLocation } {} - engine::StepType type{}; + StepType type{}; std::string regex; StepFactory factory; std::source_location sourceLocation; @@ -147,7 +146,7 @@ namespace cucumber_cpp::library::support }; template - static std::size_t Register(const std::string& matcher, engine::StepType stepType, std::source_location sourceLocation = std::source_location::current()); + static std::size_t Register(const std::string& matcher, StepType stepType, std::source_location sourceLocation = std::source_location::current()); std::span GetEntries(); [[nodiscard]] std::span GetEntries() const; @@ -161,7 +160,7 @@ namespace cucumber_cpp::library::support ////////////////////////// template - std::size_t StepStringRegistration::Register(const std::string& matcher, engine::StepType stepType, std::source_location sourceLocation) + std::size_t StepStringRegistration::Register(const std::string& matcher, StepType stepType, std::source_location sourceLocation) { Instance().registry.emplace_back(stepType, matcher, StepBodyFactory, sourceLocation); diff --git a/cucumber_cpp/library/engine/StepType.hpp b/cucumber_cpp/library/support/StepType.hpp similarity index 79% rename from cucumber_cpp/library/engine/StepType.hpp rename to cucumber_cpp/library/support/StepType.hpp index 2aa5a87a..c967bdb6 100644 --- a/cucumber_cpp/library/engine/StepType.hpp +++ b/cucumber_cpp/library/support/StepType.hpp @@ -1,7 +1,7 @@ #ifndef ENGINE_STEPTYPE_HPP #define ENGINE_STEPTYPE_HPP -namespace cucumber_cpp::library::engine +namespace cucumber_cpp::library::support { enum struct StepType { diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index 3c706c7d..213f1df6 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -1,9 +1,9 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" -#include +#include "cucumber_cpp/library/support/StepType.hpp" #include #include #include @@ -18,11 +18,6 @@ namespace cucumber_cpp::library::support { - std::strong_ordering ParameterEntry::operator<=>(const ParameterEntry& other) const - { - return std::tie(params.name, params.regex) <=> std::tie(other.params.name, other.params.regex); - } - bool SourceLocationOrder::operator()(const std::source_location& lhs, const std::source_location& rhs) const { return std::forward_as_tuple(lhs.file_name(), lhs.line()) < std::forward_as_tuple(rhs.file_name(), rhs.line()); @@ -58,7 +53,7 @@ namespace cucumber_cpp::library::support return { allSteps.begin(), allSteps.end() }; } - const std::set>& DefinitionRegistration::GetRegisteredParameters() const + const std::set>& DefinitionRegistration::GetRegisteredParameters() const { return customParameters; } @@ -88,7 +83,7 @@ namespace cucumber_cpp::library::support return registry.size(); } - std::size_t DefinitionRegistration::Register(std::string_view matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation) + std::size_t DefinitionRegistration::Register(std::string_view matcher, StepType stepType, StepFactory factory, std::source_location sourceLocation) { registry.emplace(sourceLocation, StepStringRegistration::Entry{ stepType, std::string{ matcher }, factory, sourceLocation }); PrintContents("Step", sourceLocation, registry); diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.hpp b/cucumber_cpp/library/support/SupportCodeLibrary.hpp index 92fc5f79..a047c9eb 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.hpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.hpp @@ -3,12 +3,10 @@ #include "cucumber/gherkin/id_generator.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/engine/StepType.hpp" #include "cucumber_cpp/library/support/HookRegistry.hpp" -#include "cucumber_cpp/library/support/ParameterConversionTypeMap.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" +#include "cucumber_cpp/library/support/StepType.hpp" #include "cucumber_cpp/library/support/UndefinedParameters.hpp" -#include #include #include #include @@ -67,24 +65,6 @@ namespace cucumber_cpp::library::support std::string id{ "unassigned" }; }; - struct ParameterEntryParams - { - std::string name; - std::string regex; - bool useForSnippets; - }; - - struct ParameterEntry - { - ParameterEntryParams params; - - std::size_t localId; - - std::source_location location; - - std::strong_ordering operator<=>(const ParameterEntry& other) const; - }; - struct SupportCodeLibrary { HookRegistry& hookRegistry; @@ -115,7 +95,7 @@ namespace cucumber_cpp::library::support std::vector GetHooks(); - const std::set>& GetRegisteredParameters() const; + const std::set>& GetRegisteredParameters() const; template static std::size_t Register(Hook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); @@ -124,18 +104,18 @@ namespace cucumber_cpp::library::support static std::size_t Register(GlobalHook hook, HookType hookType, std::source_location sourceLocation = std::source_location::current()); template - static std::size_t Register(std::string_view matcher, engine::StepType stepType, std::source_location sourceLocation = std::source_location::current()); + static std::size_t Register(std::string_view matcher, StepType stepType, std::source_location sourceLocation = std::source_location::current()); template - static std::size_t Register(ParameterEntryParams params, std::source_location location = std::source_location::current()); + static std::size_t Register(cucumber_expression::CustomParameterEntryParams params, std::source_location location = std::source_location::current()); private: std::size_t Register(Hook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation); std::size_t Register(GlobalHook hook, HookType hookType, HookFactory factory, std::source_location sourceLocation); - std::size_t Register(std::string_view matcher, engine::StepType stepType, StepFactory factory, std::source_location sourceLocation); + std::size_t Register(std::string_view matcher, StepType stepType, StepFactory factory, std::source_location sourceLocation); std::map registry; - std::set> customParameters; + std::set> customParameters; }; ////////////////////////// @@ -173,18 +153,18 @@ namespace cucumber_cpp::library::support } template - std::size_t DefinitionRegistration::Register(std::string_view matcher, engine::StepType stepType, std::source_location sourceLocation) + std::size_t DefinitionRegistration::Register(std::string_view matcher, StepType stepType, std::source_location sourceLocation) { return Instance().Register(matcher, stepType, StepBodyFactory, sourceLocation); } template - std::size_t DefinitionRegistration::Register(ParameterEntryParams params, std::source_location location) + std::size_t DefinitionRegistration::Register(cucumber_expression::CustomParameterEntryParams params, std::source_location location) { auto& instance = Instance(); instance.customParameters.emplace(params, instance.customParameters.size() + 1, location); - ConverterTypeMap::Instance()[params.name] = Transformer::Transform; + cucumber_expression::ConverterTypeMap::Instance()[params.name] = Transformer::Transform; return instance.customParameters.size(); } diff --git a/cucumber_cpp/library/test/TestSteps.cpp b/cucumber_cpp/library/test/TestSteps.cpp index 61ef0323..b5d90e74 100644 --- a/cucumber_cpp/library/test/TestSteps.cpp +++ b/cucumber_cpp/library/test/TestSteps.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/Query.hpp" +#include "cucumber_cpp/library/StepRegistry.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" #include "gtest/gtest.h" @@ -14,7 +15,7 @@ namespace cucumber_cpp::library { struct TestSteps : testing::Test { - cucumber_expression::ParameterRegistry parameterRegistry{}; + cucumber_expression::ParameterRegistry parameterRegistry{ {} }; StepRegistry stepRegistry{ parameterRegistry }; }; @@ -43,12 +44,12 @@ namespace cucumber_cpp::library TEST_F(TestSteps, GetInvalidStep) { - EXPECT_THROW((void)stepRegistry.Query("This step does not exist"), StepRegistry::StepNotFoundError); + 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"), StepRegistry::AmbiguousStepError); + EXPECT_THROW((void)stepRegistry.Query("an ambiguous step"), support::StepRegistry::AmbiguousStepError); } TEST_F(TestSteps, InvokeTestWithCucumberExpressions) From 7ab4c0e32225a330024d16c4caee14a80f0b3853 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 9 Jan 2026 15:07:05 +0000 Subject: [PATCH 112/196] Refactor Query and Formatter Integration - Updated include paths for Query to use the new query structure. - Modified Formatter and related classes to accept the new query type. - Added test directories and dummy test files for api, assemble, formatter, query, runtime, and util components. - Enhanced CMakeLists.txt files to include test executables and link necessary libraries. - Introduced a new Query class to encapsulate query-related functionalities and improve code organization. - Ensured all references to the old Query class are replaced with the new structure across the codebase. --- cucumber_cpp/library/CMakeLists.txt | 11 +++++------ cucumber_cpp/library/api/CMakeLists.txt | 6 +++++- cucumber_cpp/library/api/Formatters.cpp | 4 ++-- cucumber_cpp/library/api/Formatters.hpp | 8 ++++---- cucumber_cpp/library/api/RunCucumber.cpp | 5 +++-- cucumber_cpp/library/api/test/CMakeLists.txt | 12 ++++++++++++ cucumber_cpp/library/api/test/TestDummy.cpp | 8 ++++++++ cucumber_cpp/library/assemble/CMakeLists.txt | 4 +++- .../library/assemble/test/CMakeLists.txt | 12 ++++++++++++ .../library/assemble/test/TestDummy.cpp | 8 ++++++++ cucumber_cpp/library/formatter/CMakeLists.txt | 7 +++++-- cucumber_cpp/library/formatter/Formatter.cpp | 4 ++-- cucumber_cpp/library/formatter/Formatter.hpp | 6 +++--- .../library/formatter/PrettyPrinter.hpp | 2 +- .../library/formatter/test/CMakeLists.txt | 12 ++++++++++++ .../library/formatter/test/TestDummy.cpp | 8 ++++++++ cucumber_cpp/library/query/CMakeLists.txt | 19 +++++++++++++++++++ cucumber_cpp/library/{ => query}/Query.cpp | 5 ++--- cucumber_cpp/library/{ => query}/Query.hpp | 3 +-- .../library/query/test/CMakeLists.txt | 12 ++++++++++++ cucumber_cpp/library/query/test/TestDummy.cpp | 8 ++++++++ cucumber_cpp/library/runtime/CMakeLists.txt | 5 ++++- .../library/runtime/test/CMakeLists.txt | 12 ++++++++++++ .../library/runtime/test/TestDummy.cpp | 8 ++++++++ cucumber_cpp/library/test/TestSteps.cpp | 2 +- cucumber_cpp/library/util/CMakeLists.txt | 5 +++++ cucumber_cpp/library/util/test/CMakeLists.txt | 12 ++++++++++++ cucumber_cpp/library/util/test/TestDummy.cpp | 8 ++++++++ 28 files changed, 185 insertions(+), 31 deletions(-) create mode 100644 cucumber_cpp/library/api/test/CMakeLists.txt create mode 100644 cucumber_cpp/library/api/test/TestDummy.cpp create mode 100644 cucumber_cpp/library/assemble/test/CMakeLists.txt create mode 100644 cucumber_cpp/library/assemble/test/TestDummy.cpp create mode 100644 cucumber_cpp/library/formatter/test/CMakeLists.txt create mode 100644 cucumber_cpp/library/formatter/test/TestDummy.cpp create mode 100644 cucumber_cpp/library/query/CMakeLists.txt rename cucumber_cpp/library/{ => query}/Query.cpp (99%) rename cucumber_cpp/library/{ => query}/Query.hpp (99%) create mode 100644 cucumber_cpp/library/query/test/CMakeLists.txt create mode 100644 cucumber_cpp/library/query/test/TestDummy.cpp create mode 100644 cucumber_cpp/library/runtime/test/CMakeLists.txt create mode 100644 cucumber_cpp/library/runtime/test/TestDummy.cpp create mode 100644 cucumber_cpp/library/util/test/CMakeLists.txt create mode 100644 cucumber_cpp/library/util/test/TestDummy.cpp diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index 964dbd68..ece95257 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -10,8 +10,6 @@ target_sources(cucumber_cpp.library PRIVATE Errors.hpp Hooks.hpp Parameter.hpp - Query.cpp - Query.hpp Rtrim.cpp Rtrim.hpp Steps.hpp @@ -42,14 +40,15 @@ target_compile_options(cucumber_cpp.library $<$:/Zc:preprocessor> ) -add_subdirectory(assemble) add_subdirectory(api) -add_subdirectory(support) -add_subdirectory(runtime) +add_subdirectory(assemble) add_subdirectory(cucumber_expression) +add_subdirectory(engine) add_subdirectory(formatter) +add_subdirectory(query) +add_subdirectory(runtime) +add_subdirectory(support) add_subdirectory(tag_expression) -add_subdirectory(engine) add_subdirectory(util) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/api/CMakeLists.txt b/cucumber_cpp/library/api/CMakeLists.txt index cdf9d4ee..08282f68 100644 --- a/cucumber_cpp/library/api/CMakeLists.txt +++ b/cucumber_cpp/library/api/CMakeLists.txt @@ -15,9 +15,13 @@ target_include_directories(cucumber_cpp.library.api PUBLIC target_link_libraries(cucumber_cpp.library.api PUBLIC cucumber_cpp.library.cucumber_expression + cucumber_cpp.library.formatter + cucumber_cpp.library.runtime + cucumber_cpp.library.support + cucumber_cpp.library.util ) if (CCR_BUILD_TESTS) - # add_subdirectory(test) + add_subdirectory(test) # add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index e9be4628..ba854f4b 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -1,9 +1,9 @@ #include "cucumber_cpp/library/api/Formatters.hpp" -#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "nlohmann/json_fwd.hpp" #include @@ -43,7 +43,7 @@ namespace cucumber_cpp::library::api return { view.begin(), view.end() }; } - std::list> Formatters::EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) + std::list> Formatters::EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) { std::list> activeFormatters; diff --git a/cucumber_cpp/library/api/Formatters.hpp b/cucumber_cpp/library/api/Formatters.hpp index 6588bc56..1ade2382 100644 --- a/cucumber_cpp/library/api/Formatters.hpp +++ b/cucumber_cpp/library/api/Formatters.hpp @@ -1,9 +1,9 @@ #ifndef API_FORMATTERS_HPP #define API_FORMATTERS_HPP -#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "nlohmann/json_fwd.hpp" #include @@ -29,7 +29,7 @@ namespace cucumber_cpp::library::api struct RegisteredFormatter { - std::function(support::SupportCodeLibrary&, Query&, const formatter::helper::EventDataCollector&, const nlohmann::json& formatOptions, std::ostream&)> factory; + std::function(support::SupportCodeLibrary&, query::Query&, const formatter::helper::EventDataCollector&, const nlohmann::json& formatOptions, std::ostream&)> factory; bool hasOutput{ false }; }; @@ -42,7 +42,7 @@ namespace cucumber_cpp::library::api std::set> GetAvailableFormatterNames() const; - [[nodiscard]] std::list> EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); + [[nodiscard]] std::list> EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); private: std::map> availableFormatters; @@ -56,7 +56,7 @@ namespace cucumber_cpp::library::api template void Formatters::RegisterFormatter(bool hasOutput) { - availableFormatters.try_emplace(T::name, [](support::SupportCodeLibrary& supportCodeLibrary, Query& query, const formatter::helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& output) + availableFormatters.try_emplace(T::name, [](support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const formatter::helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& output) { return std::make_unique(supportCodeLibrary, query, eventDataCollector, formatOptions, output); }, diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 88b9a90f..3908dc27 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -5,11 +5,12 @@ #include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/step_definition_pattern.hpp" -#include "cucumber_cpp/library/Query.hpp" +#include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/Gherkin.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/runtime/MakeRuntime.hpp" #include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" @@ -184,7 +185,7 @@ namespace cucumber_cpp::library::api }; formatter::helper::EventDataCollector eventDataCollector{ broadcaster }; - Query query{ broadcaster }; + query::Query query{ broadcaster }; const auto formatOptionsJson = formatOptions.empty() ? nlohmann::json::object() : nlohmann::json::parse(formatOptions); const auto activeFormatters = formatters.EnableFormatters(format, formatOptionsJson, supportCodeLibrary, query, eventDataCollector); diff --git a/cucumber_cpp/library/api/test/CMakeLists.txt b/cucumber_cpp/library/api/test/CMakeLists.txt new file mode 100644 index 00000000..6f6e4700 --- /dev/null +++ b/cucumber_cpp/library/api/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(cucumber_cpp.library.api.test) +add_test(NAME cucumber_cpp.library.api.test COMMAND cucumber_cpp.library.api.test) + +target_link_libraries(cucumber_cpp.library.api.test PUBLIC + gmock_main + cucumber_cpp.library.api + GTest::gmock +) + +target_sources(cucumber_cpp.library.api.test PRIVATE + TestDummy.cpp +) diff --git a/cucumber_cpp/library/api/test/TestDummy.cpp b/cucumber_cpp/library/api/test/TestDummy.cpp new file mode 100644 index 00000000..c81abaf1 --- /dev/null +++ b/cucumber_cpp/library/api/test/TestDummy.cpp @@ -0,0 +1,8 @@ +#include + +namespace cucumber_cpp::library::support +{ + TEST(Dummy, dummy) + { + } +} diff --git a/cucumber_cpp/library/assemble/CMakeLists.txt b/cucumber_cpp/library/assemble/CMakeLists.txt index 296aaf60..1c038f88 100644 --- a/cucumber_cpp/library/assemble/CMakeLists.txt +++ b/cucumber_cpp/library/assemble/CMakeLists.txt @@ -13,9 +13,11 @@ target_include_directories(cucumber_cpp.library.assemble PUBLIC target_link_libraries(cucumber_cpp.library.assemble PUBLIC cucumber_cpp.library.cucumber_expression + cucumber_cpp.library.support + cucumber_cpp.library.util ) if (CCR_BUILD_TESTS) - # add_subdirectory(test) + add_subdirectory(test) # add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/assemble/test/CMakeLists.txt b/cucumber_cpp/library/assemble/test/CMakeLists.txt new file mode 100644 index 00000000..d837451e --- /dev/null +++ b/cucumber_cpp/library/assemble/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(cucumber_cpp.library.assemble.test) +add_test(NAME cucumber_cpp.library.assemble.test COMMAND cucumber_cpp.library.assemble.test) + +target_link_libraries(cucumber_cpp.library.assemble.test PUBLIC + gmock_main + cucumber_cpp.library.assemble + GTest::gmock +) + +target_sources(cucumber_cpp.library.assemble.test PRIVATE + TestDummy.cpp +) diff --git a/cucumber_cpp/library/assemble/test/TestDummy.cpp b/cucumber_cpp/library/assemble/test/TestDummy.cpp new file mode 100644 index 00000000..c81abaf1 --- /dev/null +++ b/cucumber_cpp/library/assemble/test/TestDummy.cpp @@ -0,0 +1,8 @@ +#include + +namespace cucumber_cpp::library::support +{ + TEST(Dummy, dummy) + { + } +} diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index cebb4f6f..8da17c56 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -16,9 +16,12 @@ target_include_directories(cucumber_cpp.library.formatter PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter PUBLIC + cpp-terminal cucumber_cpp.library.cucumber_expression cucumber_cpp.library.formatter.helper - cpp-terminal::cpp-terminal + cucumber_cpp.library.query + cucumber_cpp.library.support + cucumber_cpp.library.util nlohmann_json ) @@ -26,6 +29,6 @@ add_subdirectory(helper) if (CCR_BUILD_TESTS) - # add_subdirectory(test) + add_subdirectory(test) # add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/formatter/Formatter.cpp b/cucumber_cpp/library/formatter/Formatter.cpp index 8dfdff72..e172a82f 100644 --- a/cucumber_cpp/library/formatter/Formatter.cpp +++ b/cucumber_cpp/library/formatter/Formatter.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber/messages/envelope.hpp" -#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "nlohmann/json_fwd.hpp" @@ -10,7 +10,7 @@ namespace cucumber_cpp::library::formatter { - Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, Query& query, const helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& outputStream) + Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& outputStream) : util::Listener{ query, [this](const cucumber::messages::envelope& envelope) { OnEnvelope(envelope); diff --git a/cucumber_cpp/library/formatter/Formatter.hpp b/cucumber_cpp/library/formatter/Formatter.hpp index 2ca0232c..83447601 100644 --- a/cucumber_cpp/library/formatter/Formatter.hpp +++ b/cucumber_cpp/library/formatter/Formatter.hpp @@ -2,8 +2,8 @@ #define FORMATTER_FORMATTER_HPP #include "cucumber/messages/envelope.hpp" -#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "nlohmann/json_fwd.hpp" @@ -16,14 +16,14 @@ namespace cucumber_cpp::library::formatter struct Formatter : util::Listener { - Formatter(support::SupportCodeLibrary& supportCodeLibrary, Query& query, const helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& outputStream = std::cout); + Formatter(support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& outputStream = std::cout); virtual ~Formatter() = default; protected: virtual void OnEnvelope(const cucumber::messages::envelope& envelope) = 0; support::SupportCodeLibrary& supportCodeLibrary; - Query& query; + query::Query& query; const helper::EventDataCollector& eventDataCollector; const nlohmann::json& formatOptions; std::ostream& outputStream; diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.hpp b/cucumber_cpp/library/formatter/PrettyPrinter.hpp index fe83e083..bf694e95 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.hpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.hpp @@ -14,9 +14,9 @@ #include "cucumber/messages/test_run_finished.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" -#include "cucumber_cpp/library/Query.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" +#include "cucumber_cpp/library/query/Query.hpp" #include #include #include diff --git a/cucumber_cpp/library/formatter/test/CMakeLists.txt b/cucumber_cpp/library/formatter/test/CMakeLists.txt new file mode 100644 index 00000000..ca355d3a --- /dev/null +++ b/cucumber_cpp/library/formatter/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(cucumber_cpp.library.formatter.test) +add_test(NAME cucumber_cpp.library.formatter.test COMMAND cucumber_cpp.library.formatter.test) + +target_link_libraries(cucumber_cpp.library.formatter.test PUBLIC + gmock_main + cucumber_cpp.library.formatter + GTest::gmock +) + +target_sources(cucumber_cpp.library.formatter.test PRIVATE + TestDummy.cpp +) diff --git a/cucumber_cpp/library/formatter/test/TestDummy.cpp b/cucumber_cpp/library/formatter/test/TestDummy.cpp new file mode 100644 index 00000000..c81abaf1 --- /dev/null +++ b/cucumber_cpp/library/formatter/test/TestDummy.cpp @@ -0,0 +1,8 @@ +#include + +namespace cucumber_cpp::library::support +{ + TEST(Dummy, dummy) + { + } +} diff --git a/cucumber_cpp/library/query/CMakeLists.txt b/cucumber_cpp/library/query/CMakeLists.txt new file mode 100644 index 00000000..ec90d8bb --- /dev/null +++ b/cucumber_cpp/library/query/CMakeLists.txt @@ -0,0 +1,19 @@ +add_library(cucumber_cpp.library.query ${CCR_EXCLUDE_FROM_ALL}) + +target_sources(cucumber_cpp.library.query PRIVATE + Query.cpp + Query.hpp +) + +target_include_directories(cucumber_cpp.library.query PUBLIC + ../../.. +) + +target_link_libraries(cucumber_cpp.library.query PUBLIC + cucumber_gherkin_lib + cucumber_cpp.library.util +) + +if (CCR_BUILD_TESTS) + add_subdirectory(test) +endif() diff --git a/cucumber_cpp/library/Query.cpp b/cucumber_cpp/library/query/Query.cpp similarity index 99% rename from cucumber_cpp/library/Query.cpp rename to cucumber_cpp/library/query/Query.cpp index 2f1741ea..cd10dd5d 100644 --- a/cucumber_cpp/library/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -1,4 +1,4 @@ -#include "cucumber_cpp/library/Query.hpp" +#include "cucumber_cpp/library/query/Query.hpp" #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/background.hpp" #include "cucumber/messages/envelope.hpp" @@ -29,7 +29,6 @@ #include "cucumber/messages/test_step_started.hpp" #include "cucumber/messages/undefined_parameter_type.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include #include #include #include @@ -38,7 +37,7 @@ #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::query { std::string Lineage::GetUniqueFeatureName() const { diff --git a/cucumber_cpp/library/Query.hpp b/cucumber_cpp/library/query/Query.hpp similarity index 99% rename from cucumber_cpp/library/Query.hpp rename to cucumber_cpp/library/query/Query.hpp index 100f1a19..351fd3aa 100644 --- a/cucumber_cpp/library/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -35,7 +35,6 @@ #include #include #include -#include #include #include #include @@ -44,7 +43,7 @@ #include #include -namespace cucumber_cpp::library +namespace cucumber_cpp::library::query { struct Lineage { diff --git a/cucumber_cpp/library/query/test/CMakeLists.txt b/cucumber_cpp/library/query/test/CMakeLists.txt new file mode 100644 index 00000000..70c5cc12 --- /dev/null +++ b/cucumber_cpp/library/query/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(cucumber_cpp.library.query.test) +add_test(NAME cucumber_cpp.library.query.test COMMAND cucumber_cpp.library.query.test) + +target_link_libraries(cucumber_cpp.library.query.test PUBLIC + gmock_main + cucumber_cpp.library.query + GTest::gmock +) + +target_sources(cucumber_cpp.library.query.test PRIVATE + TestDummy.cpp +) diff --git a/cucumber_cpp/library/query/test/TestDummy.cpp b/cucumber_cpp/library/query/test/TestDummy.cpp new file mode 100644 index 00000000..c81abaf1 --- /dev/null +++ b/cucumber_cpp/library/query/test/TestDummy.cpp @@ -0,0 +1,8 @@ +#include + +namespace cucumber_cpp::library::support +{ + TEST(Dummy, dummy) + { + } +} diff --git a/cucumber_cpp/library/runtime/CMakeLists.txt b/cucumber_cpp/library/runtime/CMakeLists.txt index e55ab6f5..e2a902a5 100644 --- a/cucumber_cpp/library/runtime/CMakeLists.txt +++ b/cucumber_cpp/library/runtime/CMakeLists.txt @@ -18,10 +18,13 @@ target_include_directories(cucumber_cpp.library.runtime PUBLIC ) target_link_libraries(cucumber_cpp.library.runtime PUBLIC + cucumber_cpp.library.assemble cucumber_cpp.library.cucumber_expression + cucumber_cpp.library.support + cucumber_cpp.library.util ) if (CCR_BUILD_TESTS) - # add_subdirectory(test) + add_subdirectory(test) # add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/runtime/test/CMakeLists.txt b/cucumber_cpp/library/runtime/test/CMakeLists.txt new file mode 100644 index 00000000..b1c57c9d --- /dev/null +++ b/cucumber_cpp/library/runtime/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(cucumber_cpp.library.runtime.test) +add_test(NAME cucumber_cpp.library.runtime.test COMMAND cucumber_cpp.library.runtime.test) + +target_link_libraries(cucumber_cpp.library.runtime.test PUBLIC + gmock_main + cucumber_cpp.library.runtime + GTest::gmock +) + +target_sources(cucumber_cpp.library.runtime.test PRIVATE + TestDummy.cpp +) diff --git a/cucumber_cpp/library/runtime/test/TestDummy.cpp b/cucumber_cpp/library/runtime/test/TestDummy.cpp new file mode 100644 index 00000000..c81abaf1 --- /dev/null +++ b/cucumber_cpp/library/runtime/test/TestDummy.cpp @@ -0,0 +1,8 @@ +#include + +namespace cucumber_cpp::library::support +{ + TEST(Dummy, dummy) + { + } +} diff --git a/cucumber_cpp/library/test/TestSteps.cpp b/cucumber_cpp/library/test/TestSteps.cpp index b5d90e74..4a3c8ea3 100644 --- a/cucumber_cpp/library/test/TestSteps.cpp +++ b/cucumber_cpp/library/test/TestSteps.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/Query.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 diff --git a/cucumber_cpp/library/util/CMakeLists.txt b/cucumber_cpp/library/util/CMakeLists.txt index d2053a5b..b3ff55cc 100644 --- a/cucumber_cpp/library/util/CMakeLists.txt +++ b/cucumber_cpp/library/util/CMakeLists.txt @@ -15,3 +15,8 @@ target_include_directories(cucumber_cpp.library.util PUBLIC target_link_libraries(cucumber_cpp.library.util PUBLIC cucumber_gherkin_lib ) + +if (CCR_BUILD_TESTS) + add_subdirectory(test) + # add_subdirectory(test_helper) +endif() diff --git a/cucumber_cpp/library/util/test/CMakeLists.txt b/cucumber_cpp/library/util/test/CMakeLists.txt new file mode 100644 index 00000000..0e714f3c --- /dev/null +++ b/cucumber_cpp/library/util/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(cucumber_cpp.library.util.test) +add_test(NAME cucumber_cpp.library.util.test COMMAND cucumber_cpp.library.util.test) + +target_link_libraries(cucumber_cpp.library.util.test PUBLIC + gmock_main + cucumber_cpp.library.util + GTest::gmock +) + +target_sources(cucumber_cpp.library.util.test PRIVATE + TestDummy.cpp +) diff --git a/cucumber_cpp/library/util/test/TestDummy.cpp b/cucumber_cpp/library/util/test/TestDummy.cpp new file mode 100644 index 00000000..c81abaf1 --- /dev/null +++ b/cucumber_cpp/library/util/test/TestDummy.cpp @@ -0,0 +1,8 @@ +#include + +namespace cucumber_cpp::library::support +{ + TEST(Dummy, dummy) + { + } +} From 3af273c4ad18533eac2bdf4b66d0e9d97c40a6ae Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 9 Jan 2026 15:22:59 +0000 Subject: [PATCH 113/196] refactor: reorganize formatter library to use helper functions and improve structure --- cucumber_cpp/library/formatter/CMakeLists.txt | 2 -- cucumber_cpp/library/formatter/PrettyPrinter.cpp | 8 ++++---- cucumber_cpp/library/formatter/SummaryFormatter.cpp | 9 ++++----- cucumber_cpp/library/formatter/SummaryFormatter.hpp | 4 ++-- cucumber_cpp/library/formatter/helper/CMakeLists.txt | 6 +++++- .../formatter/{ => helper}/GetColorFunctions.cpp | 4 ++-- .../formatter/{ => helper}/GetColorFunctions.hpp | 2 +- .../library/formatter/helper/SummaryHelpers.cpp | 2 +- .../formatter/helper/TestCaseAttemptFormatter.cpp | 2 +- .../library/formatter/helper/test/CMakeLists.txt | 12 ++++++++++++ .../library/formatter/helper/test/TestDummy.cpp | 8 ++++++++ 11 files changed, 40 insertions(+), 19 deletions(-) rename cucumber_cpp/library/formatter/{ => helper}/GetColorFunctions.cpp (94%) rename cucumber_cpp/library/formatter/{ => helper}/GetColorFunctions.hpp (93%) create mode 100644 cucumber_cpp/library/formatter/helper/test/CMakeLists.txt create mode 100644 cucumber_cpp/library/formatter/helper/test/TestDummy.cpp diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index 8da17c56..3ce3dd9e 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -3,8 +3,6 @@ add_library(cucumber_cpp.library.formatter ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.formatter PRIVATE Formatter.cpp Formatter.hpp - GetColorFunctions.hpp - GetColorFunctions.cpp PrettyPrinter.cpp PrettyPrinter.hpp SummaryFormatter.cpp diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyPrinter.cpp index c2e7b0b1..2283b45d 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.cpp +++ b/cucumber_cpp/library/formatter/PrettyPrinter.cpp @@ -14,7 +14,7 @@ #include "cucumber/messages/test_run_finished.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" -#include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" +#include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" #include "cucumber_cpp/library/support/Join.hpp" #include "cucumber_cpp/library/support/Polyfill.hpp" #include @@ -166,7 +166,7 @@ namespace cucumber_cpp::library::formatter return tag.name; }); std::vector tagVec{ tags.begin(), tags.end() }; - support::print(outputStream, "{:{}}{}\n", "", scenarioIndent, ColorFunctions::Tag(support::Join(tagVec, " "))); + support::print(outputStream, "{:{}}{}\n", "", scenarioIndent, helper::ColorFunctions::Tag(support::Join(tagVec, " "))); } void PrettyPrinter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) @@ -179,7 +179,7 @@ namespace cucumber_cpp::library::formatter const auto uri = stepDefinition ? std::make_optional(*stepDefinition->source_reference.uri) : std::nullopt; const auto line = stepDefinition ? std::make_optional(stepDefinition->source_reference.location->line) : std::nullopt; - PrintGherkinLine(std::format("{}{}", step.keyword, pickleStep.text), ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); + PrintGherkinLine(std::format("{}{}", step.keyword, pickleStep.text), helper::ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); } void PrettyPrinter::PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) @@ -196,7 +196,7 @@ namespace cucumber_cpp::library::formatter }; if (uri.has_value() && line.has_value()) - support::print(outputStream, "{:{}}{}{:{}} {}\n", "", indent, formatTitle(title), "", padding, ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); + support::print(outputStream, "{:{}}{}{:{}} {}\n", "", indent, formatTitle(title), "", padding, helper::ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); else support::print(outputStream, "{:{}}{}\n", "", indent, formatTitle(title)); } diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index ad38719b..2374c36b 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -7,13 +7,12 @@ #include "cucumber_cpp/library/formatter/helper/IssueHelpers.hpp" #include "cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp" #include "cucumber_cpp/library/support/Polyfill.hpp" -#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Timestamp.hpp" #include #include +#include #include #include -#include namespace cucumber_cpp::library::formatter { @@ -51,8 +50,8 @@ namespace cucumber_cpp::library::formatter void SummaryFormatter::LogSummary(const cucumber::messages::duration& testRunDuration) { - std::vector failures{}; - std::vector warnings{}; + std::list failures{}; + std::list warnings{}; const auto attempts = eventDataCollector.GetTestCaseAttempts(); for (const auto& attempt : attempts) @@ -70,7 +69,7 @@ namespace cucumber_cpp::library::formatter outputStream << helper::FormatSummary(attempts, testRunDuration); } - void SummaryFormatter::LogIssues(std::span attempts, std::string_view title) + void SummaryFormatter::LogIssues(const std::list& attempts, std::string_view title) { if (!attempts.empty()) { diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.hpp b/cucumber_cpp/library/formatter/SummaryFormatter.hpp index 049147c7..795f8a74 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.hpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.hpp @@ -6,7 +6,7 @@ #include "cucumber/messages/timestamp.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include +#include #include namespace cucumber_cpp::library::formatter @@ -21,7 +21,7 @@ namespace cucumber_cpp::library::formatter private: void OnEnvelope(const cucumber::messages::envelope& envelope) override; void LogSummary(const cucumber::messages::duration& testRunDuration); - void LogIssues(std::span attempts, std::string_view title); + void LogIssues(const std::list& attempts, std::string_view title); cucumber::messages::timestamp testRunStartedAt{}; }; diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index 51a29eae..1f89b48d 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -3,6 +3,8 @@ add_library(cucumber_cpp.library.formatter.helper ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.formatter.helper PRIVATE EventDataCollector.cpp EventDataCollector.hpp + GetColorFunctions.hpp + GetColorFunctions.cpp GherkinDocumentParser.cpp GherkinDocumentParser.hpp IndentString.cpp @@ -28,10 +30,12 @@ target_include_directories(cucumber_cpp.library.formatter.helper PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter.helper PUBLIC + cpp-terminal cucumber_cpp.library.cucumber_expression + cucumber_cpp.library.support ) if (CCR_BUILD_TESTS) - # add_subdirectory(test) + add_subdirectory(test) # add_subdirectory(test_helper) endif() diff --git a/cucumber_cpp/library/formatter/GetColorFunctions.cpp b/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp similarity index 94% rename from cucumber_cpp/library/formatter/GetColorFunctions.cpp rename to cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp index 307b2bc6..bdaec299 100644 --- a/cucumber_cpp/library/formatter/GetColorFunctions.cpp +++ b/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp @@ -1,5 +1,5 @@ -#include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" +#include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" #include "cpp-terminal/color.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include @@ -7,7 +7,7 @@ #include #include -namespace cucumber_cpp::library::formatter +namespace cucumber_cpp::library::formatter::helper { namespace { diff --git a/cucumber_cpp/library/formatter/GetColorFunctions.hpp b/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp similarity index 93% rename from cucumber_cpp/library/formatter/GetColorFunctions.hpp rename to cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp index 3d498484..ce8cd4df 100644 --- a/cucumber_cpp/library/formatter/GetColorFunctions.hpp +++ b/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp @@ -6,7 +6,7 @@ #include #include -namespace cucumber_cpp::library::formatter +namespace cucumber_cpp::library::formatter::helper { struct ColorFunctions { diff --git a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp index 009b654d..111d5597 100644 --- a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp +++ b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp @@ -3,8 +3,8 @@ #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" #include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/Join.hpp" #include diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp index f0ddde1a..7007d3e0 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/formatter/GetColorFunctions.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" #include "cucumber_cpp/library/formatter/helper/IndentString.hpp" #include "cucumber_cpp/library/formatter/helper/LocationHelpers.hpp" #include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" diff --git a/cucumber_cpp/library/formatter/helper/test/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/test/CMakeLists.txt new file mode 100644 index 00000000..7dfec4b3 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/test/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(cucumber_cpp.library.formatter.helper.test) +add_test(NAME cucumber_cpp.library.formatter.helper.test COMMAND cucumber_cpp.library.formatter.helper.test) + +target_link_libraries(cucumber_cpp.library.formatter.helper.test PUBLIC + gmock_main + cucumber_cpp.library.formatter.helper + GTest::gmock +) + +target_sources(cucumber_cpp.library.formatter.helper.test PRIVATE + TestDummy.cpp +) diff --git a/cucumber_cpp/library/formatter/helper/test/TestDummy.cpp b/cucumber_cpp/library/formatter/helper/test/TestDummy.cpp new file mode 100644 index 00000000..c81abaf1 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/test/TestDummy.cpp @@ -0,0 +1,8 @@ +#include + +namespace cucumber_cpp::library::support +{ + TEST(Dummy, dummy) + { + } +} From 56a2de7c869439c2b548c856ca5f705729d6acaa Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 13 Jan 2026 09:58:34 +0000 Subject: [PATCH 114/196] treat warnings as errors only --- CMakeLists.txt | 8 ++++++-- cucumber_cpp/CMakeLists.txt | 7 +++++-- cucumber_cpp/library/CMakeLists.txt | 2 -- cucumber_cpp/library/tag_expression/CMakeLists.txt | 2 -- 4 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index fb6a6387..edea2db3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,6 +12,7 @@ endif() if (CCR_STANDALONE) set(CCR_DEFAULTOPT On) set(CCR_EXCLUDE_FROM_ALL "") + set(CMAKE_COMPILE_WARNING_AS_ERROR On) else() set(CCR_DEFAULTOPT Off) set(CCR_EXCLUDE_FROM_ALL "EXCLUDE_FROM_ALL") @@ -59,7 +60,7 @@ include(CTest) include(GoogleTest) if (CCR_FETCH_DEPS) - add_subdirectory(external) + add_subdirectory(external SYSTEM) else() find_package(CLI11 REQUIRED) find_package(nlohmann_json REQUIRED) @@ -71,4 +72,7 @@ else() endif() add_subdirectory(cucumber_cpp) -add_subdirectory(compatibility) + +if (CCR_STANDALONE) + add_subdirectory(compatibility) +endif() diff --git a/cucumber_cpp/CMakeLists.txt b/cucumber_cpp/CMakeLists.txt index d6b5a532..6d6c51c0 100644 --- a/cucumber_cpp/CMakeLists.txt +++ b/cucumber_cpp/CMakeLists.txt @@ -1,7 +1,10 @@ add_subdirectory(library) add_subdirectory(runner) -add_subdirectory(example) -add_subdirectory(acceptance_test) + +if (CCR_STANDALONE) + add_subdirectory(example) + add_subdirectory(acceptance_test) +endif() add_library(cucumber_cpp INTERFACE) diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index ece95257..84f4e8ac 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -1,5 +1,3 @@ -set(CMAKE_COMPILE_WARNING_AS_ERROR On) - add_library(cucumber_cpp.library ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library PRIVATE diff --git a/cucumber_cpp/library/tag_expression/CMakeLists.txt b/cucumber_cpp/library/tag_expression/CMakeLists.txt index 47076a8d..01acf468 100644 --- a/cucumber_cpp/library/tag_expression/CMakeLists.txt +++ b/cucumber_cpp/library/tag_expression/CMakeLists.txt @@ -1,5 +1,3 @@ -set(CMAKE_COMPILE_WARNING_AS_ERROR On) - add_library(cucumber_cpp.library.tag_expression ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.tag_expression PRIVATE From e167a273b6697ff91c5461016588dba03d01ef6c Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 13 Jan 2026 16:00:29 +0000 Subject: [PATCH 115/196] refactor: move address sanitizer options under CCR_STANDALONE condition --- CMakeLists.txt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index edea2db3..4243be77 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,9 +24,11 @@ option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements set(CMAKE_POSITION_INDEPENDENT_CODE ON) -if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) +if (CCR_STANDALONE) + if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) + add_compile_options(-fsanitize=address) + add_link_options(-fsanitize=address) + endif() endif() if (CCR_BUILD_TESTS) From e7c416a9cf8998feeea53cfea4b2d50908a87b62 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 13 Jan 2026 22:43:50 +0000 Subject: [PATCH 116/196] refactor: add CCR_STANDALONE condition for compilation and logging in SupportCodeLibrary --- cucumber_cpp/library/support/CMakeLists.txt | 6 ++++++ cucumber_cpp/library/support/SupportCodeLibrary.cpp | 2 ++ 2 files changed, 8 insertions(+) diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index d65d9877..73fd046d 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -1,5 +1,11 @@ add_library(cucumber_cpp.library.support ${CCR_EXCLUDE_FROM_ALL}) +if (CCR_STANDALONE) + target_compile_definitions(cucumber_cpp.library.support PRIVATE + CCR_STANDALONE + ) +endif() + target_sources(cucumber_cpp.library.support PRIVATE Body.cpp Body.hpp diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index 213f1df6..f8bf66e4 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -62,10 +62,12 @@ namespace cucumber_cpp::library::support { void PrintContents(std::string_view type, std::source_location sourceLocation, const std::map& registry) { +#if defined(CCR_STANDALONE) std::cout << std::format("Added ({}): {}:{}\n", type, sourceLocation.file_name(), sourceLocation.line()); std::cout << "Registry contents:\n"; for (const auto& [key, item] : registry) std::cout << std::format(" {}:{}\n", key.file_name(), key.line()); +#endif } } From 8e9835a94c255087a873dcb03749521b71373387 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 13 Jan 2026 23:05:16 +0000 Subject: [PATCH 117/196] refactor: reorganize GoogleTest integration and remove redundant includes --- CMakeLists.txt | 1 - compatibility/CMakeLists.txt | 1 - external/CMakeLists.txt | 2 +- external/google/CMakeLists.txt | 1 + external/google/googletest/CMakeLists.txt | 16 ++++++++++++++++ external/googletest/CMakeLists.txt | 15 --------------- 6 files changed, 18 insertions(+), 18 deletions(-) create mode 100644 external/google/CMakeLists.txt create mode 100644 external/google/googletest/CMakeLists.txt delete mode 100644 external/googletest/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 4243be77..b3463d93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -59,7 +59,6 @@ set_directory_properties(PROPERTY USE_FOLDERS ON) include(FetchContent) include(GNUInstallDirs) include(CTest) -include(GoogleTest) if (CCR_FETCH_DEPS) add_subdirectory(external SYSTEM) diff --git a/compatibility/CMakeLists.txt b/compatibility/CMakeLists.txt index 11237dbd..b0258437 100644 --- a/compatibility/CMakeLists.txt +++ b/compatibility/CMakeLists.txt @@ -42,7 +42,6 @@ endfunction() file(GLOB kits RELATIVE ${CMAKE_CURRENT_LIST_DIR} ${CMAKE_CURRENT_LIST_DIR}/*) foreach(kit ${kits}) if (IS_DIRECTORY ${CMAKE_CURRENT_LIST_DIR}/${kit}) - message(STATUS "Adding compatibility kit: ${kit}") add_compatibility_kit(${kit}) endif() endforeach() diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 86bda483..92d015da 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -4,7 +4,7 @@ add_subdirectory(nlohmann) # before cucumber add_subdirectory(cliutils) add_subdirectory(cucumber) -add_subdirectory(googletest) +add_subdirectory(google) add_subdirectory(jbeder) add_subdirectory(jupyter-xeus) add_subdirectory(tobiaslocker) diff --git a/external/google/CMakeLists.txt b/external/google/CMakeLists.txt new file mode 100644 index 00000000..73deebc3 --- /dev/null +++ b/external/google/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(googletest) diff --git a/external/google/googletest/CMakeLists.txt b/external/google/googletest/CMakeLists.txt new file mode 100644 index 00000000..7a813011 --- /dev/null +++ b/external/google/googletest/CMakeLists.txt @@ -0,0 +1,16 @@ +if(NOT TARGET gtest) + FetchContent_Declare( + googletest + GIT_REPOSITORY https://github.com/google/googletest + GIT_TAG v1.14.0 + FIND_PACKAGE_ARGS NAMES GTest gtest GMock gmock + ) + + set(gtest_force_shared_crt On CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings + set(INSTALL_GTEST Off CACHE BOOL "" FORCE) + + FetchContent_MakeAvailable(googletest) + + set_target_properties(gtest gtest_main gmock gmock_main PROPERTIES FOLDER External/GoogleTest) + mark_as_advanced(BUILD_GMOCK BUILD_GTEST BUILD_SHARED_LIBS gmock_build_tests gtest_build_samples test_build_tests gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols) +endif() diff --git a/external/googletest/CMakeLists.txt b/external/googletest/CMakeLists.txt deleted file mode 100644 index 853e49be..00000000 --- a/external/googletest/CMakeLists.txt +++ /dev/null @@ -1,15 +0,0 @@ - -include(FetchContent) -FetchContent_Declare( - googletest - GIT_REPOSITORY https://github.com/google/googletest - GIT_TAG v1.14.0 -) - -set(gtest_force_shared_crt On CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings -set(INSTALL_GTEST Off CACHE BOOL "" FORCE) - -FetchContent_MakeAvailable(googletest) - -set_target_properties(gtest gtest_main gmock gmock_main PROPERTIES FOLDER External/GoogleTest) -mark_as_advanced(BUILD_GMOCK BUILD_GTEST BUILD_SHARED_LIBS gmock_build_tests gtest_build_samples test_build_tests gtest_disable_pthreads gtest_force_shared_crt gtest_hide_internal_symbols) From de7fd17ac893d6e6b1f9fa70a1e13459668c1718 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 14 Jan 2026 07:50:17 +0000 Subject: [PATCH 118/196] refactor: add SYSTEM option to FetchContent_Declare for multiple dependencies --- external/CMakeLists.txt | 1 + external/cliutils/cli11/CMakeLists.txt | 2 ++ external/cucumber/gherkin/CMakeLists.txt | 2 ++ external/cucumber/messages/CMakeLists.txt | 1 + external/google/googletest/CMakeLists.txt | 4 +++- external/jbeder/yaml-cpp/CMakeLists.txt | 8 +++++--- external/jupyter-xeus/cpp-terminal/CMakeLists.txt | 2 ++ external/nlohmann/json/CMakeLists.txt | 1 + external/zeux/pugixml/CMakeLists.txt | 2 ++ 9 files changed, 19 insertions(+), 4 deletions(-) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 92d015da..86248804 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,4 +1,5 @@ set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE "") +set(CMAKE_COMPILE_WARNING_AS_ERROR Off) add_subdirectory(nlohmann) # before cucumber diff --git a/external/cliutils/cli11/CMakeLists.txt b/external/cliutils/cli11/CMakeLists.txt index 51ba3070..92d17734 100644 --- a/external/cliutils/cli11/CMakeLists.txt +++ b/external/cliutils/cli11/CMakeLists.txt @@ -2,6 +2,8 @@ FetchContent_Declare( cli11 GIT_REPOSITORY https://github.com/CLIUtils/CLI11 GIT_TAG f4d0731cebb123ff0ace712c099dffbcd2c58e5a # v2.4.1 + + SYSTEM ) set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) diff --git a/external/cucumber/gherkin/CMakeLists.txt b/external/cucumber/gherkin/CMakeLists.txt index 10c9a94f..f47d71ad 100644 --- a/external/cucumber/gherkin/CMakeLists.txt +++ b/external/cucumber/gherkin/CMakeLists.txt @@ -1,6 +1,8 @@ FetchContent_Declare(cucumber_gherkin GIT_REPOSITORY https://github.com/cucumber/gherkin.git GIT_TAG "v37.0.0" + + SYSTEM ) FetchContent_MakeAvailable(cucumber_gherkin) diff --git a/external/cucumber/messages/CMakeLists.txt b/external/cucumber/messages/CMakeLists.txt index b645a475..d628618b 100644 --- a/external/cucumber/messages/CMakeLists.txt +++ b/external/cucumber/messages/CMakeLists.txt @@ -2,6 +2,7 @@ FetchContent_Declare(cucumber_messages GIT_REPOSITORY https://github.com/cucumber/messages.git GIT_TAG "v31.0.0" + SYSTEM OVERRIDE_FIND_PACKAGE ) diff --git a/external/google/googletest/CMakeLists.txt b/external/google/googletest/CMakeLists.txt index 7a813011..7a8502c0 100644 --- a/external/google/googletest/CMakeLists.txt +++ b/external/google/googletest/CMakeLists.txt @@ -3,7 +3,9 @@ if(NOT TARGET gtest) googletest GIT_REPOSITORY https://github.com/google/googletest GIT_TAG v1.14.0 - FIND_PACKAGE_ARGS NAMES GTest gtest GMock gmock + + SYSTEM + FIND_PACKAGE_ARGS NAMES GTest ) set(gtest_force_shared_crt On CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings diff --git a/external/jbeder/yaml-cpp/CMakeLists.txt b/external/jbeder/yaml-cpp/CMakeLists.txt index 5de41095..3502382f 100644 --- a/external/jbeder/yaml-cpp/CMakeLists.txt +++ b/external/jbeder/yaml-cpp/CMakeLists.txt @@ -1,7 +1,9 @@ FetchContent_Declare( - yaml-cpp - GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git - GIT_TAG 2f86d13775d119edbb69af52e5f566fd65c6953b # Unreleased + yaml-cpp + GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git + GIT_TAG 2f86d13775d119edbb69af52e5f566fd65c6953b # Unreleased + + SYSTEM ) set(YAML_ENABLE_PIC OFF CACHE STRING "") diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt index 5855aa30..c8d3f930 100644 --- a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt +++ b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt @@ -2,6 +2,8 @@ FetchContent_Declare( cpp-terminal GIT_REPOSITORY https://github.com/jupyter-xeus/cpp-terminal GIT_TAG 48ae2f284084850901c45b6c10a9d68949c1b272 # unreleased main + + SYSTEM ) set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) diff --git a/external/nlohmann/json/CMakeLists.txt b/external/nlohmann/json/CMakeLists.txt index 0c2c6973..515cfcae 100644 --- a/external/nlohmann/json/CMakeLists.txt +++ b/external/nlohmann/json/CMakeLists.txt @@ -2,6 +2,7 @@ FetchContent_Declare(nlohmann_json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG "79587f896ed4db2a0b63baa9151113f4da336599" # Unreleased + SYSTEM OVERRIDE_FIND_PACKAGE ) diff --git a/external/zeux/pugixml/CMakeLists.txt b/external/zeux/pugixml/CMakeLists.txt index 405b9968..317a1b6f 100644 --- a/external/zeux/pugixml/CMakeLists.txt +++ b/external/zeux/pugixml/CMakeLists.txt @@ -1,6 +1,8 @@ FetchContent_Declare(pugixml GIT_REPOSITORY https://github.com/zeux/pugixml.git GIT_TAG "db78afc2b7d8f043b4bc6b185635d949ea2ed2a8" # v1.14 + + SYSTEM ) FetchContent_MakeAvailable(pugixml) From 74141ce76018d61c9514df27806497a4406bf632 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 15 Jan 2026 15:38:29 +0000 Subject: [PATCH 119/196] refactor: update container image to version 6.6.4 in Dockerfile and CI workflows --- .devcontainer/Dockerfile | 2 +- .github/workflows/ci.yml | 6 +++--- .github/workflows/static-analysis.yml | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 151af6dd..b6e650d4 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,3 +1,3 @@ -FROM ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.1@sha256:9ba2d20db24a646edd6ea7a8a075e76239ce63d1542cc823242e680901e792f9 +FROM ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.4@sha256:0d6b572f5c589def3efb245844528ef44903c9927149da9bcec21feab36388b9 HEALTHCHECK NONE diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f833e1c7..19ca7bd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,7 +24,7 @@ jobs: build-windows: name: Windows Host Build runs-on: [ubuntu-latest] - container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.1@sha256:9ba2d20db24a646edd6ea7a8a075e76239ce63d1542cc823242e680901e792f9 # v6.6.1 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.4@sha256:0d6b572f5c589def3efb245844528ef44903c9927149da9bcec21feab36388b9 # v6.6.1 steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 @@ -70,7 +70,7 @@ jobs: build-linux-devcontainer: name: Linux Host Build in Devcontainer runs-on: [ubuntu-latest] - container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.1@sha256:9ba2d20db24a646edd6ea7a8a075e76239ce63d1542cc823242e680901e792f9 # v6.6.1 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.4@sha256:0d6b572f5c589def3efb245844528ef44903c9927149da9bcec21feab36388b9 # v6.6.1 steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2.20 @@ -98,7 +98,7 @@ jobs: issues: read checks: write pull-requests: write - container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.1@sha256:9ba2d20db24a646edd6ea7a8a075e76239ce63d1542cc823242e680901e792f9 # v6.6.1 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.4@sha256:0d6b572f5c589def3efb245844528ef44903c9927149da9bcec21feab36388b9 # v6.6.1 steps: - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2.20 diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index 19526bf4..9acba51f 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -18,7 +18,7 @@ jobs: sonar: name: SonarCloud runs-on: ubuntu-latest - container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.1@sha256:9ba2d20db24a646edd6ea7a8a075e76239ce63d1542cc823242e680901e792f9 # v6.6.1 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.4@sha256:0d6b572f5c589def3efb245844528ef44903c9927149da9bcec21feab36388b9 # v6.6.1 env: SONAR_SERVER_URL: "https://sonarcloud.io" steps: @@ -99,7 +99,7 @@ jobs: codeql: name: CodeQL runs-on: ubuntu-latest - container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.1@sha256:9ba2d20db24a646edd6ea7a8a075e76239ce63d1542cc823242e680901e792f9 # v6.6.1 + container: ghcr.io/philips-software/amp-devcontainer-cpp:v6.6.4@sha256:0d6b572f5c589def3efb245844528ef44903c9927149da9bcec21feab36388b9 # v6.6.1 permissions: security-events: write steps: From d2b4f24415f5b781c42d90d975d9edae09a73b16 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 15 Jan 2026 15:39:13 +0000 Subject: [PATCH 120/196] refactor: update library link targets to use scoped names for CLI11 and nlohmann_json --- cucumber_cpp/library/CMakeLists.txt | 2 +- cucumber_cpp/library/formatter/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index 84f4e8ac..e893b440 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -27,7 +27,7 @@ target_link_libraries(cucumber_cpp.library PUBLIC cucumber_cpp.library.util cucumber_cpp.library.formatter cucumber_cpp.library.support - CLI11 + CLI11::CLI11 ) target_compile_options(cucumber_cpp.library diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index 3ce3dd9e..c678fa80 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -20,7 +20,7 @@ target_link_libraries(cucumber_cpp.library.formatter PUBLIC cucumber_cpp.library.query cucumber_cpp.library.support cucumber_cpp.library.util - nlohmann_json + nlohmann_json::nlohmann_json ) add_subdirectory(helper) From 594503bf7ca5f213acd1c5b737cd77022a8cce4b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 15 Jan 2026 16:22:11 +0000 Subject: [PATCH 121/196] refactor: remove unused duration includes and improve hook error handling in Worker --- cucumber_cpp/library/runtime/Worker.cpp | 48 +++++++------------------ cucumber_cpp/library/runtime/Worker.hpp | 11 ++++-- 2 files changed, 21 insertions(+), 38 deletions(-) diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index a84d0ebf..0c8c3620 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -1,6 +1,5 @@ #include "cucumber_cpp/library/runtime/Worker.hpp" #include "cucumber/gherkin/id_generator.hpp" -#include "cucumber/messages/duration.hpp" #include "cucumber/messages/feature.hpp" #include "cucumber/messages/gherkin_document.hpp" #include "cucumber/messages/pickle.hpp" @@ -24,45 +23,21 @@ #include #include #include -#include #include #include -#include -#include #include namespace cucumber_cpp::library::runtime { namespace { - cucumber::messages::duration operator+=(cucumber::messages::duration durationA, cucumber::messages::duration durationB) - { - const auto seconds = durationA.seconds + durationB.seconds; - const auto nanos = durationA.nanos + durationB.nanos; - - if (nanos >= support::nanosecondsPerSecond) - return { seconds + 1, nanos - support::nanosecondsPerSecond }; - else - return { seconds, nanos }; - } - - const auto to_underlying = [](const auto& value) - { - return static_cast>>(value); - }; - - const auto compare = [](const cucumber::messages::test_step_result& a, const cucumber::messages::test_step_result& b) - { - return to_underlying(a.status) < to_underlying(b.status); - }; - const inline std::set failingStatuses{ cucumber::messages::test_step_result_status::AMBIGUOUS, cucumber::messages::test_step_result_status::FAILED, cucumber::messages::test_step_result_status::UNDEFINED, }; - std::size_t RetriesForPickle(const cucumber::messages::pickle& pickle, support::RunOptions::Runtime& options) + std::size_t RetriesForPickle(const cucumber::messages::pickle& pickle, const support::RunOptions::Runtime& options) { if (options.retry == 0) return 0; @@ -92,7 +67,7 @@ namespace cucumber_cpp::library::runtime std::vector results; const auto ids = supportCodeLibrary.hookRegistry.FindIds(support::HookType::beforeAll); for (const auto& id : ids) - results.emplace_back(std::move(RunTestHook(id, programContext))); + results.emplace_back(RunTestHook(id, programContext)); return results; } @@ -102,7 +77,7 @@ namespace cucumber_cpp::library::runtime std::vector results; auto ids = supportCodeLibrary.hookRegistry.FindIds(support::HookType::afterAll); for (const auto& id : ids | std::views::reverse) - results.emplace_back(std::move(RunTestHook(id, programContext))); + results.emplace_back(RunTestHook(id, programContext)); return results; } @@ -130,7 +105,7 @@ namespace cucumber_cpp::library::runtime gherkinDocument, assembledTestCase.pickle, assembledTestCase.testCase, - options.retry, + RetriesForPickle(assembledTestCase.pickle, options), options.dryRun || (options.failFast && failing), supportCodeLibrary, testSuiteContext, @@ -145,11 +120,12 @@ namespace cucumber_cpp::library::runtime { std::vector results; const auto ids = supportCodeLibrary.hookRegistry.FindIds(support::HookType::beforeFeature, feature.tags); + for (const auto& id : ids) - results.emplace_back(std::move(RunTestHook(id, context))); + results.emplace_back(RunTestHook(id, context)); if (util::GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) - throw std::runtime_error("Failed before feature hook"); + throw FeatureHookError{ "Failed before feature hook" }; return results; } @@ -158,16 +134,17 @@ namespace cucumber_cpp::library::runtime { std::vector results; const auto ids = supportCodeLibrary.hookRegistry.FindIds(support::HookType::afterFeature, feature.tags); + for (const auto& id : ids) - results.emplace_back(std::move(RunTestHook(id, context))); + results.emplace_back(RunTestHook(id, context)); if (util::GetWorstTestStepResult(results).status != cucumber::messages::test_step_result_status::PASSED) - throw std::runtime_error("Failed after feature hook"); + throw FeatureHookError{ "Failed after feature hook" }; return results; } - cucumber::messages::test_step_result Worker::RunTestHook(std::string id, Context& context) + cucumber::messages::test_step_result Worker::RunTestHook(const std::string& id, Context& context) { const auto& definition = supportCodeLibrary.hookRegistry.GetDefinitionById(id); const auto testRunHookStartedId = idGenerator->next_id(); @@ -192,7 +169,7 @@ namespace cucumber_cpp::library::runtime return result; } - bool Worker::IsStatusFailed(cucumber::messages::test_step_result_status status) + bool Worker::IsStatusFailed(cucumber::messages::test_step_result_status status) const { if (options.dryRun) return false; @@ -202,5 +179,4 @@ namespace cucumber_cpp::library::runtime return failingStatuses.contains(status); } - } diff --git a/cucumber_cpp/library/runtime/Worker.hpp b/cucumber_cpp/library/runtime/Worker.hpp index 5f6c547e..b58bf66b 100644 --- a/cucumber_cpp/library/runtime/Worker.hpp +++ b/cucumber_cpp/library/runtime/Worker.hpp @@ -12,12 +12,19 @@ #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include #include #include #include namespace cucumber_cpp::library::runtime { + struct FeatureHookError : std::runtime_error + { + using std::runtime_error::runtime_error; + }; + struct Worker { Worker(std::string_view testRunStartedId, @@ -37,9 +44,9 @@ namespace cucumber_cpp::library::runtime std::vector RunBeforeTestSuiteHooks(const cucumber::messages::feature& feature, Context& context); std::vector RunAfterTestSuiteHooks(const cucumber::messages::feature& feature, Context& context); - cucumber::messages::test_step_result RunTestHook(std::string id, Context& context); + cucumber::messages::test_step_result RunTestHook(const std::string& id, Context& context); - bool IsStatusFailed(cucumber::messages::test_step_result_status status); + bool IsStatusFailed(cucumber::messages::test_step_result_status status) const; std::string_view testRunStartedId; util::Broadcaster& broadcaster; From 9007ce132fbc43671aacc85d75a9cfe272927c44 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 14 Jan 2026 23:08:49 +0000 Subject: [PATCH 122/196] refactor: update CCR_FETCH_DEPS option to use CCR_DEFAULTOPT and add cpp-terminal dependency --- CMakeLists.txt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b3463d93..2d7dc405 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -18,7 +18,7 @@ else() set(CCR_EXCLUDE_FROM_ALL "EXCLUDE_FROM_ALL") endif() -option(CCR_FETCH_DEPS "Fetch dependencies via FetchContent." On ) +option(CCR_FETCH_DEPS "Fetch dependencies via FetchContent." ${CCR_DEFAULTOPT} ) option(CCR_BUILD_TESTS "Enable build of the tests" ${CCR_DEFAULTOPT}) option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements" Off) @@ -70,6 +70,7 @@ else() find_package(cucumber_messages REQUIRED) find_package(cucumber_gherkin REQUIRED) find_package(yaml-cpp REQUIRED) + find_package(cpp-terminal REQUIRED) endif() add_subdirectory(cucumber_cpp) From 7087103bafd956e76d88e4fb138a5a116811057e Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 15 Jan 2026 23:38:42 +0000 Subject: [PATCH 123/196] refactor: add retryTagExpression to runtime configuration in compatibility --- compatibility/compatibility.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 655bf82d..34fda25d 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -257,6 +257,7 @@ namespace compatibility .runtime = { .retry = std::string{ KIT_STRING }.starts_with("retry") ? 2u : 0u, .strict = true, + .retryTagExpression = cucumber_cpp::library::tag_expression::Parse(""), }, }; From 5e1de27d3d4f54d22b3ff468360ccf3f91e03876 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 15 Jan 2026 23:59:04 +0000 Subject: [PATCH 124/196] refactor: reorganize CMakeLists.txt files and add fmtlib dependency --- external/CMakeLists.txt | 1 + external/cliutils/cli11/CMakeLists.txt | 2 -- external/cucumber/gherkin/CMakeLists.txt | 5 ++--- external/cucumber/messages/CMakeLists.txt | 5 ++--- external/fmtlib/CMakeLists.txt | 1 + external/fmtlib/fmt/CMakeLists.txt | 7 +++++++ external/google/googletest/CMakeLists.txt | 3 --- external/jbeder/yaml-cpp/CMakeLists.txt | 2 -- external/jupyter-xeus/cpp-terminal/CMakeLists.txt | 2 -- external/nlohmann/json/CMakeLists.txt | 2 -- external/zeux/pugixml/CMakeLists.txt | 2 -- 11 files changed, 13 insertions(+), 19 deletions(-) create mode 100644 external/fmtlib/CMakeLists.txt create mode 100644 external/fmtlib/fmt/CMakeLists.txt diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 86248804..26e1b31e 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory(nlohmann) # before cucumber add_subdirectory(cliutils) add_subdirectory(cucumber) +add_subdirectory(fmtlib) add_subdirectory(google) add_subdirectory(jbeder) add_subdirectory(jupyter-xeus) diff --git a/external/cliutils/cli11/CMakeLists.txt b/external/cliutils/cli11/CMakeLists.txt index 92d17734..51ba3070 100644 --- a/external/cliutils/cli11/CMakeLists.txt +++ b/external/cliutils/cli11/CMakeLists.txt @@ -2,8 +2,6 @@ FetchContent_Declare( cli11 GIT_REPOSITORY https://github.com/CLIUtils/CLI11 GIT_TAG f4d0731cebb123ff0ace712c099dffbcd2c58e5a # v2.4.1 - - SYSTEM ) set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) diff --git a/external/cucumber/gherkin/CMakeLists.txt b/external/cucumber/gherkin/CMakeLists.txt index f47d71ad..4022c03f 100644 --- a/external/cucumber/gherkin/CMakeLists.txt +++ b/external/cucumber/gherkin/CMakeLists.txt @@ -1,8 +1,7 @@ -FetchContent_Declare(cucumber_gherkin +FetchContent_Declare( + cucumber_gherkin GIT_REPOSITORY https://github.com/cucumber/gherkin.git GIT_TAG "v37.0.0" - - SYSTEM ) FetchContent_MakeAvailable(cucumber_gherkin) diff --git a/external/cucumber/messages/CMakeLists.txt b/external/cucumber/messages/CMakeLists.txt index d628618b..1e8a68c0 100644 --- a/external/cucumber/messages/CMakeLists.txt +++ b/external/cucumber/messages/CMakeLists.txt @@ -1,8 +1,7 @@ -FetchContent_Declare(cucumber_messages +FetchContent_Declare( + cucumber_messages GIT_REPOSITORY https://github.com/cucumber/messages.git GIT_TAG "v31.0.0" - - SYSTEM OVERRIDE_FIND_PACKAGE ) diff --git a/external/fmtlib/CMakeLists.txt b/external/fmtlib/CMakeLists.txt new file mode 100644 index 00000000..468fe7de --- /dev/null +++ b/external/fmtlib/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(fmt) diff --git a/external/fmtlib/fmt/CMakeLists.txt b/external/fmtlib/fmt/CMakeLists.txt new file mode 100644 index 00000000..2e687af6 --- /dev/null +++ b/external/fmtlib/fmt/CMakeLists.txt @@ -0,0 +1,7 @@ +FetchContent_Declare( + libfmt + GIT_REPOSITORY https://github.com/fmtlib/fmt + GIT_TAG 12.1.0 +) + +FetchContent_MakeAvailable(libfmt) diff --git a/external/google/googletest/CMakeLists.txt b/external/google/googletest/CMakeLists.txt index 7a8502c0..e569ea75 100644 --- a/external/google/googletest/CMakeLists.txt +++ b/external/google/googletest/CMakeLists.txt @@ -3,9 +3,6 @@ if(NOT TARGET gtest) googletest GIT_REPOSITORY https://github.com/google/googletest GIT_TAG v1.14.0 - - SYSTEM - FIND_PACKAGE_ARGS NAMES GTest ) set(gtest_force_shared_crt On CACHE BOOL "" FORCE) # For Windows: Prevent overriding the parent project's compiler/linker settings diff --git a/external/jbeder/yaml-cpp/CMakeLists.txt b/external/jbeder/yaml-cpp/CMakeLists.txt index 3502382f..aaae6767 100644 --- a/external/jbeder/yaml-cpp/CMakeLists.txt +++ b/external/jbeder/yaml-cpp/CMakeLists.txt @@ -2,8 +2,6 @@ FetchContent_Declare( yaml-cpp GIT_REPOSITORY https://github.com/jbeder/yaml-cpp.git GIT_TAG 2f86d13775d119edbb69af52e5f566fd65c6953b # Unreleased - - SYSTEM ) set(YAML_ENABLE_PIC OFF CACHE STRING "") diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt index c8d3f930..5855aa30 100644 --- a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt +++ b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt @@ -2,8 +2,6 @@ FetchContent_Declare( cpp-terminal GIT_REPOSITORY https://github.com/jupyter-xeus/cpp-terminal GIT_TAG 48ae2f284084850901c45b6c10a9d68949c1b272 # unreleased main - - SYSTEM ) set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) diff --git a/external/nlohmann/json/CMakeLists.txt b/external/nlohmann/json/CMakeLists.txt index 515cfcae..2accb643 100644 --- a/external/nlohmann/json/CMakeLists.txt +++ b/external/nlohmann/json/CMakeLists.txt @@ -1,8 +1,6 @@ FetchContent_Declare(nlohmann_json GIT_REPOSITORY https://github.com/nlohmann/json.git GIT_TAG "79587f896ed4db2a0b63baa9151113f4da336599" # Unreleased - - SYSTEM OVERRIDE_FIND_PACKAGE ) diff --git a/external/zeux/pugixml/CMakeLists.txt b/external/zeux/pugixml/CMakeLists.txt index 317a1b6f..405b9968 100644 --- a/external/zeux/pugixml/CMakeLists.txt +++ b/external/zeux/pugixml/CMakeLists.txt @@ -1,8 +1,6 @@ FetchContent_Declare(pugixml GIT_REPOSITORY https://github.com/zeux/pugixml.git GIT_TAG "db78afc2b7d8f043b4bc6b185635d949ea2ed2a8" # v1.14 - - SYSTEM ) FetchContent_MakeAvailable(pugixml) From 0f3d0ceb81bb60e1fd1db4d92520048c0d826144 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 00:15:16 +0000 Subject: [PATCH 125/196] refactor: rename PrettyPrinter to PrettyFormatter and implement formatter logic --- cucumber_cpp/library/api/Formatters.cpp | 4 +-- cucumber_cpp/library/formatter/CMakeLists.txt | 4 +-- ...{PrettyPrinter.cpp => PrettyFormatter.cpp} | 26 +++++++++---------- ...{PrettyPrinter.hpp => PrettyFormatter.hpp} | 2 +- 4 files changed, 18 insertions(+), 18 deletions(-) rename cucumber_cpp/library/formatter/{PrettyPrinter.cpp => PrettyFormatter.cpp} (80%) rename cucumber_cpp/library/formatter/{PrettyPrinter.hpp => PrettyFormatter.hpp} (99%) diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index ba854f4b..2bab65cc 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -1,6 +1,6 @@ #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" -#include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" +#include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/query/Query.hpp" @@ -30,7 +30,7 @@ namespace cucumber_cpp::library::api Formatters::Formatters() { - RegisterFormatter(); + RegisterFormatter(); RegisterFormatter(); } diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index c678fa80..95951395 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -3,8 +3,8 @@ add_library(cucumber_cpp.library.formatter ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.formatter PRIVATE Formatter.cpp Formatter.hpp - PrettyPrinter.cpp - PrettyPrinter.hpp + PrettyFormatter.cpp + PrettyFormatter.hpp SummaryFormatter.cpp SummaryFormatter.hpp ) diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp similarity index 80% rename from cucumber_cpp/library/formatter/PrettyPrinter.cpp rename to cucumber_cpp/library/formatter/PrettyFormatter.cpp index 2283b45d..c8f9674d 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -1,4 +1,4 @@ -#include "cucumber_cpp/library/formatter/PrettyPrinter.hpp" +#include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/feature.hpp" @@ -44,7 +44,7 @@ namespace cucumber_cpp::library::formatter } } - void PrettyPrinter::OnEnvelope(const cucumber::messages::envelope& envelope) + void PrettyFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) { if (envelope.test_case_started) { @@ -58,7 +58,7 @@ namespace cucumber_cpp::library::formatter } } - void PrettyPrinter::CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted) + void PrettyFormatter::CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted) { const auto& pickle = query.FindPickleBy(testCaseStarted); const auto& lineage = query.FindLineageByPickle(pickle); @@ -91,7 +91,7 @@ namespace cucumber_cpp::library::formatter scenarioIndentByTestCaseStartedId[testCaseStarted.id] = scenarioIndent; } - void PrettyPrinter::HandleTestCaseStarted(const cucumber::messages::test_case_started& testCaseStarted) + void PrettyFormatter::HandleTestCaseStarted(const cucumber::messages::test_case_started& testCaseStarted) { const auto& pickle = query.FindPickleBy(testCaseStarted); const auto& location = query.FindLocationOf(pickle); @@ -111,12 +111,12 @@ namespace cucumber_cpp::library::formatter PrintScenarioLine(pickle, *scenario, scenarioIndent, maxContentLength); } - void PrettyPrinter::HandleAttachment(const cucumber::messages::attachment& attachment) + void PrettyFormatter::HandleAttachment(const cucumber::messages::attachment& attachment) { /* TODO implement */ } - void PrettyPrinter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) + void PrettyFormatter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) { const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(testStepFinished.test_case_started_id); const auto maxContentLength = maxContentLengthByTestCaseStartedId.at(testStepFinished.test_case_started_id); @@ -133,12 +133,12 @@ namespace cucumber_cpp::library::formatter } } - void PrettyPrinter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) + void PrettyFormatter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) { /* TODO implement */ } - void PrettyPrinter::PrintFeatureLine(const cucumber::messages::feature& feature) + void PrettyFormatter::PrintFeatureLine(const cucumber::messages::feature& feature) { if (printedFeatureUris.contains(&feature)) return; @@ -147,7 +147,7 @@ namespace cucumber_cpp::library::formatter printedFeatureUris.insert(&feature); } - void PrettyPrinter::PrintRuleLine(const cucumber::messages::rule& rule) + void PrettyFormatter::PrintRuleLine(const cucumber::messages::rule& rule) { if (printedRuleIds.contains(&rule)) return; @@ -156,7 +156,7 @@ namespace cucumber_cpp::library::formatter printedRuleIds.insert(&rule); } - void PrettyPrinter::PrintTags(const cucumber::messages::pickle& pickle, std::size_t scenarioIndent) + void PrettyFormatter::PrintTags(const cucumber::messages::pickle& pickle, std::size_t scenarioIndent) { if (pickle.tags.empty()) return; @@ -169,12 +169,12 @@ namespace cucumber_cpp::library::formatter support::print(outputStream, "{:{}}{}\n", "", scenarioIndent, helper::ColorFunctions::Tag(support::Join(tagVec, " "))); } - void PrettyPrinter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) + void PrettyFormatter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) { PrintGherkinLine(std::format("{}: {}", scenario.keyword, pickle.name), nullptr, pickle.uri, scenario.location.line, scenarioIndent, maxContentLength); } - void PrettyPrinter::PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength) + void PrettyFormatter::PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength) { const auto uri = stepDefinition ? std::make_optional(*stepDefinition->source_reference.uri) : std::nullopt; const auto line = stepDefinition ? std::make_optional(stepDefinition->source_reference.location->line) : std::nullopt; @@ -182,7 +182,7 @@ namespace cucumber_cpp::library::formatter PrintGherkinLine(std::format("{}{}", step.keyword, pickleStep.text), helper::ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); } - void PrettyPrinter::PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) + void PrettyFormatter::PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) { if (title.length() > maxContentLength) throw std::logic_error("maxContentLength is smaller than title length"); diff --git a/cucumber_cpp/library/formatter/PrettyPrinter.hpp b/cucumber_cpp/library/formatter/PrettyFormatter.hpp similarity index 99% rename from cucumber_cpp/library/formatter/PrettyPrinter.hpp rename to cucumber_cpp/library/formatter/PrettyFormatter.hpp index bf694e95..a9cce339 100644 --- a/cucumber_cpp/library/formatter/PrettyPrinter.hpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.hpp @@ -27,7 +27,7 @@ namespace cucumber_cpp::library::formatter { - struct PrettyPrinter + struct PrettyFormatter : Formatter { using Formatter::Formatter; From 77cc94b1d60f484fb05bbcebe5daba1ce4e5ec39 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 01:22:06 +0000 Subject: [PATCH 126/196] refactor: enhance PrettyFormatter to support attachment formatting and status icons --- .../library/formatter/PrettyFormatter.cpp | 88 +++++++++++++++++-- .../library/formatter/PrettyFormatter.hpp | 15 +++- .../formatter/helper/GetColorFunctions.cpp | 8 +- .../formatter/helper/GetColorFunctions.hpp | 13 +-- 4 files changed, 107 insertions(+), 17 deletions(-) diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index c8f9674d..6621ad6a 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/attachment_content_encoding.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/feature.hpp" #include "cucumber/messages/location.hpp" @@ -14,14 +15,17 @@ #include "cucumber/messages/test_run_finished.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" #include "cucumber_cpp/library/support/Join.hpp" #include "cucumber_cpp/library/support/Polyfill.hpp" +#include "nlohmann/json_fwd.hpp" #include #include #include #include #include +#include #include #include #include @@ -33,6 +37,20 @@ namespace cucumber_cpp::library::formatter { namespace { + constexpr auto gherkinIndentLength = 2; + constexpr auto stepArgumentIndentLength = 2; + constexpr auto attachmentIndentLength = 4; + + std::map> iconMap{ + { cucumber::messages::test_step_result_status::AMBIGUOUS, "❓" }, + { cucumber::messages::test_step_result_status::FAILED, "❌" }, + { cucumber::messages::test_step_result_status::PASSED, "✅" }, + { cucumber::messages::test_step_result_status::PENDING, "🟡" }, + { cucumber::messages::test_step_result_status::SKIPPED, "➡️" }, + { cucumber::messages::test_step_result_status::UNDEFINED, "⚠️" }, + { cucumber::messages::test_step_result_status::UNKNOWN, "❔" }, + }; + std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario) { return std::format("{}: {}", scenario.keyword, pickle.name); @@ -42,6 +60,40 @@ namespace cucumber_cpp::library::formatter { return std::format("{}{}", step.keyword, pickleStep.text); } + + std::string FormatBase64Attachment(const std::string& body, const std::string& mediaType, const std::optional& filename) + { + if (!filename) + return helper::ColorFunctions::Attachment(std::format("Embedding [{} {} bytes]", mediaType, body.length())); + else + return helper::ColorFunctions::Attachment(std::format("Embedding {} [{} {} bytes]", filename.value(), mediaType, body.length())); + } + + std::string FormatTextAttachment(const std::string& body) + { + return helper::ColorFunctions::Attachment(body); + } + + std::string FormatAttachment(const cucumber::messages::attachment& attachment) + { + if (attachment.content_encoding == cucumber::messages::attachment_content_encoding::BASE64) + { + return FormatBase64Attachment(attachment.body, attachment.media_type, attachment.file_name); + } + else + { + return FormatTextAttachment(attachment.body); + } + return ""; + } + } + + PrettyFormatter::Options::Options(const nlohmann::json& formatOptions) + : includeAttachments{ formatOptions.contains("pretty") ? formatOptions.at("pretty").value("include_attachments", true) : true } + , includeFeatureLine{ formatOptions.contains("pretty") ? formatOptions.at("pretty").value("include_feature_line", true) : true } + , includeRuleLine{ formatOptions.contains("pretty") ? formatOptions.at("pretty").value("include_rule_line", true) : true } + , useStatusIcon{ formatOptions.contains("pretty") ? formatOptions.at("pretty").value("use_status_icon", true) : true } + { } void PrettyFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) @@ -52,10 +104,20 @@ namespace cucumber_cpp::library::formatter HandleTestCaseStarted(envelope.test_case_started.value()); } + if (envelope.attachment) + { + HandleAttachment(envelope.attachment.value()); + } + if (envelope.test_step_finished) { HandleTestStepFinished(envelope.test_step_finished.value()); } + + if (envelope.test_run_finished) + { + HandleTestRunFinished(envelope.test_run_finished.value()); + } } void PrettyFormatter::CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted) @@ -79,10 +141,11 @@ namespace cucumber_cpp::library::formatter }; auto steplengths = testCase.test_steps | std::views::filter(hasPickleStepId) | std::views::transform(toLength); + const auto maxStepLengthIter = std::ranges::max_element(steplengths); const auto maxStepLength = (maxStepLengthIter != steplengths.end()) ? *maxStepLengthIter : 0; - maxContentLengthByTestCaseStartedId[testCaseStarted.id] = std::max(scenarioLength, maxStepLength); + maxContentLengthByTestCaseStartedId[testCaseStarted.id] = std::max(scenarioLength, options.useStatusIcon ? maxStepLength + 2 : maxStepLength); std::size_t scenarioIndent{ 0 }; scenarioIndent += 2; @@ -113,7 +176,13 @@ namespace cucumber_cpp::library::formatter void PrettyFormatter::HandleAttachment(const cucumber::messages::attachment& attachment) { - /* TODO implement */ + if (!options.includeAttachments) + return; + + const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(attachment.test_case_started_id.value()); + const auto content = FormatAttachment(attachment); + + support::print(outputStream, "{:{}}{}\n", "", scenarioIndent + gherkinIndentLength + attachmentIndentLength, content); } void PrettyFormatter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) @@ -135,7 +204,10 @@ namespace cucumber_cpp::library::formatter void PrettyFormatter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) { - /* TODO implement */ + if (testRunFinished.exception && testRunFinished.exception->stack_trace) + support::print(outputStream, "{}\n", helper::ColorFunctions::ForStatus(cucumber::messages::test_step_result_status::FAILED)(testRunFinished.exception->stack_trace.value())); + else if (testRunFinished.exception && testRunFinished.exception->message) + support::print(outputStream, "{}\n", helper::ColorFunctions::ForStatus(cucumber::messages::test_step_result_status::FAILED)(testRunFinished.exception->message.value())); } void PrettyFormatter::PrintFeatureLine(const cucumber::messages::feature& feature) @@ -171,7 +243,7 @@ namespace cucumber_cpp::library::formatter void PrettyFormatter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) { - PrintGherkinLine(std::format("{}: {}", scenario.keyword, pickle.name), nullptr, pickle.uri, scenario.location.line, scenarioIndent, maxContentLength); + PrintGherkinLine("", std::format("{}: {}", scenario.keyword, pickle.name), nullptr, pickle.uri, scenario.location.line, scenarioIndent, maxContentLength); } void PrettyFormatter::PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength) @@ -179,10 +251,10 @@ namespace cucumber_cpp::library::formatter const auto uri = stepDefinition ? std::make_optional(*stepDefinition->source_reference.uri) : std::nullopt; const auto line = stepDefinition ? std::make_optional(stepDefinition->source_reference.location->line) : std::nullopt; - PrintGherkinLine(std::format("{}{}", step.keyword, pickleStep.text), helper::ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); + PrintGherkinLine(options.useStatusIcon ? iconMap.at(testStepFinished.test_step_result.status) : "", std::format("{}{}", step.keyword, pickleStep.text), helper::ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); } - void PrettyFormatter::PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) + void PrettyFormatter::PrintGherkinLine(std::string_view icon, std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) { if (title.length() > maxContentLength) throw std::logic_error("maxContentLength is smaller than title length"); @@ -196,8 +268,8 @@ namespace cucumber_cpp::library::formatter }; if (uri.has_value() && line.has_value()) - support::print(outputStream, "{:{}}{}{:{}} {}\n", "", indent, formatTitle(title), "", padding, helper::ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); + support::print(outputStream, "{:{}}{}{}{:{}} {}\n", "", indent, icon, formatTitle(title), "", padding, helper::ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); else - support::print(outputStream, "{:{}}{}\n", "", indent, formatTitle(title)); + support::print(outputStream, "{:{}}{}{}\n", "", indent, icon, formatTitle(title)); } } diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.hpp b/cucumber_cpp/library/formatter/PrettyFormatter.hpp index a9cce339..70040da8 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.hpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.hpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -35,6 +36,16 @@ namespace cucumber_cpp::library::formatter constexpr static auto name = "pretty"; private: + struct Options + { + explicit Options(const nlohmann::json& formatOptions); + + const bool includeAttachments; + const bool includeFeatureLine; + const bool includeRuleLine; + const bool useStatusIcon; + }; + void OnEnvelope(const cucumber::messages::envelope& envelope) override; void CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted); @@ -50,7 +61,9 @@ namespace cucumber_cpp::library::formatter void PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength); void PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength); - void PrintGherkinLine(std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength); + void PrintGherkinLine(std::string_view icon, std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength); + + Options options{ formatOptions }; std::map testCaseStartedIdToScenarioMap; diff --git a/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp b/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp index bdaec299..03b21432 100644 --- a/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp +++ b/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp @@ -18,8 +18,7 @@ namespace cucumber_cpp::library::formatter::helper } } - std::function - ColorFunctions::ForStatus(cucumber::messages::test_step_result_status status) + std::function ColorFunctions::ForStatus(cucumber::messages::test_step_result_status status) { using enum cucumber::messages::test_step_result_status; @@ -40,6 +39,11 @@ namespace cucumber_cpp::library::formatter::helper } } + std::string ColorFunctions::Attachment(std::string_view sv) + { + return ColorString(sv); + } + std::string ColorFunctions::Location(std::string_view sv) { return ColorString(sv); diff --git a/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp b/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp index ce8cd4df..5f5526bc 100644 --- a/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp +++ b/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp @@ -11,12 +11,13 @@ namespace cucumber_cpp::library::formatter::helper struct ColorFunctions { static std::function ForStatus(cucumber::messages::test_step_result_status status); - static std::string Location(std::string_view); - static std::string Tag(std::string_view); - static std::string DiffAdded(std::string_view); - static std::string DiffRemoved(std::string_view); - static std::string ErrorMessage(std::string_view); - static std::string ErrorStack(std::string_view); + static std::string Attachment(std::string_view sv); + static std::string Location(std::string_view sv); + static std::string Tag(std::string_view sv); + static std::string DiffAdded(std::string_view sv); + static std::string DiffRemoved(std::string_view sv); + static std::string ErrorMessage(std::string_view sv); + static std::string ErrorStack(std::string_view sv); }; } From 5df8140692d1838e848e1bcc06a6e80188aeb47f Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 01:22:30 +0000 Subject: [PATCH 127/196] wip: JUnitXmlFormatter --- cucumber_cpp/library/formatter/CMakeLists.txt | 3 ++- .../library/formatter/JunitXmlFormatter.cpp | 25 +++++++++++++++++++ .../library/formatter/JunitXmlFormatter.hpp | 21 ++++++++++++++++ cucumber_cpp/library/query/Query.cpp | 17 +++++++++++++ cucumber_cpp/library/query/Query.hpp | 7 +++++- 5 files changed, 71 insertions(+), 2 deletions(-) create mode 100644 cucumber_cpp/library/formatter/JunitXmlFormatter.cpp create mode 100644 cucumber_cpp/library/formatter/JunitXmlFormatter.hpp diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index 95951395..b5daf9da 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -21,11 +21,12 @@ target_link_libraries(cucumber_cpp.library.formatter PUBLIC cucumber_cpp.library.support cucumber_cpp.library.util nlohmann_json::nlohmann_json + pugixml + fmt-header-only ) add_subdirectory(helper) - if (CCR_BUILD_TESTS) add_subdirectory(test) # add_subdirectory(test_helper) diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp new file mode 100644 index 00000000..50149c80 --- /dev/null +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp @@ -0,0 +1,25 @@ + +#include "cucumber_cpp/library/formatter/JunitXmlFormatter.hpp" +#include "cucumber_cpp/library/query/Query.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::formatter +{ + namespace + { + void MakeReport(query::Query& query, std::string testClassName) + { + + } + } + + void JunitXmlFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) + { + if (envelope.test_run_finished) + { + pugi::xml_node xml; + } + } +} diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp new file mode 100644 index 00000000..584fdd94 --- /dev/null +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp @@ -0,0 +1,21 @@ +#ifndef FORMATTER_JUNIT_XML_FORMATTER_HPP +#define FORMATTER_JUNIT_XML_FORMATTER_HPP + +#include "cucumber_cpp/library/formatter/Formatter.hpp" +#include + +namespace cucumber_cpp::library::formatter +{ + struct JunitXmlFormatter + : Formatter + { + using Formatter::Formatter; + + constexpr static auto name = "junit"; + + private: + void OnEnvelope(const cucumber::messages::envelope& envelope) override; + }; +} + +#endif diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index cd10dd5d..d660c545 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -29,6 +29,8 @@ #include "cucumber/messages/test_step_started.hpp" #include "cucumber/messages/undefined_parameter_type.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include #include #include #include @@ -148,6 +150,21 @@ namespace cucumber_cpp::library::query return testCaseFinishedByTestCaseStartedId; } + std::map> Query::CountMostSevereTestStepResultStatus() const + { + std::map> result{ + { cucumber::messages::test_step_result_status::UNKNOWN, 0 }, + { cucumber::messages::test_step_result_status::PASSED, 0 }, + { cucumber::messages::test_step_result_status::SKIPPED, 0 }, + { cucumber::messages::test_step_result_status::PENDING, 0 }, + { cucumber::messages::test_step_result_status::UNDEFINED, 0 }, + { cucumber::messages::test_step_result_status::AMBIGUOUS, 0 }, + { cucumber::messages::test_step_result_status::FAILED, 0 }, + }; + + return result; + } + void Query::operator+=(const cucumber::messages::envelope& envelope) { if (envelope.meta) diff --git a/cucumber_cpp/library/query/Query.hpp b/cucumber_cpp/library/query/Query.hpp index 351fd3aa..86eede69 100644 --- a/cucumber_cpp/library/query/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -32,7 +32,9 @@ #include "cucumber/messages/test_step_started.hpp" #include "cucumber/messages/undefined_parameter_type.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include #include +#include #include #include #include @@ -141,8 +143,11 @@ namespace cucumber_cpp::library::query const std::map>& TestCaseStarted() const; const std::map>& TestCaseFinishedByTestCaseStartedId() const; + std::map> CountMostSevereTestStepResultStatus() const; + private: - void operator+=(const cucumber::messages::envelope& envelope); + void + operator+=(const cucumber::messages::envelope& envelope); void operator+=(const cucumber::messages::gherkin_document& gherkinDocument); void operator+=(const cucumber::messages::pickle& pickle); From d7895d080ddf586d3c347222e6805d2e0082307b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 16:28:58 +0000 Subject: [PATCH 128/196] WIP: junit xml progress --- .vscode/launch.json | 3 +- CMakeLists.txt | 2 +- compatibility/compatibility.cpp | 14 +- .../features/test_undefined_success_1.feature | 3 +- .../features/test_undefined_success_2.feature | 1 - cucumber_cpp/acceptance_test/test.bats | 4 +- cucumber_cpp/library/Application.cpp | 4 +- cucumber_cpp/library/Application.hpp | 8 +- cucumber_cpp/library/api/Formatters.cpp | 2 + .../library/engine/ExecutionContext.cpp | 4 +- cucumber_cpp/library/formatter/CMakeLists.txt | 2 + .../library/formatter/JunitXmlFormatter.cpp | 143 +++++++++++++++++- .../library/formatter/JunitXmlFormatter.hpp | 15 +- .../library/formatter/PrettyFormatter.cpp | 18 +-- .../library/formatter/SummaryFormatter.cpp | 2 +- .../formatter/helper/SummaryHelpers.cpp | 4 +- cucumber_cpp/library/query/Query.cpp | 106 ++++++++++++- cucumber_cpp/library/query/Query.hpp | 23 ++- cucumber_cpp/library/runtime/Coordinator.cpp | 6 +- .../library/runtime/TestCaseRunner.cpp | 12 +- cucumber_cpp/library/runtime/Worker.cpp | 6 +- cucumber_cpp/library/support/Body.cpp | 6 +- cucumber_cpp/library/support/CMakeLists.txt | 4 - cucumber_cpp/library/support/Types.hpp | 4 +- cucumber_cpp/library/util/CMakeLists.txt | 7 +- .../library/{support => util}/Duration.cpp | 6 +- .../library/{support => util}/Duration.hpp | 8 +- .../library/{support => util}/Timestamp.cpp | 8 +- .../library/{support => util}/Timestamp.hpp | 8 +- 29 files changed, 352 insertions(+), 81 deletions(-) rename cucumber_cpp/library/{support => util}/Duration.cpp (92%) rename cucumber_cpp/library/{support => util}/Duration.hpp (87%) rename cucumber_cpp/library/{support => util}/Timestamp.cpp (92%) rename cucumber_cpp/library/{support => util}/Timestamp.hpp (86%) diff --git a/.vscode/launch.json b/.vscode/launch.json index 46ed6118..8e623ca0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -46,7 +46,7 @@ "COMx", "--nordic", "--format", - "summary", + "junit", "--tags", "${input:tag}", "--", @@ -62,6 +62,7 @@ ], "externalConsole": false, "MIMode": "gdb", + "debugServerPath": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", diff --git a/CMakeLists.txt b/CMakeLists.txt index 2d7dc405..be46cc5c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -61,7 +61,7 @@ include(GNUInstallDirs) include(CTest) if (CCR_FETCH_DEPS) - add_subdirectory(external SYSTEM) + add_subdirectory(external) else() find_package(CLI11 REQUIRED) find_package(nlohmann_json REQUIRED) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 34fda25d..561c46c5 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -3,12 +3,12 @@ #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include "nlohmann/json.hpp" #include "nlohmann/json_fwd.hpp" #include "gmock/gmock.h" @@ -45,7 +45,7 @@ namespace compatibility { struct Devkit { - std::set paths; + std::set> paths; std::string tagExpression; std::size_t retry; std::filesystem::path ndjsonFile; @@ -198,9 +198,9 @@ namespace compatibility return std::filesystem::is_regular_file(entry) && entry.path().has_extension() && entry.path().extension() == ".feature"; } - std::set GetFeatureFiles(std::set paths) + std::set> GetFeatureFiles(std::set> paths) { - std::set files; + std::set> files; for (const auto feature : paths) if (std::filesystem::is_directory(feature)) @@ -215,7 +215,7 @@ namespace compatibility return files; } - struct StopwatchIncremental : cucumber_cpp::library::support::Stopwatch + struct StopwatchIncremental : cucumber_cpp::library::util::Stopwatch { virtual ~StopwatchIncremental() = default; @@ -231,7 +231,7 @@ namespace compatibility std::chrono::nanoseconds current{ std::chrono::milliseconds{ 1 } }; }; - struct TimestampGeneratorIncremental : cucumber_cpp::library::support::TimestampGenerator + struct TimestampGeneratorIncremental : cucumber_cpp::library::util::TimestampGenerator { virtual ~TimestampGeneratorIncremental() = default; diff --git a/cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature b/cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature index e690cef7..909e7c1f 100644 --- a/cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature +++ b/cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature @@ -1,7 +1,6 @@ @undefinedsuccess Feature: Undefined success results in success - @result:success Scenario: Results in success Given a given step When a when step - Then a then step + Then a then step is missing diff --git a/cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature b/cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature index 29104865..d9b9ff09 100644 --- a/cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature +++ b/cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature @@ -1,6 +1,5 @@ @undefinedsuccess Feature: Undefined success results in undefined - @result:undefined Scenario: Results in undefined Given a given step When a when step diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index 239ea416..e9467562 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -61,8 +61,8 @@ teardown() { } @test "Second feature file does not overwrite success with an undefined status" { - run $acceptance_test --format summary pretty --tags "@undefinedsuccess and @result:success" -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature - assert_success + run $acceptance_test --format summary pretty --tags @undefinedsuccess -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature + assert_failure } @test "Valid reporters only" { diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 466734f4..0f829fea 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -61,9 +61,9 @@ namespace cucumber_cpp::library return std::filesystem::is_regular_file(entry) && entry.path().has_extension() && entry.path().extension() == ".feature"; } - std::set GetFeatureFiles(Application::Options& options) + std::set> GetFeatureFiles(Application::Options& options) { - std::set files; + std::set> files; for (const auto feature : options.paths | std::views::transform(ToFileSystemPath)) if (std::filesystem::is_directory(feature)) diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index cebde23a..effb157d 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -7,12 +7,12 @@ #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include #include #include @@ -78,8 +78,8 @@ namespace cucumber_cpp::library cucumber_expression::ParameterRegistry parameterRegistry{ cucumber_cpp::library::support::DefinitionRegistration::Instance().GetRegisteredParameters() }; bool removeDefaultGoogleTestListener; - support::StopWatchHighResolutionClock stopwatchHighResolutionClock; - support::TimestampGeneratorSystemClock timestampGeneratorSystemClock; + util::StopWatchHighResolutionClock stopwatchHighResolutionClock; + util::TimestampGeneratorSystemClock timestampGeneratorSystemClock; bool runPassed{ false }; }; diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index 2bab65cc..16f396af 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "cucumber_cpp/library/formatter/JunitXmlFormatter.hpp" #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" @@ -32,6 +33,7 @@ namespace cucumber_cpp::library::api { RegisterFormatter(); RegisterFormatter(); + RegisterFormatter(); } std::set> Formatters::GetAvailableFormatterNames() const diff --git a/cucumber_cpp/library/engine/ExecutionContext.cpp b/cucumber_cpp/library/engine/ExecutionContext.cpp index 8a90a72c..04f4e2e0 100644 --- a/cucumber_cpp/library/engine/ExecutionContext.cpp +++ b/cucumber_cpp/library/engine/ExecutionContext.cpp @@ -5,8 +5,8 @@ #include "cucumber/messages/test_run_hook_started.hpp" #include "cucumber/messages/test_step_started.hpp" #include "cucumber_cpp/library/Context.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include #include #include @@ -127,7 +127,7 @@ namespace cucumber_cpp::library::engine .test_case_started_id = std::move(test_case_started_id), .test_step_id = std::move(test_step_id), .test_run_hook_started_id = std::move(test_run_hook_started_id), - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), }, }); } diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index b5daf9da..80d214d9 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -3,6 +3,8 @@ add_library(cucumber_cpp.library.formatter ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.formatter PRIVATE Formatter.cpp Formatter.hpp + JunitXmlFormatter.cpp + JunitXmlFormatter.hpp PrettyFormatter.cpp PrettyFormatter.hpp SummaryFormatter.cpp diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp index 50149c80..490a722e 100644 --- a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp @@ -1,25 +1,158 @@ #include "cucumber_cpp/library/formatter/JunitXmlFormatter.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/query/Query.hpp" -#include -#include +#include "nlohmann/json_fwd.hpp" +#include "pugixml.hpp" +#include +#include +#include #include namespace cucumber_cpp::library::formatter { namespace { - void MakeReport(query::Query& query, std::string testClassName) + enum class FailureKind { - + failure, + skipped + }; + + struct ReportFailure + { + FailureKind kind; + std::optional type; + std::optional message; + std::optional stack; + }; + + struct ReportTestCase + { + std::string classname; + std::string name; + std::size_t time; + std::optional failure; + std::string output; + }; + + struct ReportSuite + { + cucumber::messages::duration time; + std::size_t tests; + std::size_t skipped; + std::size_t failures; + std::size_t errors; + std::list testCases; + std::optional timestamp; + }; + + std::optional MakeFailure(query::Query& query, const cucumber::messages::test_case_started& testCaseStarted) + { + return std::nullopt; + } + + std::string MakeOutput(query::Query& query, const cucumber::messages::test_case_started& testCaseStarted) + { + return ""; + } + + std::list MakeTestCases(query::Query& query, const std::optional& testClassName) + { + std::list testCases; + + const auto allTestCaseStarted = query.FindAllTestCaseStarted(); + for (const auto testCaseStartedPtr : allTestCaseStarted) + { + const auto& pickle = query.FindPickleBy(*testCaseStartedPtr); + const auto& lineage = query.FindLineageByPickle(pickle); + const auto& testStepFinishedAndTestStep = query.FindTestStepFinishedAndTestStepBy(*testCaseStartedPtr); + + testCases.emplace_back(testClassName.has_value() + ? testClassName.value() + : lineage.feature + ? lineage.feature->name + : pickle.uri, + query::NamingStrategy::Reduce(lineage, pickle), query.FindTestCaseDurationBy(*testCaseStartedPtr).seconds, MakeFailure(query, *testCaseStartedPtr), MakeOutput(query, *testCaseStartedPtr)); + } + + return testCases; + } + + ReportSuite MakeReport(query::Query& query, const std::optional& testClassName) + { + const auto& statuses = query.CountMostSevereTestStepResultStatus(); + + return { + .time = query.FindTestRunDuration(), + .tests = query.CountTestCasesStarted(), + .skipped = statuses.at(cucumber::messages::test_step_result_status::SKIPPED), + .failures = statuses.at(cucumber::messages::test_step_result_status::UNKNOWN) + + statuses.at(cucumber::messages::test_step_result_status::PENDING) + + statuses.at(cucumber::messages::test_step_result_status::UNDEFINED) + + statuses.at(cucumber::messages::test_step_result_status::AMBIGUOUS) + + statuses.at(cucumber::messages::test_step_result_status::FAILED), + .errors = 0, + .testCases = MakeTestCases(query, testClassName), + }; } } + JunitXmlFormatter::Options::Options(const nlohmann::json& formatOptions) + : suiteName{ formatOptions.contains("junit") ? formatOptions.at("junit").value("suite_name", "Cucumber") : "Cucumber" } + , testClassName{ formatOptions.contains("junit") ? formatOptions.at("junit").contains("test_class_name") ? std::make_optional(formatOptions.at("junit")["test_class_name"].get()) : std::nullopt : std::nullopt } + { + } + void JunitXmlFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) { if (envelope.test_run_finished) { - pugi::xml_node xml; + pugi::xml_document doc; + const auto& report = MakeReport(query, options.testClassName); + + auto testSuite = doc.append_child("testsuite"); + testSuite.append_attribute("name").set_value(options.suiteName.c_str()); + testSuite.append_attribute("tests").set_value(static_cast(report.tests)); + testSuite.append_attribute("skipped").set_value(static_cast(report.skipped)); + testSuite.append_attribute("failures").set_value(static_cast(report.failures)); + testSuite.append_attribute("errors").set_value(static_cast(report.errors)); + + if (report.timestamp) + testSuite.append_attribute("timestamp").set_value(report.timestamp->c_str()); + + for (const auto& testCase : report.testCases) + { + auto testCaseNode = testSuite.append_child("testcase"); + testCaseNode.append_attribute("classname").set_value(testCase.classname.c_str()); + testCaseNode.append_attribute("name").set_value(testCase.name.c_str()); + testCaseNode.append_attribute("time").set_value(testCase.time); + + if (testCase.failure) + { + auto failureNode = testCaseNode.append_child(testCase.failure->kind == FailureKind::failure ? "failure" : "skipped"); + + if (testCase.failure->kind == FailureKind::failure && testCase.failure->type) + failureNode.append_attribute("type").set_value(testCase.failure->type->c_str()); + + if (testCase.failure->message) + failureNode.append_attribute("message").set_value(testCase.failure->message->c_str()); + + if (testCase.failure->stack) + failureNode.append_child(pugi::node_pcdata).set_value(testCase.failure->stack->c_str()); + } + + if (!testCase.output.empty()) + { + auto systemOutNode = testCaseNode.append_child("system-out"); + systemOutNode.append_child(pugi::node_pcdata).set_value(testCase.output.c_str()); + } + } + + doc.save(outputStream); } } } diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp index 584fdd94..dc227440 100644 --- a/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp @@ -1,8 +1,11 @@ #ifndef FORMATTER_JUNIT_XML_FORMATTER_HPP #define FORMATTER_JUNIT_XML_FORMATTER_HPP +#include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" -#include +#include "nlohmann/json_fwd.hpp" +#include +#include namespace cucumber_cpp::library::formatter { @@ -14,7 +17,17 @@ namespace cucumber_cpp::library::formatter constexpr static auto name = "junit"; private: + struct Options + { + explicit Options(const nlohmann::json& formatOptions); + + const std::string suiteName; + const std::optional testClassName; + }; + void OnEnvelope(const cucumber::messages::envelope& envelope) override; + + Options options{ formatOptions }; }; } diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index 6621ad6a..c0601580 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -42,13 +42,13 @@ namespace cucumber_cpp::library::formatter constexpr auto attachmentIndentLength = 4; std::map> iconMap{ - { cucumber::messages::test_step_result_status::AMBIGUOUS, "❓" }, - { cucumber::messages::test_step_result_status::FAILED, "❌" }, - { cucumber::messages::test_step_result_status::PASSED, "✅" }, - { cucumber::messages::test_step_result_status::PENDING, "🟡" }, - { cucumber::messages::test_step_result_status::SKIPPED, "➡️" }, - { cucumber::messages::test_step_result_status::UNDEFINED, "⚠️" }, - { cucumber::messages::test_step_result_status::UNKNOWN, "❔" }, + { cucumber::messages::test_step_result_status::AMBIGUOUS, "✘" }, + { cucumber::messages::test_step_result_status::FAILED, "✘" }, + { cucumber::messages::test_step_result_status::PASSED, "✔" }, + { cucumber::messages::test_step_result_status::PENDING, "■" }, + { cucumber::messages::test_step_result_status::SKIPPED, "↷" }, + { cucumber::messages::test_step_result_status::UNDEFINED, "■" }, + { cucumber::messages::test_step_result_status::UNKNOWN, " " }, }; std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario) @@ -268,8 +268,8 @@ namespace cucumber_cpp::library::formatter }; if (uri.has_value() && line.has_value()) - support::print(outputStream, "{:{}}{}{}{:{}} {}\n", "", indent, icon, formatTitle(title), "", padding, helper::ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); + support::print(outputStream, "{:{}}{} {}{:{}} {}\n", "", indent, formatTitle(icon), formatTitle(title), "", padding, helper::ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); else - support::print(outputStream, "{:{}}{}{}\n", "", indent, icon, formatTitle(title)); + support::print(outputStream, "{:{}}{} {}\n", "", indent, formatTitle(icon), formatTitle(title)); } } diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index 2374c36b..4b461328 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -7,7 +7,7 @@ #include "cucumber_cpp/library/formatter/helper/IssueHelpers.hpp" #include "cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp" #include "cucumber_cpp/library/support/Polyfill.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include #include #include diff --git a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp index 111d5597..2adc6c5c 100644 --- a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp +++ b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp @@ -5,8 +5,8 @@ #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" -#include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/Join.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" #include #include #include @@ -57,7 +57,7 @@ namespace cucumber_cpp::library::formatter::helper std::string GetDurationSummary(const cucumber::messages::duration& duration) { - const auto total = support::DurationToMilliseconds(duration); + const auto total = util::DurationToMilliseconds(duration); return std::format("{:%Mm %S}s", total); } diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index d660c545..d74081ac 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -1,6 +1,7 @@ #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/background.hpp" +#include "cucumber/messages/duration.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/examples.hpp" #include "cucumber/messages/feature.hpp" @@ -26,18 +27,25 @@ #include "cucumber/messages/test_run_started.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result_status.hpp" #include "cucumber/messages/test_step_started.hpp" #include "cucumber/messages/undefined_parameter_type.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" +#include #include -#include #include +#include #include +#include #include #include +#include +#include #include #include #include +#include namespace cucumber_cpp::library::query { @@ -53,6 +61,34 @@ namespace cucumber_cpp::library::query return scenario->name; } + std::string NamingStrategy::Reduce(const Lineage& lineage, const cucumber::messages::pickle& pickle) + { + std::vector parts; + parts.reserve(10); + + if (!lineage.feature) + parts.emplace_back(lineage.feature->name); + + if (lineage.rule) + parts.emplace_back(lineage.rule->name); + + if (lineage.scenario) + parts.emplace_back(lineage.scenario->name); + else + parts.emplace_back(pickle.name); + + if (lineage.examples) + parts.emplace_back(lineage.examples->name); + + return std::accumulate(parts.begin(), parts.end(), std::string{}, [](const std::string& a, const std::string& b) + { + if (b.empty()) + return a; + else + return a + " - " + b; + }); + } + Query::Query(util::Broadcaster& broadcaster) : util::Listener{ broadcaster, [this](const cucumber::messages::envelope& envelope) { @@ -150,8 +186,24 @@ namespace cucumber_cpp::library::query return testCaseFinishedByTestCaseStartedId; } + std::size_t Query::CountTestCasesStarted() const + { + return FindAllTestCaseStarted().size(); + } + std::map> Query::CountMostSevereTestStepResultStatus() const { + struct SortByStatus + { + bool operator()(const std::pair& a, + const std::pair& b) const + { + const auto& statusA = a.first->test_step_result.status; + const auto& statusB = b.first->test_step_result.status; + return static_cast(statusA) < static_cast(statusB); + } + }; + std::map> result{ { cucumber::messages::test_step_result_status::UNKNOWN, 0 }, { cucumber::messages::test_step_result_status::PASSED, 0 }, @@ -162,9 +214,61 @@ namespace cucumber_cpp::library::query { cucumber::messages::test_step_result_status::FAILED, 0 }, }; + for (const auto* testCaseStarted : FindAllTestCaseStarted()) + { + const auto testStepFinishedAndTestSteps = FindTestStepFinishedAndTestStepBy(*testCaseStarted); + const auto [testStepFinished, testStep] = *std::ranges::max_element(testStepFinishedAndTestSteps, SortByStatus{}); + ++result[testStepFinished->test_step_result.status]; + } + return result; } + std::list Query::FindAllTestCaseStarted() const + { + auto view = testCaseStartedById | + std::views::values | + std::views::filter([this](const cucumber::messages::test_case_started& testCaseStarted) + { + return !testCaseFinishedByTestCaseStartedId.at(testCaseStarted.id).will_be_retried; + }) | + std::views::transform([](const cucumber::messages::test_case_started& testCaseStarted) + { + return &testCaseStarted; + }); + return { view.begin(), view.end() }; + } + + std::list> Query::FindTestStepFinishedAndTestStepBy(const cucumber::messages::test_case_started& testCaseStarted) const + { + const auto& testStepsFinished = testStepFinishedByTestCaseStartedId.at(testCaseStarted.id); + auto view = testStepsFinished | std::views::transform([this](const auto& testStepFinished) + { + return std::make_pair(&testStepFinished, &FindTestStepBy(testStepFinished)); + }); + return { view.begin(), view.end() }; + } + + cucumber::messages::duration Query::FindTestRunDuration() const + { + return testRunFinished->timestamp - testRunStarted->timestamp; + } + + cucumber::messages::duration Query::FindTestCaseDurationBy(const cucumber::messages::test_case_started& testCaseStarted) const + { + return FindTestCaseDurationBy(testCaseStarted, testCaseFinishedByTestCaseStartedId.at(testCaseStarted.id)); + } + + cucumber::messages::duration Query::FindTestCaseDurationBy(const cucumber::messages::test_case_finished& testCaseFinished) const + { + return FindTestCaseDurationBy(testCaseStartedById.at(testCaseFinished.test_case_started_id), testCaseFinished); + } + + cucumber::messages::duration Query::FindTestCaseDurationBy(const cucumber::messages::test_case_started& testCaseStarted, const cucumber::messages::test_case_finished& testCaseFinished) const + { + return testCaseFinished.timestamp - testCaseStarted.timestamp; + } + void Query::operator+=(const cucumber::messages::envelope& envelope) { if (envelope.meta) diff --git a/cucumber_cpp/library/query/Query.hpp b/cucumber_cpp/library/query/Query.hpp index 86eede69..fe1408f8 100644 --- a/cucumber_cpp/library/query/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -2,6 +2,7 @@ #define LIBRARY_QUERY_HPP #include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/duration.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/examples.hpp" #include "cucumber/messages/feature.hpp" @@ -29,14 +30,15 @@ #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" #include "cucumber/messages/test_step_started.hpp" #include "cucumber/messages/undefined_parameter_type.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include -#include #include #include +#include #include #include #include @@ -104,6 +106,11 @@ namespace cucumber_cpp::library::query } }; + struct NamingStrategy + { + static std::string Reduce(const Lineage& lineage, const cucumber::messages::pickle& pickle); + }; + struct Query : util::Broadcaster , util::Listener @@ -133,8 +140,7 @@ namespace cucumber_cpp::library::query const cucumber::messages::test_step& FindTestStepBy(const cucumber::messages::test_step_finished& testStepFinished) const; - const cucumber::messages::step& - FindStepBy(const cucumber::messages::pickle_step& pickleStep) const; + const cucumber::messages::step& FindStepBy(const cucumber::messages::pickle_step& pickleStep) const; const cucumber::messages::step_definition& FindStepDefinitionById(const std::string& id) const; @@ -143,8 +149,19 @@ namespace cucumber_cpp::library::query const std::map>& TestCaseStarted() const; const std::map>& TestCaseFinishedByTestCaseStartedId() const; + std::size_t CountTestCasesStarted() const; + std::map> CountMostSevereTestStepResultStatus() const; + std::list FindAllTestCaseStarted() const; + std::list> FindTestStepFinishedAndTestStepBy(const cucumber::messages::test_case_started& testCaseStarted) const; + + cucumber::messages::duration FindTestRunDuration() const; + + cucumber::messages::duration FindTestCaseDurationBy(const cucumber::messages::test_case_started& testCaseStarted) const; + cucumber::messages::duration FindTestCaseDurationBy(const cucumber::messages::test_case_finished& testCaseFinished) const; + cucumber::messages::duration FindTestCaseDurationBy(const cucumber::messages::test_case_started& testCaseStarted, const cucumber::messages::test_case_finished& testCaseFinished) const; + private: void operator+=(const cucumber::messages::envelope& envelope); diff --git a/cucumber_cpp/library/runtime/Coordinator.cpp b/cucumber_cpp/library/runtime/Coordinator.cpp index 79b91efc..59defbe4 100644 --- a/cucumber_cpp/library/runtime/Coordinator.cpp +++ b/cucumber_cpp/library/runtime/Coordinator.cpp @@ -4,9 +4,9 @@ #include "cucumber/messages/test_run_started.hpp" #include "cucumber_cpp/library/assemble/AssembleTestSuites.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include #include #include @@ -29,7 +29,7 @@ namespace cucumber_cpp::library::runtime bool Coordinator::Run() { broadcaster.BroadcastEvent({ .test_run_started = cucumber::messages::test_run_started{ - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), .id = std::string{ testRunStartedId }, } }); @@ -37,7 +37,7 @@ namespace cucumber_cpp::library::runtime broadcaster.BroadcastEvent({ .test_run_finished = cucumber::messages::test_run_finished{ .success = success, - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), .test_run_started_id = std::string{ testRunStartedId }, } }); diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index b1b4a601..0605ebc4 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -17,13 +17,13 @@ #include "cucumber/messages/test_step_started.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/support/Body.hpp" -#include "cucumber_cpp/library/support/Duration.hpp" #include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" #include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include #include #include @@ -82,7 +82,7 @@ namespace cucumber_cpp::library::runtime .attempt = attempt, .id = currentTestCaseStartedId, .test_case_id = testCase.id, - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), } }); bool seenSteps = false; @@ -93,7 +93,7 @@ namespace cucumber_cpp::library::runtime auto testStepStarted = cucumber::messages::test_step_started{ .test_case_started_id = currentTestCaseStartedId, .test_step_id = testStep.id, - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), }; broadcaster.BroadcastEvent({ .test_step_started = testStepStarted }); @@ -115,7 +115,7 @@ namespace cucumber_cpp::library::runtime .test_case_started_id = currentTestCaseStartedId, .test_step_id = testStep.id, .test_step_result = testStepResult, - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), } }); } @@ -123,7 +123,7 @@ namespace cucumber_cpp::library::runtime broadcaster.BroadcastEvent({ .test_case_finished = cucumber::messages::test_case_finished{ .test_case_started_id = currentTestCaseStartedId, - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), .will_be_retried = willRetry, } }); diff --git a/cucumber_cpp/library/runtime/Worker.cpp b/cucumber_cpp/library/runtime/Worker.cpp index 0c8c3620..adb95c9e 100644 --- a/cucumber_cpp/library/runtime/Worker.cpp +++ b/cucumber_cpp/library/runtime/Worker.cpp @@ -13,10 +13,10 @@ #include "cucumber_cpp/library/runtime/TestCaseRunner.hpp" #include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include #include #include @@ -153,7 +153,7 @@ namespace cucumber_cpp::library::runtime .id = testRunHookStartedId, .test_run_started_id = std::string{ testRunStartedId }, .hook_id = definition.hook.id, - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), }; broadcaster.BroadcastEvent({ .test_run_hook_started = testRunHookStarted }); @@ -163,7 +163,7 @@ namespace cucumber_cpp::library::runtime broadcaster.BroadcastEvent({ .test_run_hook_finished = cucumber::messages::test_run_hook_finished{ .test_run_hook_started_id = testRunHookStartedId, .result = result, - .timestamp = support::TimestampNow(), + .timestamp = util::TimestampNow(), } }); return result; diff --git a/cucumber_cpp/library/support/Body.cpp b/cucumber_cpp/library/support/Body.cpp index 4b366a24..9f375f93 100644 --- a/cucumber_cpp/library/support/Body.cpp +++ b/cucumber_cpp/library/support/Body.cpp @@ -3,7 +3,7 @@ #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/support/Duration.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" #include "gtest/gtest.h" #include #include @@ -52,7 +52,7 @@ namespace cucumber_cpp::library::support try { - support::Stopwatch::Instance().Start(); + util::Stopwatch::Instance().Start(); Execute(args); } catch (const StepSkipped& e) @@ -88,7 +88,7 @@ namespace cucumber_cpp::library::support }; } - auto nanoseconds = support::Stopwatch::Instance().Duration(); + auto nanoseconds = util::Stopwatch::Instance().Duration(); static constexpr std::size_t nanosecondsPerSecond = 1e9; testStepResult.duration = { .seconds = nanoseconds.count() / nanosecondsPerSecond, diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index 73fd046d..748c98b1 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -9,8 +9,6 @@ endif() target_sources(cucumber_cpp.library.support PRIVATE Body.cpp Body.hpp - Duration.cpp - Duration.hpp HookRegistry.cpp HookRegistry.hpp Join.cpp @@ -20,8 +18,6 @@ target_sources(cucumber_cpp.library.support PRIVATE StepType.hpp SupportCodeLibrary.cpp SupportCodeLibrary.hpp - Timestamp.cpp - Timestamp.hpp Types.hpp ) diff --git a/cucumber_cpp/library/support/Types.hpp b/cucumber_cpp/library/support/Types.hpp index 36f9255a..673d7793 100644 --- a/cucumber_cpp/library/support/Types.hpp +++ b/cucumber_cpp/library/support/Types.hpp @@ -7,11 +7,11 @@ #include "cucumber_cpp/library/tag_expression/Model.hpp" #include #include +#include #include #include #include #include -#include namespace cucumber_cpp::library::support { @@ -25,7 +25,7 @@ namespace cucumber_cpp::library::support struct Sources { - std::set paths{}; + std::set> paths{}; std::unique_ptr tagExpression; Ordering ordering{ Ordering::defined }; diff --git a/cucumber_cpp/library/util/CMakeLists.txt b/cucumber_cpp/library/util/CMakeLists.txt index b3ff55cc..df9d532f 100644 --- a/cucumber_cpp/library/util/CMakeLists.txt +++ b/cucumber_cpp/library/util/CMakeLists.txt @@ -3,9 +3,14 @@ add_library(cucumber_cpp.library.util ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.util PRIVATE Broadcaster.cpp Broadcaster.hpp - GetWorstTestStepResult.hpp + Duration.cpp + Duration.hpp GetWorstTestStepResult.cpp + GetWorstTestStepResult.hpp Immoveable.hpp + Timestamp.cpp + Timestamp.hpp + ) target_include_directories(cucumber_cpp.library.util PUBLIC diff --git a/cucumber_cpp/library/support/Duration.cpp b/cucumber_cpp/library/util/Duration.cpp similarity index 92% rename from cucumber_cpp/library/support/Duration.cpp rename to cucumber_cpp/library/util/Duration.cpp index 566ce826..5d44ed28 100644 --- a/cucumber_cpp/library/support/Duration.cpp +++ b/cucumber_cpp/library/util/Duration.cpp @@ -1,11 +1,11 @@ +#include "cucumber_cpp/library/util/Duration.hpp" #include "cucumber/messages/duration.hpp" -#include "cucumber_cpp/library/support/Duration.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include #include -namespace cucumber_cpp::library::support +namespace cucumber_cpp::library::util { namespace { diff --git a/cucumber_cpp/library/support/Duration.hpp b/cucumber_cpp/library/util/Duration.hpp similarity index 87% rename from cucumber_cpp/library/support/Duration.hpp rename to cucumber_cpp/library/util/Duration.hpp index 7e52f33b..dcc03bc4 100644 --- a/cucumber_cpp/library/support/Duration.hpp +++ b/cucumber_cpp/library/util/Duration.hpp @@ -1,10 +1,10 @@ -#ifndef SUPPORT_DURATION_HPP -#define SUPPORT_DURATION_HPP +#ifndef UTIL_DURATION_HPP +#define UTIL_DURATION_HPP #include "cucumber/messages/duration.hpp" #include -namespace cucumber_cpp::library::support +namespace cucumber_cpp::library::util { cucumber::messages::duration MillisecondsToDuration(std::chrono::milliseconds millis); @@ -40,7 +40,7 @@ namespace cucumber_cpp::library::support namespace cucumber::messages { - using cucumber_cpp::library::support::operator+=; + using cucumber_cpp::library::util::operator+=; }; #endif diff --git a/cucumber_cpp/library/support/Timestamp.cpp b/cucumber_cpp/library/util/Timestamp.cpp similarity index 92% rename from cucumber_cpp/library/support/Timestamp.cpp rename to cucumber_cpp/library/util/Timestamp.cpp index c5d61047..e41c7122 100644 --- a/cucumber_cpp/library/support/Timestamp.cpp +++ b/cucumber_cpp/library/util/Timestamp.cpp @@ -1,11 +1,11 @@ -#include "cucumber/messages/timestamp.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include "cucumber/messages/duration.hpp" -#include "cucumber_cpp/library/support/Duration.hpp" -#include "cucumber_cpp/library/support/Timestamp.hpp" +#include "cucumber/messages/timestamp.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" #include -namespace cucumber_cpp::library::support +namespace cucumber_cpp::library::util { namespace { diff --git a/cucumber_cpp/library/support/Timestamp.hpp b/cucumber_cpp/library/util/Timestamp.hpp similarity index 86% rename from cucumber_cpp/library/support/Timestamp.hpp rename to cucumber_cpp/library/util/Timestamp.hpp index 1b432490..3e111dbc 100644 --- a/cucumber_cpp/library/support/Timestamp.hpp +++ b/cucumber_cpp/library/util/Timestamp.hpp @@ -1,12 +1,12 @@ -#ifndef SUPPORT_TIMESTAMP_HPP -#define SUPPORT_TIMESTAMP_HPP +#ifndef UTIL_TIMESTAMP_HPP +#define UTIL_TIMESTAMP_HPP #include "cucumber/messages/duration.hpp" #include "cucumber/messages/timestamp.hpp" #include #include -namespace cucumber_cpp::library::support +namespace cucumber_cpp::library::util { constexpr std::size_t millisecondsPerSecond = 1e3; constexpr std::size_t nanosecondsPerMillisecond = 1e6; @@ -41,7 +41,7 @@ namespace cucumber_cpp::library::support namespace cucumber::messages { - using cucumber_cpp::library::support::operator-; + using cucumber_cpp::library::util::operator-; }; #endif From 022138e461279134c8d8d382a95bf79e11e35f7b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 16:31:22 +0000 Subject: [PATCH 129/196] refactor: improve GetUniqueFeatureName logic for better readability and efficiency --- cucumber_cpp/library/query/Query.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index d74081ac..4d3ef635 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -37,6 +37,7 @@ #include #include #include +#include #include #include #include @@ -80,7 +81,10 @@ namespace cucumber_cpp::library::query if (lineage.examples) parts.emplace_back(lineage.examples->name); - return std::accumulate(parts.begin(), parts.end(), std::string{}, [](const std::string& a, const std::string& b) + if (parts.size() == 1) + return parts.front(); + + return std::accumulate(std::next(parts.begin()), parts.end(), parts.front(), [](const std::string& a, const std::string& b) { if (b.empty()) return a; From 665103e585f64d080dfe60d4a33e7e0d995363c9 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 23:11:21 +0000 Subject: [PATCH 130/196] feat: add MessageFormatter to handle cucumber messages --- cucumber_cpp/library/api/Formatters.cpp | 2 ++ cucumber_cpp/library/formatter/CMakeLists.txt | 2 ++ .../library/formatter/MessageFormatter.cpp | 10 ++++++++ .../library/formatter/MessageFormatter.hpp | 24 +++++++++++++++++++ 4 files changed, 38 insertions(+) create mode 100644 cucumber_cpp/library/formatter/MessageFormatter.cpp create mode 100644 cucumber_cpp/library/formatter/MessageFormatter.hpp diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index 16f396af..d07de8e9 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -1,6 +1,7 @@ #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/JunitXmlFormatter.hpp" +#include "cucumber_cpp/library/formatter/MessageFormatter.hpp" #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" @@ -34,6 +35,7 @@ namespace cucumber_cpp::library::api RegisterFormatter(); RegisterFormatter(); RegisterFormatter(); + RegisterFormatter(); } std::set> Formatters::GetAvailableFormatterNames() const diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index 80d214d9..f080e625 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -5,6 +5,8 @@ target_sources(cucumber_cpp.library.formatter PRIVATE Formatter.hpp JunitXmlFormatter.cpp JunitXmlFormatter.hpp + MessageFormatter.cpp + MessageFormatter.hpp PrettyFormatter.cpp PrettyFormatter.hpp SummaryFormatter.cpp diff --git a/cucumber_cpp/library/formatter/MessageFormatter.cpp b/cucumber_cpp/library/formatter/MessageFormatter.cpp new file mode 100644 index 00000000..35ab691d --- /dev/null +++ b/cucumber_cpp/library/formatter/MessageFormatter.cpp @@ -0,0 +1,10 @@ +#include "cucumber_cpp/library/formatter/MessageFormatter.hpp" +#include "cucumber/messages/envelope.hpp" + +namespace cucumber_cpp::library::formatter +{ + void MessageFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) + { + outputStream << envelope.to_json() << "\n"; + } +} diff --git a/cucumber_cpp/library/formatter/MessageFormatter.hpp b/cucumber_cpp/library/formatter/MessageFormatter.hpp new file mode 100644 index 00000000..b7d69493 --- /dev/null +++ b/cucumber_cpp/library/formatter/MessageFormatter.hpp @@ -0,0 +1,24 @@ +#ifndef FORMATTER_MESSAGE_FORMATTER_HPP +#define FORMATTER_MESSAGE_FORMATTER_HPP + +#include "cucumber/messages/envelope.hpp" +#include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "nlohmann/json_fwd.hpp" +#include +#include + +namespace cucumber_cpp::library::formatter +{ + struct MessageFormatter + : Formatter + { + using Formatter::Formatter; + + constexpr static auto name = "message"; + + private: + void OnEnvelope(const cucumber::messages::envelope& envelope) override; + }; +} + +#endif From e717651af331f1aef77ff956618aeacd2c18cd32 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 23:12:24 +0000 Subject: [PATCH 131/196] feat: enhance JunitXmlFormatter and Query for improved test reporting and timestamp handling --- .../library/formatter/JunitXmlFormatter.cpp | 116 +++++++++++++++--- .../library/formatter/JunitXmlFormatter.hpp | 6 +- cucumber_cpp/library/query/Query.cpp | 67 +++++++--- cucumber_cpp/library/query/Query.hpp | 29 +++-- cucumber_cpp/library/util/Timestamp.cpp | 8 ++ cucumber_cpp/library/util/Timestamp.hpp | 6 +- 6 files changed, 182 insertions(+), 50 deletions(-) diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp index 490a722e..20973563 100644 --- a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp @@ -1,21 +1,58 @@ #include "cucumber_cpp/library/formatter/JunitXmlFormatter.hpp" -#include "cucumber/messages/duration.hpp" #include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/step.hpp" #include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/query/Query.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" +#include "cucumber_cpp/library/util/Timestamp.hpp" #include "nlohmann/json_fwd.hpp" #include "pugixml.hpp" +#include +#include #include +#include +#include +#include #include +#include #include +#include #include +#include namespace cucumber_cpp::library::formatter { namespace { + void ltrim(std::string& s) + { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) + { + return !std::isspace(ch); + })); + } + + void rtrim(std::string& s) + { + s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) + { + return !std::isspace(ch); + }) + .base(), + s.end()); + } + + void trim(std::string& s) + { + rtrim(s); + ltrim(s); + } + enum class FailureKind { failure, @@ -34,14 +71,14 @@ namespace cucumber_cpp::library::formatter { std::string classname; std::string name; - std::size_t time; + std::int64_t time; std::optional failure; std::string output; }; struct ReportSuite { - cucumber::messages::duration time; + std::int64_t time; std::size_t tests; std::size_t skipped; std::size_t failures; @@ -52,12 +89,60 @@ namespace cucumber_cpp::library::formatter std::optional MakeFailure(query::Query& query, const cucumber::messages::test_case_started& testCaseStarted) { - return std::nullopt; + const auto result = query.FindMostSevereTestStepResultBy(testCaseStarted); + if (!result.has_value() || result.value()->status == cucumber::messages::test_step_result_status::PASSED) + return std::nullopt; + + const auto& testStepResultStatus = result.value(); + + return std::optional{ + std::in_place, + testStepResultStatus->status == cucumber::messages::test_step_result_status::SKIPPED ? FailureKind::skipped : FailureKind::failure, + testStepResultStatus->exception ? std::make_optional(testStepResultStatus->exception->type) : std::nullopt, + testStepResultStatus->exception ? testStepResultStatus->exception->message : std::nullopt, + testStepResultStatus->exception ? testStepResultStatus->exception->stack_trace : testStepResultStatus->message, + }; + } + + std::string FormatStep(const cucumber::messages::step& gherkinStep, const cucumber::messages::pickle_step& pickleStep, cucumber::messages::test_step_result_status status) + { + auto statusString = std::string{ cucumber::messages::to_string(status) }; + std::transform(statusString.begin(), statusString.end(), statusString.begin(), [](unsigned char c) + { + return std::tolower(c); + }); + + auto keyword = gherkinStep.keyword; + auto text = pickleStep.text; + + trim(keyword); + trim(text); + + return std::format("{:.<76}{}", keyword + " " + text, statusString); } std::string MakeOutput(query::Query& query, const cucumber::messages::test_case_started& testCaseStarted) { - return ""; + const auto& testStepFinishedAndTestStep = query.FindTestStepFinishedAndTestStepBy(testCaseStarted); + auto outputView = testStepFinishedAndTestStep | + std::views::filter([](const std::pair& pair) + { + const auto [_, testStep] = pair; + return testStep->pickle_step_id.has_value(); + }) | + std::views::transform([&query](const std::pair& pair) + { + const auto [testStepFinished, testStep] = pair; + const auto& pickleStep = *query.FindPickleStepBy(*testStep); + const auto& gherkinStep = query.FindStepBy(pickleStep); + return FormatStep(gherkinStep, pickleStep, testStepFinished->test_step_result.status); + }); + + return "\n" + std::accumulate(std::next(outputView.begin()), outputView.end(), outputView.front(), [](const std::string& a, const std::string& b) + { + return a + "\n" + b; + }) + + "\n"; } std::list MakeTestCases(query::Query& query, const std::optional& testClassName) @@ -76,7 +161,10 @@ namespace cucumber_cpp::library::formatter : lineage.feature ? lineage.feature->name : pickle.uri, - query::NamingStrategy::Reduce(lineage, pickle), query.FindTestCaseDurationBy(*testCaseStartedPtr).seconds, MakeFailure(query, *testCaseStartedPtr), MakeOutput(query, *testCaseStartedPtr)); + query::NamingStrategy::Reduce(lineage, pickle), + util::DurationToMilliseconds(query.FindTestCaseDurationBy(*testCaseStartedPtr)).count(), + MakeFailure(query, *testCaseStartedPtr), + MakeOutput(query, *testCaseStartedPtr)); } return testCases; @@ -87,7 +175,7 @@ namespace cucumber_cpp::library::formatter const auto& statuses = query.CountMostSevereTestStepResultStatus(); return { - .time = query.FindTestRunDuration(), + .time = util::DurationToMilliseconds(query.FindTestRunDuration()).count(), .tests = query.CountTestCasesStarted(), .skipped = statuses.at(cucumber::messages::test_step_result_status::SKIPPED), .failures = statuses.at(cucumber::messages::test_step_result_status::UNKNOWN) + @@ -97,13 +185,14 @@ namespace cucumber_cpp::library::formatter statuses.at(cucumber::messages::test_step_result_status::FAILED), .errors = 0, .testCases = MakeTestCases(query, testClassName), + .timestamp = util::MakeIso8601Timestamp(query.FindTestRunStarted().timestamp), }; } } JunitXmlFormatter::Options::Options(const nlohmann::json& formatOptions) - : suiteName{ formatOptions.contains("junit") ? formatOptions.at("junit").value("suite_name", "Cucumber") : "Cucumber" } - , testClassName{ formatOptions.contains("junit") ? formatOptions.at("junit").contains("test_class_name") ? std::make_optional(formatOptions.at("junit")["test_class_name"].get()) : std::nullopt : std::nullopt } + : suiteName{ formatOptions.value("suite_name", "Cucumber") } + , testClassName{ formatOptions.contains("test_class_name") ? std::make_optional(formatOptions.at("test_class_name").get()) : std::nullopt } { } @@ -111,11 +200,10 @@ namespace cucumber_cpp::library::formatter { if (envelope.test_run_finished) { - pugi::xml_document doc; const auto& report = MakeReport(query, options.testClassName); - auto testSuite = doc.append_child("testsuite"); testSuite.append_attribute("name").set_value(options.suiteName.c_str()); + testSuite.append_attribute("time").set_value(report.time / 1000.0f); testSuite.append_attribute("tests").set_value(static_cast(report.tests)); testSuite.append_attribute("skipped").set_value(static_cast(report.skipped)); testSuite.append_attribute("failures").set_value(static_cast(report.failures)); @@ -129,7 +217,7 @@ namespace cucumber_cpp::library::formatter auto testCaseNode = testSuite.append_child("testcase"); testCaseNode.append_attribute("classname").set_value(testCase.classname.c_str()); testCaseNode.append_attribute("name").set_value(testCase.name.c_str()); - testCaseNode.append_attribute("time").set_value(testCase.time); + testCaseNode.append_attribute("time").set_value(testCase.time / 1000.0f); if (testCase.failure) { @@ -142,13 +230,13 @@ namespace cucumber_cpp::library::formatter failureNode.append_attribute("message").set_value(testCase.failure->message->c_str()); if (testCase.failure->stack) - failureNode.append_child(pugi::node_pcdata).set_value(testCase.failure->stack->c_str()); + failureNode.append_child(pugi::node_cdata).set_value(testCase.failure->stack->c_str()); } if (!testCase.output.empty()) { auto systemOutNode = testCaseNode.append_child("system-out"); - systemOutNode.append_child(pugi::node_pcdata).set_value(testCase.output.c_str()); + systemOutNode.append_child(pugi::node_cdata).set_value(testCase.output.c_str()); } } diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp index dc227440..a46298da 100644 --- a/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp @@ -4,6 +4,7 @@ #include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "nlohmann/json_fwd.hpp" +#include "pugixml.cpp" #include #include @@ -27,7 +28,10 @@ namespace cucumber_cpp::library::formatter void OnEnvelope(const cucumber::messages::envelope& envelope) override; - Options options{ formatOptions }; + Options options{ formatOptions.contains(name) ? formatOptions.at(name) : nlohmann::json::object() }; + + pugi::xml_document doc; + pugi::xml_node testSuite{ doc.append_child("testsuite") }; }; } diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index 4d3ef635..a5ab9fe9 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -27,6 +27,7 @@ #include "cucumber/messages/test_run_started.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber/messages/test_step_started.hpp" #include "cucumber/messages/undefined_parameter_type.hpp" @@ -42,6 +43,7 @@ #include #include #include +#include #include #include #include @@ -50,6 +52,20 @@ namespace cucumber_cpp::library::query { + namespace + { + struct SortByStatus + { + bool operator()(const std::pair& a, + const std::pair& b) const + { + const auto& statusA = a.first->test_step_result.status; + const auto& statusB = b.first->test_step_result.status; + return static_cast(statusA) < static_cast(statusB); + } + }; + } + std::string Lineage::GetUniqueFeatureName() const { return std::format("{}/{}", feature->name, featureIndex); @@ -197,17 +213,6 @@ namespace cucumber_cpp::library::query std::map> Query::CountMostSevereTestStepResultStatus() const { - struct SortByStatus - { - bool operator()(const std::pair& a, - const std::pair& b) const - { - const auto& statusA = a.first->test_step_result.status; - const auto& statusB = b.first->test_step_result.status; - return static_cast(statusA) < static_cast(statusB); - } - }; - std::map> result{ { cucumber::messages::test_step_result_status::UNKNOWN, 0 }, { cucumber::messages::test_step_result_status::PASSED, 0 }, @@ -228,6 +233,23 @@ namespace cucumber_cpp::library::query return result; } + std::optional Query::FindMostSevereTestStepResultBy(const cucumber::messages::test_case_started& testCaseStarted) const + { + auto list = FindTestStepFinishedAndTestStepBy(testCaseStarted); + if (list.empty()) + return std::nullopt; + + list.sort(SortByStatus{}); + + const auto [testStepFinished, testStep] = list.back(); + return &testStepFinished->test_step_result; + } + + std::optional Query::FindMostSevereTestStepResultBy(const cucumber::messages::test_case_finished& testCaseFinished) const + { + return FindMostSevereTestStepResultBy(testCaseStartedById.at(testCaseFinished.test_case_started_id)); + } + std::list Query::FindAllTestCaseStarted() const { auto view = testCaseStartedById | @@ -253,6 +275,11 @@ namespace cucumber_cpp::library::query return { view.begin(), view.end() }; } + const cucumber::messages::test_run_started& Query::FindTestRunStarted() const + { + return *testRunStarted; + } + cucumber::messages::duration Query::FindTestRunDuration() const { return testRunFinished->timestamp - testRunStarted->timestamp; @@ -383,7 +410,7 @@ namespace cucumber_cpp::library::query if (testStep.pickle_step_id) { pickleStepIdByTestStepId[testStep.id] = testStep.pickle_step_id.value(); - testStepIdsByPickleStepId[testStep.pickle_step_id.value()].push_front(testStep.id); + testStepIdsByPickleStepId[testStep.pickle_step_id.value()].push_back(testStep.id); if (testStep.step_match_arguments_lists) stepMatchArgumentsListsByPickleStepId[testStep.pickle_step_id.value()] = testStep.step_match_arguments_lists.value(); @@ -400,31 +427,31 @@ namespace cucumber_cpp::library::query void Query::operator+=(const cucumber::messages::test_step_started& testStepStarted) { - testStepStartedByTestCaseStartedId[testStepStarted.test_case_started_id].push_front(testStepStarted); + testStepStartedByTestCaseStartedId[testStepStarted.test_case_started_id].push_back(testStepStarted); } void Query::operator+=(const cucumber::messages::attachment& attachment) { auto* ptr = &attachments.emplace_front(attachment); if (attachment.test_step_id) - attachmentsByTestStepId[attachment.test_step_id.value()].push_front(ptr); + attachmentsByTestStepId[attachment.test_step_id.value()].push_back(ptr); if (attachment.test_case_started_id) - attachmentsByTestCaseStartedId[attachment.test_case_started_id.value()].push_front(ptr); + attachmentsByTestCaseStartedId[attachment.test_case_started_id.value()].push_back(ptr); } void Query::operator+=(const cucumber::messages::test_step_finished& testStepFinished) { auto* testStepResultPtr = &testStepResults.emplace_front(testStepFinished.test_step_result); - testStepFinishedByTestCaseStartedId[testStepFinished.test_case_started_id].push_front(testStepFinished); + testStepFinishedByTestCaseStartedId[testStepFinished.test_case_started_id].push_back(testStepFinished); const auto& pickleId = pickleIdByTestStepId.at(testStepFinished.test_step_id); - testStepResultByPickleId[pickleId].push_front(testStepResultPtr); + testStepResultByPickleId[pickleId].push_back(testStepResultPtr); const auto& testStep = testStepById.at(testStepFinished.test_step_id); - testStepResultsbyTestStepId[testStep.id].push_front(testStepResultPtr); + testStepResultsbyTestStepId[testStep.id].push_back(testStepResultPtr); if (testStep.pickle_step_id) - testStepResultsByPickleStepId[testStep.pickle_step_id.value()].push_front(testStepResultPtr); + testStepResultsByPickleStepId[testStep.pickle_step_id.value()].push_back(testStepResultPtr); } void Query::operator+=(const cucumber::messages::test_case_finished& testCaseFinished) @@ -439,7 +466,7 @@ namespace cucumber_cpp::library::query void Query::operator+=(const cucumber::messages::suggestion& suggestion) { - suggestionsByPickleStepId[suggestion.pickle_step_id].push_front(suggestion); + suggestionsByPickleStepId[suggestion.pickle_step_id].push_back(suggestion); } void Query::operator+=(const cucumber::messages::undefined_parameter_type& undefinedParameterType) diff --git a/cucumber_cpp/library/query/Query.hpp b/cucumber_cpp/library/query/Query.hpp index fe1408f8..b524c2a1 100644 --- a/cucumber_cpp/library/query/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -36,11 +36,11 @@ #include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include -#include #include #include #include #include +#include #include #include #include @@ -152,10 +152,13 @@ namespace cucumber_cpp::library::query std::size_t CountTestCasesStarted() const; std::map> CountMostSevereTestStepResultStatus() const; + std::optional FindMostSevereTestStepResultBy(const cucumber::messages::test_case_started& testCaseStarted) const; + std::optional FindMostSevereTestStepResultBy(const cucumber::messages::test_case_finished& testCaseFinished) const; std::list FindAllTestCaseStarted() const; std::list> FindTestStepFinishedAndTestStepBy(const cucumber::messages::test_case_started& testCaseStarted) const; + const cucumber::messages::test_run_started& FindTestRunStarted() const; cucumber::messages::duration FindTestRunDuration() const; cucumber::messages::duration FindTestCaseDurationBy(const cucumber::messages::test_case_started& testCaseStarted) const; @@ -192,21 +195,21 @@ namespace cucumber_cpp::library::query std::map> featureCountByName; - std::forward_list testStepResults; - std::map, std::less<>> testStepResultByPickleId; - std::map, std::less<>> testStepResultsByPickleStepId; - std::map, std::less<>> testStepResultsbyTestStepId; + std::list testStepResults; + std::map, std::less<>> testStepResultByPickleId; + std::map, std::less<>> testStepResultsByPickleStepId; + std::map, std::less<>> testStepResultsbyTestStepId; std::map> testCaseById; std::map> testCaseByPickleId; std::map> pickleIdByTestStepId; std::map> pickleStepIdByTestStepId; - std::map, std::less<>> testStepIdsByPickleStepId; + std::map, std::less<>> testStepIdsByPickleStepId; std::map> hooksById; - std::forward_list attachments; - std::map, std::less<>> attachmentsByTestStepId; - std::map, std::less<>> attachmentsByTestCaseStartedId; + std::list attachments; + std::map, std::less<>> attachmentsByTestStepId; + std::map, std::less<>> attachmentsByTestCaseStartedId; // std::map>, std::less<>> attachmentsByTestRunHookStartedId; std::map, std::less<>> stepMatchArgumentsListsByPickleStepId; @@ -228,11 +231,11 @@ namespace cucumber_cpp::library::query std::map> testStepById; std::map> testRunHookStartedById; std::map> testRunHookFinishedByTestRunHookStartedId; - std::map, std::less<>> testStepStartedByTestCaseStartedId; - std::map, std::less<>> testStepFinishedByTestCaseStartedId; + std::map, std::less<>> testStepStartedByTestCaseStartedId; + std::map, std::less<>> testStepFinishedByTestCaseStartedId; - std::map, std::less<>> suggestionsByPickleStepId; - std::forward_list undefinedParameterTypes; + std::map, std::less<>> suggestionsByPickleStepId; + std::list undefinedParameterTypes; std::map> parameterTypeById; std::map> parameterTypeByName; diff --git a/cucumber_cpp/library/util/Timestamp.cpp b/cucumber_cpp/library/util/Timestamp.cpp index e41c7122..dfe959b1 100644 --- a/cucumber_cpp/library/util/Timestamp.cpp +++ b/cucumber_cpp/library/util/Timestamp.cpp @@ -4,6 +4,8 @@ #include "cucumber/messages/timestamp.hpp" #include "cucumber_cpp/library/util/Duration.hpp" #include +#include +#include namespace cucumber_cpp::library::util { @@ -58,4 +60,10 @@ namespace cucumber_cpp::library::util const auto durationMillis = TimestampToMillis(lhs) - TimestampToMillis(rhs); return MillisecondsToDuration(durationMillis); } + + std::string MakeIso8601Timestamp(const cucumber::messages::timestamp& timestamp) + { + const std::chrono::system_clock::time_point tp{ std::chrono::seconds(timestamp.seconds) + std::chrono::nanoseconds(timestamp.nanos) }; + return std::format("{:%FT%T%Z}", tp); + } } diff --git a/cucumber_cpp/library/util/Timestamp.hpp b/cucumber_cpp/library/util/Timestamp.hpp index 3e111dbc..a4ab865a 100644 --- a/cucumber_cpp/library/util/Timestamp.hpp +++ b/cucumber_cpp/library/util/Timestamp.hpp @@ -5,6 +5,7 @@ #include "cucumber/messages/timestamp.hpp" #include #include +#include namespace cucumber_cpp::library::util { @@ -33,10 +34,11 @@ namespace cucumber_cpp::library::util std::chrono::milliseconds Now() override; }; - cucumber::messages::timestamp - TimestampNow(); + cucumber::messages::timestamp TimestampNow(); cucumber::messages::duration operator-(const cucumber::messages::timestamp& lhs, const cucumber::messages::timestamp& rhs); + + std::string MakeIso8601Timestamp(const cucumber::messages::timestamp& timestamp); } namespace cucumber::messages From 9329317b00b0cc2b2ae96826bf721e6156da3451 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 23:27:57 +0000 Subject: [PATCH 132/196] fix: update FindLocationOf to return location from tableRow if available --- cucumber_cpp/library/query/Query.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index a5ab9fe9..6995470c 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -192,7 +192,8 @@ namespace cucumber_cpp::library::query const cucumber::messages::location& Query::FindLocationOf(const cucumber::messages::pickle& pickle) const { const auto& lineage = FindLineageByUri(pickle.uri); - // if (lineage.examples) + if (lineage.tableRow) + return lineage.tableRow->location; return lineage.scenario->location; } From 6ee3064dfdb6aa71696b48b8cb4114c8b6a879e6 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 23:28:05 +0000 Subject: [PATCH 133/196] refactor: add comments for future implementation of step argument formatting and attachment printing --- .../helper/TestCaseAttemptFormatter.cpp | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp index 7007d3e0..17664554 100644 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp +++ b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp @@ -60,9 +60,21 @@ namespace cucumber_cpp::library::formatter::helper text += '\n'; if (testStep.argument) - ; // indent 4 FormatStepArgument append \n - - // indent 4 print attachments append \n + // const argumentsText = formatStepArgument(testStep.argument) + // text += indentString(`${colorFn(argumentsText)}\n`, 4) + ; + + // if (valueOrDefault(printAttachments, true)) { + // attachments.forEach(({ body, mediaType, fileName }) => { + // let message = '' + // if (mediaType === 'text/plain') { + // message = `: ${body}` + // } else if (fileName) { + // message = `: ${fileName}` + // } + // text += indentString(`Attachment (${mediaType})${message}\n`, 4) + // }) + // } auto message = GetStepMessage(testStep); if (!message.empty()) From 412b7813a1407c3c606b4d2ef104080670e8d88e Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 16 Jan 2026 23:31:47 +0000 Subject: [PATCH 134/196] feat: update test runner to include message format for improved output consistency --- cucumber_cpp/acceptance_test/test.bats | 56 +++++++++++++------------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index e9467562..dcfa85e2 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -15,53 +15,53 @@ teardown() { } @test "Successful test" { - run $acceptance_test --format summary pretty --tags "@result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Parse tag expression" { - run $acceptance_test --format summary pretty --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@smoke and @result:OK" -- cucumber_cpp/acceptance_test/features assert_success } @test "Failed tests" { - run $acceptance_test --format summary pretty --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure } @test "Undefined tests" { - run $acceptance_test --format summary pretty --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "UNDEFINED Given a missing step" assert_output --partial "SKIPPED Then this should be skipped" } @test "No tests" { - run $acceptance_test --format summary pretty --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@invalidtag" -- cucumber_cpp/acceptance_test/features assert_success } @test "All features in a folder" { - run $acceptance_test --format summary pretty -- cucumber_cpp/acceptance_test/features/subfolder + run $acceptance_test --format summary pretty message junit -- cucumber_cpp/acceptance_test/features/subfolder assert_success assert_output --partial "2 scenarios" assert_output --partial "2 passed" } @test "Missing mandatory paths argument" { - run $acceptance_test --format summary pretty -- + run $acceptance_test --format summary pretty message junit -- assert_failure assert_output --partial "paths is required" } @test "Missing mandatory custom argument" { - run $acceptance_test.custom --format summary pretty -- cucumber_cpp/acceptance_test/features + run $acceptance_test.custom --format summary pretty message junit -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "--required is required" } @test "Second feature file does not overwrite success with an undefined status" { - run $acceptance_test --format summary pretty --tags @undefinedsuccess -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature + run $acceptance_test --format summary pretty message junit --tags @undefinedsuccess -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature assert_failure } @@ -72,7 +72,7 @@ teardown() { } @test "Run Program hooks" { - run $acceptance_test --format summary pretty --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags @bats and @program_hooks -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_ALL" @@ -80,7 +80,7 @@ teardown() { } @test "Run Scenario hooks" { - run $acceptance_test --format summary pretty --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags @bats and @scenariohook and not @stephook -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -91,7 +91,7 @@ teardown() { } @test "Run Step hooks" { - run $acceptance_test --format summary pretty --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags @bats and @stephook and not @scenariohook -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "HOOK_BEFORE_SCENARIO" @@ -102,7 +102,7 @@ teardown() { } @test "Run Scenario and Step hooks" { - run $acceptance_test --format summary pretty --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@bats and (@scenariohook or @stephook)" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "HOOK_BEFORE_SCENARIO" @@ -113,70 +113,70 @@ teardown() { } @test "Dry run with known failing steps" { - run $acceptance_test --format summary pretty --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test --format summary pretty --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@result:FAILED" --dry-run cucumber_cpp/acceptance_test/features assert_success } @test "Dry run with known missing steps" { - run $acceptance_test --format summary pretty --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test --format summary pretty --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features assert_success assert_output --partial "UNDEFINED Given a missing step" } @test "Test the and keyword" { - run $acceptance_test --format summary pretty --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@keyword-and" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--and--" } @test "Test the but keyword" { - run $acceptance_test --format summary pretty --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@keyword-but" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "--when--" assert_output --partial "--but--" } @test "Test the asterisk keyword" { - run $acceptance_test --format summary pretty --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@keyword-asterisk" -- cucumber_cpp/acceptance_test/features assert_output --partial "print: --when--" assert_output --partial "print: --asterisk--" assert_success } @test "Test passing scenario after failed scenario reports feature as failed" { - run $acceptance_test --format summary pretty --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@fail_feature" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "2 scenarios" assert_output --partial "1 passed" } @test "Test failing hook before results in error" { - run $acceptance_test --format summary pretty --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED Before" } @test "Test failing hook after results in error" { - run $acceptance_test --format summary pretty --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED After" } @test "Test throwing hook results in error" { - run $acceptance_test --format summary pretty --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "FAILED Before" } @test "Test error program hook results in error and skipped steps" { - run $acceptance_test.custom --format summary pretty --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features + run $acceptance_test.custom --format summary pretty message junit --tags "@smoke and @result:OK" --required --failprogramhook cucumber_cpp/acceptance_test/features assert_failure assert_output --partial "HOOK_BEFORE_ALL" assert_output --partial "HOOK_AFTER_ALL" @@ -185,21 +185,21 @@ teardown() { } @test "Test unicode" { - run $acceptance_test --format summary pretty --tags "@unicode" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@unicode" -- cucumber_cpp/acceptance_test/features assert_success assert_output --partial "1 scenario" assert_output --partial "1 passed" } # @test "Test unused step reporting" { -# run $acceptance_test --format summary pretty --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features +# run $acceptance_test --format summary pretty message junit --tags "@unused_steps" --report cucumber_cpp/acceptance_test/features # assert_success # assert_output --regexp ".*The following steps have not been used:.*this step is not being used.*" # refute_output --regexp ".*The following steps have not been used:.*this step is being used.*" # } @test "Test unused steps by default not reported" { - run $acceptance_test --format summary pretty --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@unused_steps" -- cucumber_cpp/acceptance_test/features assert_success refute_output --partial "The following steps have not been used:" } From b1c6aa0edfa64cda9fd5988f0aa99938c35b9deb Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 00:39:52 +0000 Subject: [PATCH 135/196] feat: add custom fixture support with LoadMeOnConstruction for enhanced step definitions --- cucumber_cpp/example/steps/Steps.cpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/cucumber_cpp/example/steps/Steps.cpp b/cucumber_cpp/example/steps/Steps.cpp index 111f1025..40bef16a 100644 --- a/cucumber_cpp/example/steps/Steps.cpp +++ b/cucumber_cpp/example/steps/Steps.cpp @@ -1,10 +1,29 @@ #include "cucumber_cpp/library/Steps.hpp" +#include "cucumber_cpp/CucumberCpp.hpp" #include "cucumber_cpp/library/Context.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" #include #include +struct LoadMeOnConstruction +{ + void Tadaa() const + {} +}; + +struct CustomFixture : cucumber_cpp::library::engine::Step +{ + using Step::Step; + + const LoadMeOnConstruction& alwaysAvailable{ context.Get() }; +}; + +GIVEN_F(CustomFixture, R"(a custom fixture background step)") +{ + alwaysAvailable.Tadaa(); +} + GIVEN(R"(a background step)") { /* no body, example only */ From ff480c963b838f9dc4cc3b4eb9093f0da97e5ea3 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 00:40:09 +0000 Subject: [PATCH 136/196] feat: implement byte and short parameter converters for improved type handling in expressions --- .../cucumber_expression/ParameterRegistry.cpp | 14 ++++++++++++-- .../cucumber_expression/test/TestExpression.cpp | 4 ++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index 54feb15d..f67c7cf0 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -45,6 +45,16 @@ namespace cucumber_cpp::library::cucumber_expression return str; }; } + + std::function CreateByteConverter() + { + return [](const cucumber::messages::group& matches) -> std::int8_t + { + if (matches.value.has_value()) + return static_cast(StringTo(matches.value.value())); + return {}; + }; + } } std::strong_ordering CustomParameterEntry::operator<=>(const CustomParameterEntry& other) const @@ -67,8 +77,8 @@ namespace cucumber_cpp::library::cucumber_expression AddBuiltinParameter("", { ".*" }, CreateStreamConverter()); AddBuiltinParameter("bigdecimal", { floatRegex }, CreateStreamConverter()); AddBuiltinParameter("biginteger", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); - AddBuiltinParameter("byte", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); - AddBuiltinParameter("short", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); + AddBuiltinParameter("byte", { { integerNegativeRegex, integerPositiveRegex } }, CreateByteConverter()); + AddBuiltinParameter("short", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); AddBuiltinParameter("long", { { integerNegativeRegex, integerPositiveRegex } }, CreateStreamConverter()); AddBuiltinParameter("double", { floatRegex }, CreateStreamConverter()); diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index 57a772d5..367b7fed 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -111,10 +111,10 @@ namespace cucumber_cpp::library::cucumber_expression EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "byte") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "short") - EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); + EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); else if (argument.Name() == "long") EXPECT_THAT(argument.GetValue(), testdata["expected_args"][i].as()) << FormatTestFailureMessage(file, testdata, expression); From 1e84a7a13d82bc4b4d295e0dd61a92609f16435f Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 00:40:15 +0000 Subject: [PATCH 137/196] feat: expand README with detailed usage instructions and custom parameter types --- README.md | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 140 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 5a026dd7..7c0b777a 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,148 @@ amp-cucumber-cpp-runner is known to build using: amp-cucumber-cpp-runner is not supposed to be used standalone. It is possible to add amp-cucumber-cpp-runner as a submodule to your own project or to use cmake's FetchContent to add amp-cucumber-cpp-runner as a cmake build dependency. En example project is provided which shows most features of amp-cucumber-cpp-runner and how to configure a cmake project to use amp-cucumber-cpp-runner. The simplest solution is to simply add a dependency on `cucumber-cpp-runner` like so: -`cmake -target_link_libraries(cucumber_cpp.example PRIVATE + +```cmake +FetchContent_Declare(cucumber_cpp + GIT_REPOSITORY https://github.com/philips-software/amp-cucumber-cpp-runner.git + GIT_TAG main +) + +FetchContent_MakeAvailable(cucumber_cpp) + +add_executable(my_test_runner) +target_sources(my_test_runner PRIVATE + steps/mysteps.cpp + hooks/myhooks.cpp +) + +target_link_libraries(my_test_runner PRIVATE cucumber_cpp.runner ) -` +``` + +## Writing step definitions + +Steps are written using the `GIVEN`, `WHEN`, `THEN` or `STEP` macro's. The syntax for step definitions is: + +```cpp +// using cucumber expressions + +STEP("cucumber expression without captures"){ + // body +} + +STEP("cucumber expression with an {int}", (int arg)){ + // body +} + +// or as a regular expression: + +STEP(R"(^a regular\?? expression without captures$)"){ + +} + +STEP(R"(^a (regular|unregular) expression with ([1-2]) capture groups$)", (const std::string& type, const std::string& nrOfGroups)){ + +} +``` + +### Builtin Parameter Types + +The argument list has to match with the cucumber expression type: + +| Cucumber Expression | Parsed as | Notes | +|---------------------|----------------|----------------------------------| +| `{int}` | `std::int32_t` | | +| `{float}` | `float` | | +| `{word}` | `std::string` | words without whitespace | +| `{string}` | `std::string` | "banana split" or 'banana split' | +| `{}` | `std::string` | | +| `{bigdecimal}` | `double` | non-standard | +| `{biginteger}` | `std::int64_t` | non-standard | +| `{byte}` | `std::int8_t` | | +| `{short}` | `std::int16_t` | | +| `{long}` | `std::int64_t` | | +| `{double}` | `double` | | + + +See the [Cucumber Expressions documentation](https://github.com/cucumber/cucumber-expressions#readme) for more details on supported types. amp-cucumber-cpp-runner has full support for cucumber expressions. + +> ℹ️ except for biginteger, but it's optional + +### Custom Parameter Types + +There is also support for custom parameter types. They are written and used like: + +```cpp +struct Flight +{ + std::string from; + std::string to; +}; + +PARAMETER(Flight, "flight", "([A-Z]{3})-([A-Z]{3})", true) +{ + return { group.children[0].value.value(), group.children[1].value.value() }; +} + +STEP(R"({flight} has been delayed)", (const Flight& flight)) +{ + EXPECT_THAT(flight.from, testing::StrEq("LHR")); + EXPECT_THAT(flight.to, testing::StrEq("CDG")); +} +``` + +The `PARAMETER` macro API is as follows: +``` +PARAMETER(, , , ) +``` +> ℹ️ \ is not supported yet + +The `PARAMETER` function body receives an [`const cucumber::messages::group& group`](https://github.com/cucumber/messages/blob/37af3aa8c54ccaf4e61c7cd168d05e39a19c1959/cpp/include/messages/cucumber/messages/group.hpp#L21) as argument. + +### Custom step fixtures + +amp-cucumber-cpp-runner has support for custom base classes for step definitions using the `GIVEN_F`, `WHEN_F`, `THEN_F` or `STEP_F` macros. These give additional flexibility for re-using common patterns for steps. For example: + +```cpp +struct LoadMeOnConstruction +{ + void Tadaa() const + {} +}; + +struct CustomFixture : cucumber_cpp::library::engine::Step +{ + using Step::Step; + + const LoadMeOnConstruction& alwaysAvailable{ context.Get() }; +}; + +GIVEN_F(CustomFixture, R"(a custom fixture background step)") +{ + alwaysAvailable.Tadaa(); +} +``` + +### Assertion Framework + +amp-cucumber-cpp-runner uses GoogleTest and GoogleMock as the basis for the assertion framework. For example: +```cpp +STEP(R"(the value should be {string})", (const std::string& expected_value)) +{ + const auto& actual = context.Get("cell"); + + ASSERT_THAT(actual, testing::StrEq(expected_value)); +} +``` + +If asserting (or expecting) is not an option then it is also safe to throw exceptions. + +## Writing hook definitions + +\<\> + ## How to test the software From 0c38290b219a6adfc4719ea486f4873796982955 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 00:42:13 +0000 Subject: [PATCH 138/196] fix: update step definition to use std::int32_t for improved type consistency --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 7c0b777a..298a2173 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ STEP("cucumber expression without captures"){ // body } -STEP("cucumber expression with an {int}", (int arg)){ +STEP("cucumber expression with an {int}", (std::int32_t arg)){ // body } From 19d145df43280329c101a2690516a88f42bf8a46 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 21:55:03 +0000 Subject: [PATCH 139/196] fix: improve MakeIso8601Timestamp by using duration_cast for better time point conversion --- cucumber_cpp/library/util/Timestamp.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/util/Timestamp.cpp b/cucumber_cpp/library/util/Timestamp.cpp index dfe959b1..5b8d49b4 100644 --- a/cucumber_cpp/library/util/Timestamp.cpp +++ b/cucumber_cpp/library/util/Timestamp.cpp @@ -63,7 +63,8 @@ namespace cucumber_cpp::library::util std::string MakeIso8601Timestamp(const cucumber::messages::timestamp& timestamp) { - const std::chrono::system_clock::time_point tp{ std::chrono::seconds(timestamp.seconds) + std::chrono::nanoseconds(timestamp.nanos) }; + const auto duration = std::chrono::duration_cast(std::chrono::seconds(timestamp.seconds) + std::chrono::nanoseconds(timestamp.nanos)); + const std::chrono::system_clock::time_point tp{ duration }; return std::format("{:%FT%T%Z}", tp); } } From 0110450d39618b85d73460d53f4bab9acdaf0a86 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 21:55:11 +0000 Subject: [PATCH 140/196] fix: correct include directive for pugixml in JunitXmlFormatter --- cucumber_cpp/library/formatter/JunitXmlFormatter.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp index a46298da..0f6c3d9a 100644 --- a/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.hpp @@ -4,7 +4,7 @@ #include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "nlohmann/json_fwd.hpp" -#include "pugixml.cpp" +#include "pugixml.hpp" #include #include From b2f4b23b97963ad50062b6420caf5e45a3b032f8 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 21:55:15 +0000 Subject: [PATCH 141/196] fix: update CMakeLists.txt to specify pugixml as a target and comment out fmt-header-only --- cucumber_cpp/library/formatter/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index f080e625..cd1c4b1d 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -25,8 +25,8 @@ target_link_libraries(cucumber_cpp.library.formatter PUBLIC cucumber_cpp.library.support cucumber_cpp.library.util nlohmann_json::nlohmann_json - pugixml - fmt-header-only + pugixml::pugixml + # fmt-header-only ) add_subdirectory(helper) From c3454bc18f6f7211e4113033a5b88eb82ecf29e7 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 22:06:28 +0000 Subject: [PATCH 142/196] fix: conditionally include yaml-cpp and jbeder based on CCR_BUILD_TESTS option --- CMakeLists.txt | 5 ++++- external/CMakeLists.txt | 4 +++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index be46cc5c..77e9780a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,8 +69,11 @@ else() find_package(pugixml REQUIRED) find_package(cucumber_messages REQUIRED) find_package(cucumber_gherkin REQUIRED) - find_package(yaml-cpp REQUIRED) find_package(cpp-terminal REQUIRED) + + if (CCR_BUILD_TESTS) + find_package(yaml-cpp REQUIRED) + endif() endif() add_subdirectory(cucumber_cpp) diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 26e1b31e..2a55863a 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -7,7 +7,9 @@ add_subdirectory(cliutils) add_subdirectory(cucumber) add_subdirectory(fmtlib) add_subdirectory(google) -add_subdirectory(jbeder) +if (CCR_BUILD_TESTS) + add_subdirectory(jbeder) +endif() add_subdirectory(jupyter-xeus) add_subdirectory(tobiaslocker) add_subdirectory(zeux) From ef974a269d409d04f757509234e5d4f16d36eecc Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sat, 17 Jan 2026 23:51:41 +0000 Subject: [PATCH 143/196] swap cpp-terminal with fmt --- CMakeLists.txt | 2 +- cucumber_cpp/library/formatter/CMakeLists.txt | 3 +- .../library/formatter/PrettyFormatter.cpp | 18 +++++------ .../library/formatter/SummaryFormatter.cpp | 5 ++- .../library/formatter/helper/CMakeLists.txt | 2 +- .../formatter/helper/GetColorFunctions.cpp | 32 +++++++++---------- .../library/formatter/helper/IssueHelpers.cpp | 8 ++--- external/CMakeLists.txt | 1 - external/jupyter-xeus/CMakeLists.txt | 1 - .../jupyter-xeus/cpp-terminal/CMakeLists.txt | 21 ------------ 10 files changed, 33 insertions(+), 60 deletions(-) delete mode 100644 external/jupyter-xeus/CMakeLists.txt delete mode 100644 external/jupyter-xeus/cpp-terminal/CMakeLists.txt diff --git a/CMakeLists.txt b/CMakeLists.txt index 77e9780a..dede778e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,7 +69,7 @@ else() find_package(pugixml REQUIRED) find_package(cucumber_messages REQUIRED) find_package(cucumber_gherkin REQUIRED) - find_package(cpp-terminal REQUIRED) + find_package(fmt REQUIRED) if (CCR_BUILD_TESTS) find_package(yaml-cpp REQUIRED) diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index cd1c4b1d..ea60ba1f 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -18,15 +18,14 @@ target_include_directories(cucumber_cpp.library.formatter PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter PUBLIC - cpp-terminal cucumber_cpp.library.cucumber_expression cucumber_cpp.library.formatter.helper cucumber_cpp.library.query cucumber_cpp.library.support cucumber_cpp.library.util + fmt-header-only nlohmann_json::nlohmann_json pugixml::pugixml - # fmt-header-only ) add_subdirectory(helper) diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index c0601580..0d9528b1 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -18,7 +18,7 @@ #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" #include "cucumber_cpp/library/support/Join.hpp" -#include "cucumber_cpp/library/support/Polyfill.hpp" +#include "fmt/ostream.h" #include "nlohmann/json_fwd.hpp" #include #include @@ -182,7 +182,7 @@ namespace cucumber_cpp::library::formatter const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(attachment.test_case_started_id.value()); const auto content = FormatAttachment(attachment); - support::print(outputStream, "{:{}}{}\n", "", scenarioIndent + gherkinIndentLength + attachmentIndentLength, content); + fmt::print(outputStream, "{:{}}{}\n", "", scenarioIndent + gherkinIndentLength + attachmentIndentLength, content); } void PrettyFormatter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) @@ -205,9 +205,9 @@ namespace cucumber_cpp::library::formatter void PrettyFormatter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) { if (testRunFinished.exception && testRunFinished.exception->stack_trace) - support::print(outputStream, "{}\n", helper::ColorFunctions::ForStatus(cucumber::messages::test_step_result_status::FAILED)(testRunFinished.exception->stack_trace.value())); + fmt::print(outputStream, "{}\n", helper::ColorFunctions::ForStatus(cucumber::messages::test_step_result_status::FAILED)(testRunFinished.exception->stack_trace.value())); else if (testRunFinished.exception && testRunFinished.exception->message) - support::print(outputStream, "{}\n", helper::ColorFunctions::ForStatus(cucumber::messages::test_step_result_status::FAILED)(testRunFinished.exception->message.value())); + fmt::print(outputStream, "{}\n", helper::ColorFunctions::ForStatus(cucumber::messages::test_step_result_status::FAILED)(testRunFinished.exception->message.value())); } void PrettyFormatter::PrintFeatureLine(const cucumber::messages::feature& feature) @@ -215,7 +215,7 @@ namespace cucumber_cpp::library::formatter if (printedFeatureUris.contains(&feature)) return; - support::print(outputStream, "{}: {}\n", feature.keyword, feature.name); + fmt::print(outputStream, "{}: {}\n", feature.keyword, feature.name); printedFeatureUris.insert(&feature); } @@ -224,7 +224,7 @@ namespace cucumber_cpp::library::formatter if (printedRuleIds.contains(&rule)) return; - support::print(outputStream, "{:{}}{}: {}\n", "", 2, rule.keyword, rule.name); + fmt::print(outputStream, "{:{}}{}: {}\n", "", 2, rule.keyword, rule.name); printedRuleIds.insert(&rule); } @@ -238,7 +238,7 @@ namespace cucumber_cpp::library::formatter return tag.name; }); std::vector tagVec{ tags.begin(), tags.end() }; - support::print(outputStream, "{:{}}{}\n", "", scenarioIndent, helper::ColorFunctions::Tag(support::Join(tagVec, " "))); + fmt::print(outputStream, "{:{}}{}\n", "", scenarioIndent, helper::ColorFunctions::Tag(support::Join(tagVec, " "))); } void PrettyFormatter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) @@ -268,8 +268,8 @@ namespace cucumber_cpp::library::formatter }; if (uri.has_value() && line.has_value()) - support::print(outputStream, "{:{}}{} {}{:{}} {}\n", "", indent, formatTitle(icon), formatTitle(title), "", padding, helper::ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); + fmt::print(outputStream, "{:{}}{} {}{:{}} {}\n", "", indent, formatTitle(icon), formatTitle(title), "", padding, helper::ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); else - support::print(outputStream, "{:{}}{} {}\n", "", indent, formatTitle(icon), formatTitle(title)); + fmt::print(outputStream, "{:{}}{} {}\n", "", indent, formatTitle(icon), formatTitle(title)); } } diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index 4b461328..8d2094cc 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -6,10 +6,9 @@ #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/formatter/helper/IssueHelpers.hpp" #include "cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp" -#include "cucumber_cpp/library/support/Polyfill.hpp" #include "cucumber_cpp/library/util/Timestamp.hpp" +#include "fmt/ostream.h" #include -#include #include #include #include @@ -73,7 +72,7 @@ namespace cucumber_cpp::library::formatter { if (!attempts.empty()) { - support::print(outputStream, "{}:\n\n", title); + fmt::print(outputStream, "{}:\n\n", title); auto nr = 1; for (const auto& issue : attempts) diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index 1f89b48d..cfde6375 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -30,9 +30,9 @@ target_include_directories(cucumber_cpp.library.formatter.helper PUBLIC ) target_link_libraries(cucumber_cpp.library.formatter.helper PUBLIC - cpp-terminal cucumber_cpp.library.cucumber_expression cucumber_cpp.library.support + fmt-header-only ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp b/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp index 03b21432..2529cce3 100644 --- a/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp +++ b/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp @@ -1,8 +1,8 @@ #include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" -#include "cpp-terminal/color.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include +#include "fmt/color.h" +#include "fmt/format.h" #include #include #include @@ -11,10 +11,10 @@ namespace cucumber_cpp::library::formatter::helper { namespace { - template - std::string ColorString(std::string_view sv) + template + std::string ColorStringFmt(std::string_view sv) { - return std::format("{}{}{}", Term::color_fg(colour), sv, Term::color_fg(Term::Color::Name::Default)); + return fmt::format("{}", fmt::styled(sv, fmt::fg(colour))); } } @@ -25,52 +25,52 @@ namespace cucumber_cpp::library::formatter::helper switch (status) { case PASSED: - return ColorString; + return ColorStringFmt; case SKIPPED: - return ColorString; + return ColorStringFmt; case UNKNOWN: case PENDING: case UNDEFINED: - return ColorString; + return ColorStringFmt; case AMBIGUOUS: case FAILED: default: - return ColorString; + return ColorStringFmt; } } std::string ColorFunctions::Attachment(std::string_view sv) { - return ColorString(sv); + return ColorStringFmt(sv); } std::string ColorFunctions::Location(std::string_view sv) { - return ColorString(sv); + return ColorStringFmt(sv); } std::string ColorFunctions::Tag(std::string_view sv) { - return ColorString(sv); + return ColorStringFmt(sv); } std::string ColorFunctions::DiffAdded(std::string_view sv) { - return ColorString(sv); + return ColorStringFmt(sv); } std::string ColorFunctions::DiffRemoved(std::string_view sv) { - return ColorString(sv); + return ColorStringFmt(sv); } std::string ColorFunctions::ErrorMessage(std::string_view sv) { - return ColorString(sv); + return ColorStringFmt(sv); } std::string ColorFunctions::ErrorStack(std::string_view sv) { - return ColorString(sv); + return ColorStringFmt(sv); } } diff --git a/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp b/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp index 64359580..9a53f84d 100644 --- a/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp +++ b/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp @@ -2,17 +2,15 @@ #include "cucumber_cpp/library/formatter/helper/IssueHelpers.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp" -#include "cucumber_cpp/library/support/Polyfill.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "fmt/ostream.h" #include #include #include -#include #include #include #include #include -#include namespace cucumber_cpp::library::formatter::helper { @@ -28,10 +26,10 @@ namespace cucumber_cpp::library::formatter::helper if (std::ranges::distance(lines) == 0) return outputStream; - support::print(outputStream, "{}{}\n", prefix, std::string_view{ lines.front().begin(), lines.front().end() }); + fmt::print(outputStream, "{}{}\n", prefix, std::string_view{ lines.front().begin(), lines.front().end() }); for (const auto line : lines | std::views::drop(1)) - support::print(outputStream, "{:{}}{}\n", "", prefix.size(), std::string_view{ line.begin(), line.end() }); + fmt::print(outputStream, "{:{}}{}\n", "", prefix.size(), std::string_view{ line.begin(), line.end() }); return outputStream; } diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index 2a55863a..6de30b50 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -10,6 +10,5 @@ add_subdirectory(google) if (CCR_BUILD_TESTS) add_subdirectory(jbeder) endif() -add_subdirectory(jupyter-xeus) add_subdirectory(tobiaslocker) add_subdirectory(zeux) diff --git a/external/jupyter-xeus/CMakeLists.txt b/external/jupyter-xeus/CMakeLists.txt deleted file mode 100644 index e886ef01..00000000 --- a/external/jupyter-xeus/CMakeLists.txt +++ /dev/null @@ -1 +0,0 @@ -add_subdirectory(cpp-terminal) diff --git a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt b/external/jupyter-xeus/cpp-terminal/CMakeLists.txt deleted file mode 100644 index 5855aa30..00000000 --- a/external/jupyter-xeus/cpp-terminal/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -FetchContent_Declare( - cpp-terminal - GIT_REPOSITORY https://github.com/jupyter-xeus/cpp-terminal - GIT_TAG 48ae2f284084850901c45b6c10a9d68949c1b272 # unreleased main -) - -set_directory_properties(PROPERTIES EXCLUDE_FROM_ALL On) - -if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) - add_compile_options( - -Wno-unused-variable - -Wno-unused-but-set-variable - ) -endif() - -set(CPPTERMINAL_BUILD_EXAMPLES Off CACHE STRING "") -set(CPPTERMINAL_ENABLE_INSTALL Off CACHE STRING "") -set(CPPTERMINAL_ENABLE_TESTING Off CACHE STRING "") -set(CPPTERMINAL_ENABLE_DOCS Off CACHE STRING "") - -FetchContent_MakeAvailable(cpp-terminal) From 3a2f838d22d9f0805369d21bd718d0f2bfedf76a Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 18 Jan 2026 23:28:18 +0000 Subject: [PATCH 144/196] feat: add TextBuilder class for structured text formatting --- .../library/formatter/helper/CMakeLists.txt | 2 + .../library/formatter/helper/TextBuilder.cpp | 57 +++++++++++++++++++ .../library/formatter/helper/TextBuilder.hpp | 22 +++++++ 3 files changed, 81 insertions(+) create mode 100644 cucumber_cpp/library/formatter/helper/TextBuilder.cpp create mode 100644 cucumber_cpp/library/formatter/helper/TextBuilder.hpp diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index cfde6375..2e977160 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -23,6 +23,8 @@ target_sources(cucumber_cpp.library.formatter.helper PRIVATE TestCaseAttemptFormatter.hpp TestCaseAttemptParser.cpp TestCaseAttemptParser.hpp + TextBuilder.cpp + TextBuilder.hpp ) target_include_directories(cucumber_cpp.library.formatter.helper PUBLIC diff --git a/cucumber_cpp/library/formatter/helper/TextBuilder.cpp b/cucumber_cpp/library/formatter/helper/TextBuilder.cpp new file mode 100644 index 00000000..21d78dc2 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/TextBuilder.cpp @@ -0,0 +1,57 @@ + +#include "cucumber_cpp/library/formatter/helper/TextBuilder.hpp" +#include "fmt/color.h" +#include "fmt/format.h" +#include "fmt/ranges.h" +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + std::string ApplyStyle(const std::string& text, std::optional style) + { + if (!style) + return text; + + return fmt::format(*style, "{}", text); + } + } + + TextBuilder& TextBuilder::Space() + { + text += ' '; + return *this; + } + + TextBuilder& TextBuilder::Line() + { + text += '\n'; + return *this; + } + + TextBuilder& TextBuilder::Append(const std::string& text, std::optional style) + { + this->text += ApplyStyle(text, style); + return *this; + } + + std::string TextBuilder::Build(std::optional style, bool styleEachLine) const + { + if (styleEachLine) + { + auto styled = text | + std::views::split('\n') | + std::views::transform([&style](const auto& line) + { + return ApplyStyle(std::string{ line.begin(), line.end() }, style); + }); + + return fmt::to_string(fmt::join(styled, "\n")); + } + + return ApplyStyle(text, style); + } +} diff --git a/cucumber_cpp/library/formatter/helper/TextBuilder.hpp b/cucumber_cpp/library/formatter/helper/TextBuilder.hpp new file mode 100644 index 00000000..c4b3d9e2 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/TextBuilder.hpp @@ -0,0 +1,22 @@ +#ifndef HELPER_TEXT_BUILDER_HPP +#define HELPER_TEXT_BUILDER_HPP + +#include "fmt/color.h" +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + struct TextBuilder + { + TextBuilder& Space(); + TextBuilder& Line(); + TextBuilder& Append(const std::string& text, std::optional style = std::nullopt); + std::string Build(std::optional style = std::nullopt, bool styleEachLine = false) const; + + private: + std::string text; + }; +} + +#endif From f6ef4df9237885bf22b3f2d096fcdc74de398c1d Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 18 Jan 2026 23:42:13 +0000 Subject: [PATCH 145/196] fix: update launch configuration to use 'summary' and 'pretty' formats for output --- .vscode/launch.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8e623ca0..9f205359 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -46,7 +46,8 @@ "COMx", "--nordic", "--format", - "junit", + "summary", + "pretty", "--tags", "${input:tag}", "--", @@ -62,7 +63,6 @@ ], "externalConsole": false, "MIMode": "gdb", - "debugServerPath": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", From 6780053cfe56d626798ecc13839f69c0c04427bf Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 18 Jan 2026 23:45:01 +0000 Subject: [PATCH 146/196] refactor: simplify IndentString implementation using fmt library --- .../library/formatter/helper/IndentString.cpp | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/cucumber_cpp/library/formatter/helper/IndentString.cpp b/cucumber_cpp/library/formatter/helper/IndentString.cpp index 5ede2363..f1f48221 100644 --- a/cucumber_cpp/library/formatter/helper/IndentString.cpp +++ b/cucumber_cpp/library/formatter/helper/IndentString.cpp @@ -1,30 +1,21 @@ #include "cucumber_cpp/library/formatter/helper/IndentString.hpp" +#include "fmt/format.h" +#include "fmt/ranges.h" #include -#include -#include -#include #include #include -#include namespace cucumber_cpp::library::formatter::helper { std::string IndentString(const std::string& str, std::size_t indentSize) { - using std::operator""sv; + auto lines = str | + std::views::split('\n') | + std::views::transform([indent = std::string(indentSize, ' ')](const auto& line) + { + return indent + std::string{ line.begin(), line.end() }; + }); - auto lines = str | std::views::split("\n"sv); - auto lineCount = std::distance(lines.begin(), lines.end()); - - if (lineCount == 0) - return ""; - - const auto indent = std::string(indentSize, ' '); - std::string indented = std::format("{}{}", indent, std::string_view{ lines.front().begin(), lines.front().end() }); - - for (const auto line : lines | std::views::drop(1)) - indented += std::format("\n{}{}", indent, std::string_view{ line.begin(), line.end() }); - - return indented; + return fmt::to_string(fmt::join(lines, "\n")); } } From 128be726f3946e8fca234a4008e55f2d66cbdb7b Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 18 Jan 2026 23:53:46 +0000 Subject: [PATCH 147/196] refactor: replace std::format with fmt library for string formatting and simplify trim function --- .../library/formatter/JunitXmlFormatter.cpp | 45 +++++-------------- 1 file changed, 10 insertions(+), 35 deletions(-) diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp index 20973563..e34830f2 100644 --- a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp @@ -10,16 +10,15 @@ #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/util/Duration.hpp" #include "cucumber_cpp/library/util/Timestamp.hpp" +#include "fmt/format.h" +#include "fmt/ranges.h" #include "nlohmann/json_fwd.hpp" #include "pugixml.hpp" #include #include #include #include -#include -#include #include -#include #include #include #include @@ -29,28 +28,14 @@ namespace cucumber_cpp::library::formatter { namespace { - void ltrim(std::string& s) + [[nodiscard]] std::string trim(const std::string& str) { - s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](unsigned char ch) - { - return !std::isspace(ch); - })); - } + const auto start = str.find_first_not_of(" \t\n\r"); + if (start == std::string::npos) + return ""; - void rtrim(std::string& s) - { - s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) - { - return !std::isspace(ch); - }) - .base(), - s.end()); - } - - void trim(std::string& s) - { - rtrim(s); - ltrim(s); + const auto end = str.find_last_not_of(" \t\n\r"); + return str.substr(start, end - start + 1); } enum class FailureKind @@ -112,13 +97,7 @@ namespace cucumber_cpp::library::formatter return std::tolower(c); }); - auto keyword = gherkinStep.keyword; - auto text = pickleStep.text; - - trim(keyword); - trim(text); - - return std::format("{:.<76}{}", keyword + " " + text, statusString); + return fmt::format("{:.<76}{}", trim(gherkinStep.keyword) + " " + trim(pickleStep.text), statusString); } std::string MakeOutput(query::Query& query, const cucumber::messages::test_case_started& testCaseStarted) @@ -138,11 +117,7 @@ namespace cucumber_cpp::library::formatter return FormatStep(gherkinStep, pickleStep, testStepFinished->test_step_result.status); }); - return "\n" + std::accumulate(std::next(outputView.begin()), outputView.end(), outputView.front(), [](const std::string& a, const std::string& b) - { - return a + "\n" + b; - }) + - "\n"; + return fmt::format("\n{}\n", fmt::join(outputView, "\n")); } std::list MakeTestCases(query::Query& query, const std::optional& testClassName) From 607b1afcffd4a76e81e4420ee88c7a74b964d7c6 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Sun, 18 Jan 2026 23:55:56 +0000 Subject: [PATCH 148/196] refactor: replace std::format with fmt library for string formatting in PrettyFormatter --- .../library/formatter/PrettyFormatter.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index 0d9528b1..70b93825 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -18,12 +18,12 @@ #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" #include "cucumber_cpp/library/support/Join.hpp" +#include "fmt/format.h" #include "fmt/ostream.h" #include "nlohmann/json_fwd.hpp" #include #include #include -#include #include #include #include @@ -53,20 +53,20 @@ namespace cucumber_cpp::library::formatter std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario) { - return std::format("{}: {}", scenario.keyword, pickle.name); + return fmt::format("{}: {}", scenario.keyword, pickle.name); } std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step) { - return std::format("{}{}", step.keyword, pickleStep.text); + return fmt::format("{}{}", step.keyword, pickleStep.text); } std::string FormatBase64Attachment(const std::string& body, const std::string& mediaType, const std::optional& filename) { if (!filename) - return helper::ColorFunctions::Attachment(std::format("Embedding [{} {} bytes]", mediaType, body.length())); + return helper::ColorFunctions::Attachment(fmt::format("Embedding [{} {} bytes]", mediaType, body.length())); else - return helper::ColorFunctions::Attachment(std::format("Embedding {} [{} {} bytes]", filename.value(), mediaType, body.length())); + return helper::ColorFunctions::Attachment(fmt::format("Embedding {} [{} {} bytes]", filename.value(), mediaType, body.length())); } std::string FormatTextAttachment(const std::string& body) @@ -243,7 +243,7 @@ namespace cucumber_cpp::library::formatter void PrettyFormatter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) { - PrintGherkinLine("", std::format("{}: {}", scenario.keyword, pickle.name), nullptr, pickle.uri, scenario.location.line, scenarioIndent, maxContentLength); + PrintGherkinLine("", fmt::format("{}: {}", scenario.keyword, pickle.name), nullptr, pickle.uri, scenario.location.line, scenarioIndent, maxContentLength); } void PrettyFormatter::PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength) @@ -251,7 +251,7 @@ namespace cucumber_cpp::library::formatter const auto uri = stepDefinition ? std::make_optional(*stepDefinition->source_reference.uri) : std::nullopt; const auto line = stepDefinition ? std::make_optional(stepDefinition->source_reference.location->line) : std::nullopt; - PrintGherkinLine(options.useStatusIcon ? iconMap.at(testStepFinished.test_step_result.status) : "", std::format("{}{}", step.keyword, pickleStep.text), helper::ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); + PrintGherkinLine(options.useStatusIcon ? iconMap.at(testStepFinished.test_step_result.status) : "", fmt::format("{}{}", step.keyword, pickleStep.text), helper::ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); } void PrettyFormatter::PrintGherkinLine(std::string_view icon, std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) @@ -268,7 +268,7 @@ namespace cucumber_cpp::library::formatter }; if (uri.has_value() && line.has_value()) - fmt::print(outputStream, "{:{}}{} {}{:{}} {}\n", "", indent, formatTitle(icon), formatTitle(title), "", padding, helper::ColorFunctions::Location(std::format("# {}:{}", *uri, *line))); + fmt::print(outputStream, "{:{}}{} {}{:{}} {}\n", "", indent, formatTitle(icon), formatTitle(title), "", padding, helper::ColorFunctions::Location(fmt::format("# {}:{}", *uri, *line))); else fmt::print(outputStream, "{:{}}{} {}\n", "", indent, formatTitle(icon), formatTitle(title)); } From c99c3579d1e20155c3e7f58f4f696747b49faee5 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 19 Jan 2026 15:49:11 +0000 Subject: [PATCH 149/196] feat: implement nested steps functionality and enhance step execution flow --- .vscode/launch.json | 3 +- compatibility/compatibility.cpp | 5 +- cucumber_cpp/CucumberCpp.hpp | 2 +- .../features/test_nested_steps.feature | 5 + cucumber_cpp/acceptance_test/steps/Steps.cpp | 22 +++ cucumber_cpp/acceptance_test/test.bats | 5 + cucumber_cpp/example/steps/Steps.cpp | 6 +- cucumber_cpp/library/Steps.hpp | 8 +- cucumber_cpp/library/engine/Step.cpp | 21 ++- cucumber_cpp/library/engine/Step.hpp | 21 ++- cucumber_cpp/library/engine/test/TestStep.cpp | 53 +++++-- cucumber_cpp/library/runtime/CMakeLists.txt | 3 + .../library/runtime/NestedTestCaseRunner.cpp | 133 ++++++++++++++++++ .../library/runtime/NestedTestCaseRunner.hpp | 42 ++++++ .../library/runtime/TestCaseRunner.cpp | 6 +- cucumber_cpp/library/support/Body.cpp | 29 +++- cucumber_cpp/library/support/CMakeLists.txt | 1 + cucumber_cpp/library/support/StepRegistry.hpp | 12 +- cucumber_cpp/library/util/Duration.cpp | 8 +- cucumber_cpp/library/util/Duration.hpp | 11 +- 20 files changed, 337 insertions(+), 59 deletions(-) create mode 100644 cucumber_cpp/acceptance_test/features/test_nested_steps.feature create mode 100644 cucumber_cpp/library/runtime/NestedTestCaseRunner.cpp create mode 100644 cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 9f205359..0b54d510 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -19,7 +19,8 @@ "@ex:2", "@substep", "@table_argument", - "@thishasarule" + "@thishasarule", + "@nested_steps" ] }, { diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 561c46c5..3412dc31 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -219,11 +219,12 @@ namespace compatibility { virtual ~StopwatchIncremental() = default; - void Start() override + std::chrono::high_resolution_clock::time_point Start() override { + return {}; } - std::chrono::nanoseconds Duration() override + std::chrono::nanoseconds Duration([[maybe_unused]] std::chrono::high_resolution_clock::time_point timePoint) override { return current; } diff --git a/cucumber_cpp/CucumberCpp.hpp b/cucumber_cpp/CucumberCpp.hpp index 6d78cf7e..cc146b26 100644 --- a/cucumber_cpp/CucumberCpp.hpp +++ b/cucumber_cpp/CucumberCpp.hpp @@ -13,7 +13,7 @@ namespace cucumber_cpp { using cucumber_cpp::library::Application; using cucumber_cpp::library::Context; - using cucumber_cpp::library::engine::Step; + using cucumber_cpp::library::engine::StepBase; using cucumber_cpp::library::engine::StringTo; } diff --git a/cucumber_cpp/acceptance_test/features/test_nested_steps.feature b/cucumber_cpp/acceptance_test/features/test_nested_steps.feature new file mode 100644 index 00000000..d4228606 --- /dev/null +++ b/cucumber_cpp/acceptance_test/features/test_nested_steps.feature @@ -0,0 +1,5 @@ +@nested_steps +Feature: Nested Steps + Scenario: Call other steps from within a step + Given a step calls another step with "cucumber" + Then the stored string is "cucumber" diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index dad2e57f..0d8b9b3a 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/CucumberCpp.hpp" +#include "fmt/format.h" #include "gmock/gmock.h" #include "gtest/gtest.h" #include @@ -93,3 +94,24 @@ GIVEN("{int} and {int} are equal", (std::int32_t a, std::int32_t b)) { EXPECT_THAT(a, testing::Eq(b)); } + +GIVEN(R"(a step calls another step with {string})", (const std::string& str)) +{ + Step(fmt::format(R"(I store "{}")", str)); +} + +GIVEN(R"(I store {string})", (const std::string& str)) +{ + Step(fmt::format(R"(I store "{}" again)", str)); +} + +GIVEN(R"(I store {string} again)", (const std::string& str)) +{ + context.InsertAt("storedstring", str); +} + +THEN(R"(the stored string is {string})", (const std::string& expected)) +{ + const auto& stored = context.Get("storedstring"); + EXPECT_THAT(stored, testing::StrEq(expected)); +} diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index dcfa85e2..8b6ed5bd 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -203,3 +203,8 @@ teardown() { assert_success refute_output --partial "The following steps have not been used:" } + +@test "Test nested steps" { + run $acceptance_test --format summary pretty --tags "@nested_steps" -- cucumber_cpp/acceptance_test/features + assert_success +} diff --git a/cucumber_cpp/example/steps/Steps.cpp b/cucumber_cpp/example/steps/Steps.cpp index 40bef16a..87ac0d88 100644 --- a/cucumber_cpp/example/steps/Steps.cpp +++ b/cucumber_cpp/example/steps/Steps.cpp @@ -12,9 +12,9 @@ struct LoadMeOnConstruction {} }; -struct CustomFixture : cucumber_cpp::library::engine::Step +struct CustomFixture : cucumber_cpp::library::engine::StepBase { - using Step::Step; + using StepBase::StepBase; const LoadMeOnConstruction& alwaysAvailable{ context.Get() }; }; @@ -101,7 +101,7 @@ STEP(R"(^a step$)") STEP(R"(^a step calls another step$)") { - Given(R"(a step)"); + Step(R"(a step)"); } STEP(R"(^the called step is executed$)") diff --git a/cucumber_cpp/library/Steps.hpp b/cucumber_cpp/library/Steps.hpp index 4bcd7410..5e8cdf3c 100644 --- a/cucumber_cpp/library/Steps.hpp +++ b/cucumber_cpp/library/Steps.hpp @@ -23,9 +23,9 @@ #define THEN_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::support::StepType::then, __VA_ARGS__) #define STEP_F(fixture, ...) STEP_TYPE_(fixture, cucumber_cpp::library::support::StepType::any, __VA_ARGS__) -#define GIVEN(...) GIVEN_F(cucumber_cpp::library::engine::Step, __VA_ARGS__) -#define WHEN(...) WHEN_F(cucumber_cpp::library::engine::Step, __VA_ARGS__) -#define THEN(...) THEN_F(cucumber_cpp::library::engine::Step, __VA_ARGS__) -#define STEP(...) STEP_F(cucumber_cpp::library::engine::Step, __VA_ARGS__) +#define GIVEN(...) GIVEN_F(cucumber_cpp::library::engine::StepBase, __VA_ARGS__) +#define WHEN(...) WHEN_F(cucumber_cpp::library::engine::StepBase, __VA_ARGS__) +#define THEN(...) THEN_F(cucumber_cpp::library::engine::StepBase, __VA_ARGS__) +#define STEP(...) STEP_F(cucumber_cpp::library::engine::StepBase, __VA_ARGS__) #endif diff --git a/cucumber_cpp/library/engine/Step.cpp b/cucumber_cpp/library/engine/Step.cpp index 5a3738b6..095d383a 100644 --- a/cucumber_cpp/library/engine/Step.cpp +++ b/cucumber_cpp/library/engine/Step.cpp @@ -3,30 +3,37 @@ #include "cucumber/messages/pickle_table.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include #include namespace cucumber_cpp::library::engine { - Step::Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString) + StepBase::StepBase(const runtime::NestedTestCaseRunner& nestedTestCaseRunner, util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString) : ExecutionContext{ broadCaster, context, stepOrHookStarted } + , nestedTestCaseRunner{ nestedTestCaseRunner } , dataTable{ dataTable } , docString{ docString } {} - void Step::Given(const std::string& step) const + void StepBase::Step(const std::string& step) const { - // CucumberTestServer::Instance()->RunStep(step, cucumber::messages::pickle_step_type::CONTEXT); + nestedTestCaseRunner.Step(step); } - void Step::When(const std::string& step) const + void StepBase::Step(const std::string& step, const std::optional& docString) const { - // CucumberTestServer::Instance()->RunStep(step, cucumber::messages::pickle_step_type::ACTION); + nestedTestCaseRunner.Step(step, docString); } - void Step::Then(const std::string& step) const + void StepBase::Step(const std::string& step, const std::optional& dataTable) const { - // CucumberTestServer::Instance()->RunStep(step, cucumber::messages::pickle_step_type::OUTCOME); + nestedTestCaseRunner.Step(step, dataTable); + } + + void StepBase::Step(const std::string& step, const std::optional& dataTable, const std::optional& docString) const + { + nestedTestCaseRunner.Step(step, dataTable, docString); } } diff --git a/cucumber_cpp/library/engine/Step.hpp b/cucumber_cpp/library/engine/Step.hpp index 34758c84..33188b58 100644 --- a/cucumber_cpp/library/engine/Step.hpp +++ b/cucumber_cpp/library/engine/Step.hpp @@ -6,23 +6,19 @@ #include "cucumber/messages/pickle_doc_string.hpp" #include "cucumber/messages/pickle_table.hpp" -#include "cucumber/messages/pickle_table_row.hpp" #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" +#include "cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" -#include #include -#include -#include #include -#include namespace cucumber_cpp::library::engine { - struct Step : ExecutionContext + struct StepBase : ExecutionContext { - Step(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString); - virtual ~Step() = default; + StepBase(const runtime::NestedTestCaseRunner& nestedTestCaseRunner, util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString); + virtual ~StepBase() = default; virtual void SetUp() { @@ -35,9 +31,12 @@ namespace cucumber_cpp::library::engine } protected: - void Given(const std::string& step) const; - void When(const std::string& step) const; - void Then(const std::string& step) const; + void Step(const std::string& step) const; + void Step(const std::string& step, const std::optional& docString) const; + void Step(const std::string& step, const std::optional& dataTable) const; + void Step(const std::string& step, const std::optional& dataTable, const std::optional& docString) const; + + const runtime::NestedTestCaseRunner& nestedTestCaseRunner; const std::optional& dataTable; const std::optional& docString; diff --git a/cucumber_cpp/library/engine/test/TestStep.cpp b/cucumber_cpp/library/engine/test/TestStep.cpp index c0d0e72a..faf56d9b 100644 --- a/cucumber_cpp/library/engine/test/TestStep.cpp +++ b/cucumber_cpp/library/engine/test/TestStep.cpp @@ -1,7 +1,15 @@ +#include "cucumber/gherkin/id_generator.hpp" #include "cucumber/messages/pickle_step_argument.hpp" +#include "cucumber/messages/test_step_started.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/engine/ExecutionContext.hpp" #include "cucumber_cpp/library/engine/Step.hpp" +#include "cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp" +#include "cucumber_cpp/library/support/HookRegistry.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/support/UndefinedParameters.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -10,23 +18,21 @@ namespace cucumber_cpp::library::engine { - struct StepMock : Step + struct StepMock : StepBase { - using Step::Step; + using StepBase::StepBase; MOCK_METHOD(void, SetUp, (), (override)); MOCK_METHOD(void, TearDown, (), (override)); - using Step::Pending; - using Step::Skipped; + using StepBase::Pending; + using StepBase::Skipped; - using Step::Given; - using Step::Then; - using Step::When; + using StepBase::Step; - using Step::context; - using Step::dataTable; - using Step::docString; + using StepBase::context; + using StepBase::dataTable; + using StepBase::docString; }; struct TestStep : testing::Test @@ -37,12 +43,33 @@ namespace cucumber_cpp::library::engine engine::StepOrHookStarted stepOrHookStarted; cucumber::messages::pickle_step_argument pickleStepArgument; + cucumber_expression::ParameterRegistry parameterRegistry{ cucumber_cpp::library::support::DefinitionRegistration::Instance().GetRegisteredParameters() }; + cucumber::gherkin::id_generator_ptr idGenerator = std::make_shared(); + support::UndefinedParameters undefinedParameters; + support::StepRegistry stepRegistry{ parameterRegistry, undefinedParameters, idGenerator }; + support::HookRegistry hookRegistry{ idGenerator }; + + support::SupportCodeLibrary supportCodeLibrary{ + hookRegistry, + stepRegistry, + parameterRegistry, + undefinedParameters + }; + runtime::NestedTestCaseRunner nestedTestCaseRunner{ + 0, + supportCodeLibrary, + broadcaster, + context, + std::get(stepOrHookStarted), + }; + StepMock step{ + nestedTestCaseRunner, broadcaster, context, stepOrHookStarted, pickleStepArgument.data_table, - pickleStepArgument.doc_string + pickleStepArgument.doc_string, }; }; @@ -50,14 +77,14 @@ namespace cucumber_cpp::library::engine { EXPECT_CALL(step, SetUp()); - static_cast(step).SetUp(); + static_cast(step).SetUp(); } TEST_F(TestStep, StepProvidesAccessToTearDownFunction) { EXPECT_CALL(step, TearDown()); - static_cast(step).TearDown(); + static_cast(step).TearDown(); } TEST_F(TestStep, ProvidesAccessToCurrentContext) diff --git a/cucumber_cpp/library/runtime/CMakeLists.txt b/cucumber_cpp/library/runtime/CMakeLists.txt index e2a902a5..88719757 100644 --- a/cucumber_cpp/library/runtime/CMakeLists.txt +++ b/cucumber_cpp/library/runtime/CMakeLists.txt @@ -5,6 +5,8 @@ target_sources(cucumber_cpp.library.runtime PRIVATE Coordinator.hpp MakeRuntime.cpp MakeRuntime.hpp + NestedTestCaseRunner.cpp + NestedTestCaseRunner.hpp SerialRuntimeAdapter.cpp SerialRuntimeAdapter.hpp TestCaseRunner.cpp @@ -22,6 +24,7 @@ target_link_libraries(cucumber_cpp.library.runtime PUBLIC cucumber_cpp.library.cucumber_expression cucumber_cpp.library.support cucumber_cpp.library.util + fmt-header-only ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/runtime/NestedTestCaseRunner.cpp b/cucumber_cpp/library/runtime/NestedTestCaseRunner.cpp new file mode 100644 index 00000000..7823b9f0 --- /dev/null +++ b/cucumber_cpp/library/runtime/NestedTestCaseRunner.cpp @@ -0,0 +1,133 @@ +#include "cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/pickle_doc_string.hpp" +#include "cucumber/messages/pickle_table.hpp" +#include "cucumber/messages/step_match_arguments_list.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/cucumber_expression/Argument.hpp" +#include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" +#include "cucumber_cpp/library/support/Body.hpp" +#include "cucumber_cpp/library/support/StepRegistry.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + namespace + { + auto TransformToMatch(const std::string& text) + { + return [&text](const support::StepRegistry::Definition& definition) -> std::pair>> + { + const auto match = std::visit(cucumber_expression::MatchVisitor{ text }, definition.regex); + return { definition.id, match }; + }; + } + + bool HasMatch(const std::pair>>& pair) + { + return pair.second.has_value(); + } + + cucumber::messages::test_step Assemble(const std::string& step, const support::SupportCodeLibrary& supportCodeLibrary, const cucumber::messages::test_step_started& testStepStarted) + { + cucumber::messages::test_step testStep{ + .id = testStepStarted.test_step_id, + .step_definition_ids = std::vector{}, + .step_match_arguments_lists = std::vector{} + }; + + const auto& stepDefinitions = supportCodeLibrary.stepRegistry.StepDefinitions(); + + for (const auto& [id, match] : stepDefinitions | + std::views::transform(TransformToMatch(step)) | + std::views::filter(HasMatch)) + { + testStep.step_definition_ids.value().push_back(id); + auto& argumentList = testStep.step_match_arguments_lists.value().emplace_back(); + for (const auto& result : *match) + argumentList.step_match_arguments.emplace_back(result.Group(), result.Name().empty() ? std::nullopt : std::make_optional(result.Name())); + } + + return testStep; + } + + void Invoke(std::size_t nesting, const std::string& step, std::unique_ptr body, const cucumber::messages::step_match_arguments_list& args) + { + const auto status = body->ExecuteAndCatchExceptions(args); + if (status.status != cucumber::messages::test_step_result_status::PASSED) + throw NestedTestCaseRunnerError{ nesting, status, step }; + } + + void Run(std::size_t nesting, const std::string& step, const cucumber::messages::test_step& testStep, const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, Context& testCaseContext, const cucumber::messages::test_step_started& testStepStarted, const std::optional& dataTable, const std::optional& docString) + { + auto stepDefinitions = (*testStep.step_definition_ids) | std::views::transform([&supportCodeLibrary](const std::string& id) + { + return supportCodeLibrary.stepRegistry.GetDefinitionById(id); + }); + + if (testStep.step_definition_ids->size() == 0) + throw NestedTestCaseRunnerError{ nesting, { + .duration = cucumber::messages::duration{}, + .status = cucumber::messages::test_step_result_status::UNDEFINED, + }, + step }; + + else if (testStep.step_definition_ids->size() > 1) + throw NestedTestCaseRunnerError{ nesting, { + .duration = cucumber::messages::duration{}, + .message = "Ambiguous step definitions", + .status = cucumber::messages::test_step_result_status::AMBIGUOUS, + }, + step }; + else + { + const auto& definition = stepDefinitions.front(); + Invoke(nesting, step, definition.factory(NestedTestCaseRunner{ nesting, supportCodeLibrary, broadcaster, testCaseContext, testStepStarted }, broadcaster, testCaseContext, testStepStarted, dataTable, docString), testStep.step_match_arguments_lists->front()); + } + } + } + + NestedTestCaseRunner::NestedTestCaseRunner(std::size_t nesting, const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, Context& testCaseContext, const cucumber::messages::test_step_started& testStepStarted) + : nesting{ nesting + 1 } + , supportCodeLibrary{ supportCodeLibrary } + , broadcaster{ broadcaster } + , testCaseContext{ testCaseContext } + , testStepStarted{ testStepStarted } + {} + + void NestedTestCaseRunner::Step(const std::string& step) const + { + Step(step, std::nullopt, std::nullopt); + } + + void NestedTestCaseRunner::Step(const std::string& step, const std::optional& docString) const + { + Step(step, std::nullopt, docString); + } + + void NestedTestCaseRunner::Step(const std::string& step, const std::optional& dataTable) const + { + Step(step, dataTable, std::nullopt); + } + + void NestedTestCaseRunner::Step(const std::string& step, const std::optional& dataTable, const std::optional& docString) const + { + const auto testStep = Assemble(step, supportCodeLibrary, testStepStarted); + Run(nesting, step, testStep, supportCodeLibrary, broadcaster, testCaseContext, testStepStarted, dataTable, docString); + } +} diff --git a/cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp b/cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp new file mode 100644 index 00000000..341196d8 --- /dev/null +++ b/cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp @@ -0,0 +1,42 @@ +#ifndef RUNTIME_NESTED_TEST_CASE_RUNNER_HPP +#define RUNTIME_NESTED_TEST_CASE_RUNNER_HPP + +#include "cucumber/messages/pickle_doc_string.hpp" +#include "cucumber/messages/pickle_table.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_started.hpp" +#include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "cucumber_cpp/library/util/Broadcaster.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::runtime +{ + struct NestedTestCaseRunnerError + { + std::size_t nesting; + cucumber::messages::test_step_result status; + std::string text; + }; + + struct NestedTestCaseRunner + { + NestedTestCaseRunner(std::size_t nesting, const support::SupportCodeLibrary& supportCodeLibrary, util::Broadcaster& broadcaster, Context& testCaseContext, const cucumber::messages::test_step_started& testStepStarted); + + void Step(const std::string& step) const; + void Step(const std::string& step, const std::optional& docString) const; + void Step(const std::string& step, const std::optional& dataTable) const; + void Step(const std::string& step, const std::optional& dataTable, const std::optional& docString) const; + + private: + std::size_t nesting; + const support::SupportCodeLibrary& supportCodeLibrary; + util::Broadcaster& broadcaster; + Context& testCaseContext; + const cucumber::messages::test_step_started& testStepStarted; + }; +} + +#endif diff --git a/cucumber_cpp/library/runtime/TestCaseRunner.cpp b/cucumber_cpp/library/runtime/TestCaseRunner.cpp index 0605ebc4..75703f58 100644 --- a/cucumber_cpp/library/runtime/TestCaseRunner.cpp +++ b/cucumber_cpp/library/runtime/TestCaseRunner.cpp @@ -16,6 +16,7 @@ #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber/messages/test_step_started.hpp" #include "cucumber_cpp/library/Context.hpp" +#include "cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp" #include "cucumber_cpp/library/support/Body.hpp" #include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" @@ -200,7 +201,10 @@ 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(broadcaster, testCaseContext, testStepStarted, dataTable, docString), testStep.step_match_arguments_lists->front()); + const auto result = InvokeStep(definition.factory( + NestedTestCaseRunner{ 0, supportCodeLibrary, broadcaster, testCaseContext, testStepStarted }, + broadcaster, testCaseContext, testStepStarted, dataTable, docString), + testStep.step_match_arguments_lists->front()); stepResults.push_back(result); } diff --git a/cucumber_cpp/library/support/Body.cpp b/cucumber_cpp/library/support/Body.cpp index 9f375f93..cbd8525d 100644 --- a/cucumber_cpp/library/support/Body.cpp +++ b/cucumber_cpp/library/support/Body.cpp @@ -3,7 +3,9 @@ #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/runtime/NestedTestCaseRunner.hpp" #include "cucumber_cpp/library/util/Duration.hpp" +#include "fmt/format.h" #include "gtest/gtest.h" #include #include @@ -12,6 +14,7 @@ #include #include #include +#include namespace cucumber_cpp::library::support { @@ -50,11 +53,33 @@ namespace cucumber_cpp::library::support cucumber::messages::test_step_result testStepResult{ .status = cucumber::messages::test_step_result_status::PASSED }; CucumberResultReporter reportListener{ testStepResult }; + const auto startTime = util::Stopwatch::Instance().Start(); try { - util::Stopwatch::Instance().Start(); Execute(args); } + catch (const runtime::NestedTestCaseRunnerError& e) + { + testStepResult.status = cucumber::messages::test_step_result_status::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 = cucumber::messages::test_step_result_status::SKIPPED; @@ -88,7 +113,7 @@ namespace cucumber_cpp::library::support }; } - auto nanoseconds = util::Stopwatch::Instance().Duration(); + auto nanoseconds = util::Stopwatch::Instance().Duration(startTime); static constexpr std::size_t nanosecondsPerSecond = 1e9; testStepResult.duration = { .seconds = nanoseconds.count() / nanosecondsPerSecond, diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index 748c98b1..5f6aee43 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -31,6 +31,7 @@ target_link_libraries(cucumber_cpp.library.support PUBLIC cucumber_cpp.library.cucumber_expression cucumber_cpp.library.tag_expression cucumber_cpp.library.util + fmt-header-only ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/support/StepRegistry.hpp b/cucumber_cpp/library/support/StepRegistry.hpp index 1af1af30..8258b752 100644 --- a/cucumber_cpp/library/support/StepRegistry.hpp +++ b/cucumber_cpp/library/support/StepRegistry.hpp @@ -32,14 +32,20 @@ #include #include +namespace cucumber_cpp::library::runtime +{ + struct NestedTestCaseRunner; +} + namespace cucumber_cpp::library::support { - using StepFactory = std::unique_ptr (&)(util::Broadcaster& broadCaster, Context&, engine::StepOrHookStarted stepOrHookStarted, const std::optional&, const std::optional&); + + using StepFactory = std::unique_ptr (&)(const runtime::NestedTestCaseRunner&, util::Broadcaster& broadCaster, Context&, engine::StepOrHookStarted stepOrHookStarted, const std::optional&, const std::optional&); template - std::unique_ptr StepBodyFactory(util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString) + std::unique_ptr StepBodyFactory(const runtime::NestedTestCaseRunner& nestedTestCaseRunner, util::Broadcaster& broadCaster, Context& context, engine::StepOrHookStarted stepOrHookStarted, const std::optional& dataTable, const std::optional& docString) { - return std::make_unique(broadCaster, context, stepOrHookStarted, dataTable, docString); + return std::make_unique(nestedTestCaseRunner, broadCaster, context, stepOrHookStarted, dataTable, docString); } struct StepMatch diff --git a/cucumber_cpp/library/util/Duration.cpp b/cucumber_cpp/library/util/Duration.cpp index 5d44ed28..65a5b3a0 100644 --- a/cucumber_cpp/library/util/Duration.cpp +++ b/cucumber_cpp/library/util/Duration.cpp @@ -34,15 +34,15 @@ namespace cucumber_cpp::library::util return *instance; } - void StopWatchHighResolutionClock::Start() + std::chrono::high_resolution_clock::time_point StopWatchHighResolutionClock::Start() { - timeStart = std::chrono::high_resolution_clock::now(); + return std::chrono::high_resolution_clock::now(); } - std::chrono::nanoseconds StopWatchHighResolutionClock::Duration() + std::chrono::nanoseconds StopWatchHighResolutionClock::Duration(std::chrono::high_resolution_clock::time_point timePoint) { const auto timeStop = std::chrono::high_resolution_clock::now(); - return std::chrono::duration_cast(timeStop - timeStart); + return std::chrono::duration_cast(timeStop - timePoint); } cucumber::messages::duration MillisecondsToDuration(std::chrono::milliseconds millis) diff --git a/cucumber_cpp/library/util/Duration.hpp b/cucumber_cpp/library/util/Duration.hpp index dcc03bc4..1f109148 100644 --- a/cucumber_cpp/library/util/Duration.hpp +++ b/cucumber_cpp/library/util/Duration.hpp @@ -20,8 +20,8 @@ namespace cucumber_cpp::library::util public: static Stopwatch& Instance(); - virtual void Start() = 0; - virtual std::chrono::nanoseconds Duration() = 0; + virtual std::chrono::high_resolution_clock::time_point Start() = 0; + virtual std::chrono::nanoseconds Duration(std::chrono::high_resolution_clock::time_point timepPoint) = 0; private: static inline Stopwatch* instance{ nullptr }; @@ -30,11 +30,8 @@ namespace cucumber_cpp::library::util struct StopWatchHighResolutionClock : Stopwatch { virtual ~StopWatchHighResolutionClock() = default; - void Start() override; - std::chrono::nanoseconds Duration() override; - - private: - std::chrono::high_resolution_clock::time_point timeStart{}; + std::chrono::high_resolution_clock::time_point Start() override; + std::chrono::nanoseconds Duration(std::chrono::high_resolution_clock::time_point timePoint) override; }; } From 6d7a37efe79f78b4259e6e78a6e94dc6b38e5b59 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 19 Jan 2026 22:22:03 +0000 Subject: [PATCH 150/196] feat: enhance acceptance tests and update Application class for recursive file search --- cucumber_cpp/acceptance_test/test.bats | 8 +-- cucumber_cpp/library/Application.cpp | 91 +++++++++++--------------- cucumber_cpp/library/Application.hpp | 14 ++-- 3 files changed, 45 insertions(+), 68 deletions(-) diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index 8b6ed5bd..0f9f5525 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -15,7 +15,7 @@ teardown() { } @test "Successful test" { - run $acceptance_test --format summary pretty message junit --tags "@result:OK" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@result:OK" --no-recursive -- cucumber_cpp/acceptance_test/features assert_success } @@ -48,12 +48,6 @@ teardown() { assert_output --partial "2 passed" } -@test "Missing mandatory paths argument" { - run $acceptance_test --format summary pretty message junit -- - assert_failure - assert_output --partial "paths is required" -} - @test "Missing mandatory custom argument" { run $acceptance_test.custom --format summary pretty message junit -- cucumber_cpp/acceptance_test/features assert_failure diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 0f829fea..5bf88c73 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -4,11 +4,11 @@ #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/RunCucumber.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" -#include "cucumber_cpp/library/cucumber_expression/Matcher.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "fmt/format.h" +#include "fmt/ranges.h" #include #include #include @@ -23,56 +23,45 @@ #include #include #include -#include #include #include -#include #include #include #include #include #include #include -#include namespace cucumber_cpp::library { namespace { - template - std::string Join(const Range& range, std::string_view delim) - { - if (range.empty()) - return ""; - - return std::accumulate(std::next(range.begin()), range.end(), range.front(), [&delim](const auto& lhs, const auto& rhs) - { - return std::format("{}{}{}", lhs, delim, rhs); - }); - } - - std::filesystem::path ToFileSystemPath(const std::string_view& sv) + bool IsFeatureFile(const std::filesystem::directory_entry& entry) { - return { sv }; + return std::filesystem::is_regular_file(entry) && entry.path().has_extension() && entry.path().extension() == ".feature"; } - bool IsFeatureFile(const std::filesystem::directory_entry& entry) + void CollectFilesFromDirectory(std::set>& foundFiles, std::filesystem::path folder, Application::Options& options) { - return std::filesystem::is_regular_file(entry) && entry.path().has_extension() && entry.path().extension() == ".feature"; + if (options.recursive) + for (const auto& entry : std::filesystem::recursive_directory_iterator{ folder } | std::views::filter(IsFeatureFile)) + foundFiles.emplace(entry.path()); + else + for (const auto& entry : std::filesystem::directory_iterator{ folder } | std::views::filter(IsFeatureFile)) + foundFiles.emplace(entry.path()); } std::set> GetFeatureFiles(Application::Options& options) { - std::set> files; + std::set> foundFiles; - for (const auto feature : options.paths | std::views::transform(ToFileSystemPath)) + for (const auto feature : options.paths) if (std::filesystem::is_directory(feature)) - for (const auto& entry : std::filesystem::directory_iterator{ feature } | std::views::filter(IsFeatureFile)) - files.emplace(entry.path()); - else - files.emplace(feature); + CollectFilesFromDirectory(foundFiles, feature, options); + else if (IsFeatureFile(std::filesystem::directory_entry{ feature })) + foundFiles.emplace(feature); - return files; + return foundFiles; } } @@ -80,22 +69,19 @@ namespace cucumber_cpp::library : contextStorageFactory{ contextStorageFactory } , removeDefaultGoogleTestListener{ removeDefaultGoogleTestListener } { - cli.parse_complete_callback([this] - { - RunFeatures(); - }); } int Application::Run(int argc, const char* const* argv) { const auto formattersSet = formatters.GetAvailableFormatterNames(); - const auto formatterDescription = std::format("{{{}}}", Join(formattersSet | std::views::transform([](const auto& pair) - { - if (pair.second) - return std::format("{}<<:output>>", pair.first); - else - return pair.first; - }), + + const auto formatterDescription = fmt::format("{{{}}}", fmt::join(formattersSet | std::views::transform([](const auto& pair) + { + if (pair.second) + return fmt::format("{}<<:output>>", pair.first); + else + return pair.first; + }), ",")); CLI::Validator formatValidator{ [&formattersSet](const std::string& str) -> std::string @@ -126,16 +112,19 @@ namespace cucumber_cpp::library auto* retryOpt = cli.add_option("--retry", options.retry, "Number of times to retry failed scenarios")->default_val(options.retry); cli.add_option("--retry-tag-filter", options.retryTagFilter, "Only retry scenarios matching this tag expression")->needs(retryOpt); cli.add_flag("--strict,!--no-strict", options.strict, "Fail if there are pending steps")->default_val(options.strict); + cli.add_flag("--recursive,!--no-recursive", options.recursive, "Search for feature files recursively")->default_val(options.recursive); CLI::deprecate_option(cli.add_option("--tag", options.tags, "Cucumber tag expression"), "-t,--tags"); cli.add_option("-t,--tags", options.tags, "Cucumber tag expression"); CLI::deprecate_option(cli.add_option("-f,--feature", options.paths, "Paths to where your feature files are")->check(CLI::ExistingPath), "paths"); - cli.add_option("paths", options.paths, "Paths to where your feature files are")->required()->check(CLI::ExistingPath); + cli.add_option("paths", options.paths, "Paths to where your feature files are, defaults to \".features\"")->default_val(options.paths); ProgramContext().InsertRef(options); cli.parse(argc, argv); + + return RunFeatures(); } catch (const CLI::ParseError& e) { @@ -144,25 +133,23 @@ namespace cucumber_cpp::library catch (const InternalError& error) { std::cout << std::format("InternalError error:\n{}\n", error.what()); - return 1; + return EXIT_FAILURE; } catch (const cucumber_expression::Error& error) { std::cout << std::format("Cucumber Expression error:\n{}\n", error.what()); - return 1; + return EXIT_FAILURE; } catch (const std::exception& error) { std::cout << std::format("Generic error:\n{}\n", error.what()); - return 1; + return EXIT_FAILURE; } catch (...) { std::cout << "Unknown error"; - return 1; + return EXIT_FAILURE; } - - return GetExitCode(); } CLI::App& Application::CliParser() @@ -185,12 +172,12 @@ namespace cucumber_cpp::library return formatters; } - void Application::RunFeatures() + int Application::RunFeatures() { const auto runOptions = support::RunOptions{ .sources = { .paths = GetFeatureFiles(options), - .tagExpression = tag_expression::Parse(Join(options.tags, " ")), + .tagExpression = tag_expression::Parse(fmt::to_string(fmt::join(options.tags, " "))), .ordering = options.ordering, }, .runtime = { @@ -198,15 +185,11 @@ namespace cucumber_cpp::library .failFast = options.failFast, .retry = options.retry, .strict = options.strict, - .retryTagExpression = tag_expression::Parse(Join(options.retryTagFilter, " ")), + .retryTagExpression = tag_expression::Parse(fmt::to_string(fmt::join(options.retryTagFilter, " "))), }, }; - runPassed = api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, options.format, options.formatOptions); - } - - int Application::GetExitCode() const - { + const auto runPassed = api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, options.format, options.formatOptions); return runPassed ? EXIT_SUCCESS : EXIT_FAILURE; } } diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index effb157d..60cfa000 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -7,7 +7,6 @@ #include "cucumber_cpp/library/Context.hpp" #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -17,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -29,7 +29,7 @@ namespace cucumber_cpp::library { struct Options { - std::set> paths{}; + std::set> paths{ { std::filesystem::path(".") / "features" } }; bool dryRun{ false }; bool failFast{ false }; @@ -46,12 +46,14 @@ namespace cucumber_cpp::library bool strict{ true }; + bool recursive{ true }; + std::vector tags{}; }; - explicit Application(std::shared_ptr contextStorageFactory = std::make_shared(), bool removeDefaultGoogleTestListener = true); + Application(std::shared_ptr contextStorageFactory = std::make_shared(), bool removeDefaultGoogleTestListener = true); - int Run(int argc, const char* const* argv); + [[nodiscard]] int Run(int argc, const char* const* argv); CLI::App& CliParser(); Context& ProgramContext(); @@ -60,9 +62,7 @@ namespace cucumber_cpp::library private: void DryRunFeatures(); - void RunFeatures(); - - [[nodiscard]] int GetExitCode() const; + [[nodiscard]] int RunFeatures(); Options options; From f07be6df559a58a6adc20cdc4e3e6a8ce763792d Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 19 Jan 2026 22:24:02 +0000 Subject: [PATCH 151/196] fix: correct spelling of 'language' in CLI option description --- cucumber_cpp/library/Application.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 5bf88c73..33628293 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -107,7 +107,7 @@ namespace cucumber_cpp::library cli.add_flag("--fail-fast", options.failFast, "Stop execution on first failure"); cli.add_option("--format", options.format, "specify the output format, optionally supply PATH to redirect formatter output.")->check(formatValidator); cli.add_option("--format-options", options.formatOptions, "provide options for formatters"); - cli.add_option("--language", options.language, "Default langauge for feature files, eg 'en'")->default_str(options.language); + cli.add_option("--language", options.language, "Default language for feature files, eg 'en'")->default_str(options.language); cli.add_option("--order", options.ordering, "Run scenarios in specificed order")->transform(CLI::CheckedTransformer(orderingMap, CLI::ignore_case)); auto* retryOpt = cli.add_option("--retry", options.retry, "Number of times to retry failed scenarios")->default_val(options.retry); cli.add_option("--retry-tag-filter", options.retryTagFilter, "Only retry scenarios matching this tag expression")->needs(retryOpt); From 7f51629fa648d9942686b2f0e637b29d350182be Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Mon, 19 Jan 2026 22:56:23 +0000 Subject: [PATCH 152/196] feat: add configuration dumping option and update default values for CLI options --- cucumber_cpp/library/Application.cpp | 20 ++++++++++++++------ cucumber_cpp/library/Application.hpp | 6 ++++-- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 33628293..f1fe414f 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -69,6 +70,7 @@ namespace cucumber_cpp::library : contextStorageFactory{ contextStorageFactory } , removeDefaultGoogleTestListener{ removeDefaultGoogleTestListener } { + cli.set_config("--config", "cucumber.toml"); } int Application::Run(int argc, const char* const* argv) @@ -103,12 +105,14 @@ namespace cucumber_cpp::library { "reverse", support::RunOptions::Ordering::reverse }, }; - cli.add_flag("-d,--dry-run", options.dryRun, "Perform a dry run without executing steps"); - cli.add_flag("--fail-fast", options.failFast, "Stop execution on first failure"); - cli.add_option("--format", options.format, "specify the output format, optionally supply PATH to redirect formatter output.")->check(formatValidator); - cli.add_option("--format-options", options.formatOptions, "provide options for formatters"); + cli.add_flag("--dump-config", options.dumpConfig, "Dump the configuration"); + + cli.add_flag("-d,--dry-run", options.dryRun, "Perform a dry run without executing steps")->default_val(options.dryRun); + cli.add_flag("--fail-fast", options.failFast, "Stop execution on first failure")->default_val(options.failFast); + cli.add_option("--format", options.format, "specify the output format, optionally supply PATH to redirect formatter output.")->check(formatValidator)->default_val(options.format); + cli.add_option("--format-options", options.formatOptions, "provide options for formatters")->default_val(options.formatOptions); cli.add_option("--language", options.language, "Default language for feature files, eg 'en'")->default_str(options.language); - cli.add_option("--order", options.ordering, "Run scenarios in specificed order")->transform(CLI::CheckedTransformer(orderingMap, CLI::ignore_case)); + cli.add_option("--order", options.ordering, "Run scenarios in specificed order")->transform(CLI::CheckedTransformer(orderingMap, CLI::ignore_case))->default_val(options.ordering); auto* retryOpt = cli.add_option("--retry", options.retry, "Number of times to retry failed scenarios")->default_val(options.retry); cli.add_option("--retry-tag-filter", options.retryTagFilter, "Only retry scenarios matching this tag expression")->needs(retryOpt); cli.add_flag("--strict,!--no-strict", options.strict, "Fail if there are pending steps")->default_val(options.strict); @@ -118,12 +122,15 @@ namespace cucumber_cpp::library cli.add_option("-t,--tags", options.tags, "Cucumber tag expression"); CLI::deprecate_option(cli.add_option("-f,--feature", options.paths, "Paths to where your feature files are")->check(CLI::ExistingPath), "paths"); - cli.add_option("paths", options.paths, "Paths to where your feature files are, defaults to \".features\"")->default_val(options.paths); + cli.add_option("paths", options.paths, "Paths to where your feature files are, defaults to \"./features\"")->default_val(options.paths); ProgramContext().InsertRef(options); cli.parse(argc, argv); + if (options.dumpConfig) + std::ofstream{ "cucumber.toml" } << cli.config_to_str(true, true); + return RunFeatures(); } catch (const CLI::ParseError& e) @@ -174,6 +181,7 @@ namespace cucumber_cpp::library int Application::RunFeatures() { + fmt::println("Running with tags: {}", options.tags); const auto runOptions = support::RunOptions{ .sources = { .paths = GetFeatureFiles(options), diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 60cfa000..08836555 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -29,13 +29,15 @@ namespace cucumber_cpp::library { struct Options { + bool dumpConfig{ false }; + std::set> paths{ { std::filesystem::path(".") / "features" } }; bool dryRun{ false }; bool failFast{ false }; - std::set> format{}; - std::string formatOptions{}; + std::set> format{ "summary" }; + std::string formatOptions{ R"({})" }; std::string language{ "en" }; From bf667b303b1c5b8b5f4819b0be6bf6b6ff601ae0 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 20 Jan 2026 15:09:03 +0000 Subject: [PATCH 153/196] enable proper theming for PrettyFormatter --- .vscode/launch.json | 7 +- .../features/test_attachment.feature | 7 + .../features/test_scenario_rules.feature | 23 ++ cucumber_cpp/acceptance_test/steps/Steps.cpp | 5 + .../library/formatter/PrettyFormatter.cpp | 265 +++++++++++++----- .../library/formatter/PrettyFormatter.hpp | 8 +- .../library/formatter/helper/CMakeLists.txt | 2 + .../library/formatter/helper/Theme.cpp | 114 ++++++++ .../library/formatter/helper/Theme.hpp | 108 +++++++ 9 files changed, 461 insertions(+), 78 deletions(-) create mode 100644 cucumber_cpp/acceptance_test/features/test_attachment.feature create mode 100644 cucumber_cpp/acceptance_test/features/test_scenario_rules.feature create mode 100644 cucumber_cpp/library/formatter/helper/Theme.cpp create mode 100644 cucumber_cpp/library/formatter/helper/Theme.hpp diff --git a/.vscode/launch.json b/.vscode/launch.json index 0b54d510..5da11011 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -20,7 +20,8 @@ "@substep", "@table_argument", "@thishasarule", - "@nested_steps" + "@nested_steps", + "@rules" ] }, { @@ -43,11 +44,7 @@ "request": "launch", "program": "${command:cmake.launchTargetPath}", "args": [ - "--com", - "COMx", - "--nordic", "--format", - "summary", "pretty", "--tags", "${input:tag}", diff --git a/cucumber_cpp/acceptance_test/features/test_attachment.feature b/cucumber_cpp/acceptance_test/features/test_attachment.feature new file mode 100644 index 00000000..8dd0425c --- /dev/null +++ b/cucumber_cpp/acceptance_test/features/test_attachment.feature @@ -0,0 +1,7 @@ +@attachment +Feature: Simple feature file + Background: + Given a background step + + Scenario: An OK scenario + Given I attach a link to "my_url.txt" diff --git a/cucumber_cpp/acceptance_test/features/test_scenario_rules.feature b/cucumber_cpp/acceptance_test/features/test_scenario_rules.feature new file mode 100644 index 00000000..2e066607 --- /dev/null +++ b/cucumber_cpp/acceptance_test/features/test_scenario_rules.feature @@ -0,0 +1,23 @@ +@rules +Feature: Simple feature file + Background: + Given a background step + + @result:OK + Scenario: An OK scenario + Given a given step + When a when step + Then a then step + + Rule: a simple rule with two scenarios + @result:FAILED + Scenario: A failing scenario + Given a given step + When a when step + Then an assertion is raised + Then a then step + + @result:UNDEFINED + Scenario: A scenario with undefined step + Given a missing step + Then this should be skipped diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index 0d8b9b3a..7b857d02 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -115,3 +115,8 @@ THEN(R"(the stored string is {string})", (const std::string& expected)) const auto& stored = context.Get("storedstring"); EXPECT_THAT(stored, testing::StrEq(expected)); } + +GIVEN(R"(I attach a link to {string})", (const std::string& url)) +{ + Link(url, "title"); +} diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index 70b93825..a88d1031 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -6,7 +6,6 @@ #include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/pickle_tag.hpp" #include "cucumber/messages/rule.hpp" #include "cucumber/messages/scenario.hpp" #include "cucumber/messages/step.hpp" @@ -16,10 +15,11 @@ #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" -#include "cucumber_cpp/library/support/Join.hpp" +#include "cucumber_cpp/library/formatter/helper/TextBuilder.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" #include "fmt/format.h" #include "fmt/ostream.h" +#include "fmt/ranges.h" #include "nlohmann/json_fwd.hpp" #include #include @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include @@ -41,58 +40,191 @@ namespace cucumber_cpp::library::formatter constexpr auto stepArgumentIndentLength = 2; constexpr auto attachmentIndentLength = 4; - std::map> iconMap{ - { cucumber::messages::test_step_result_status::AMBIGUOUS, "✘" }, - { cucumber::messages::test_step_result_status::FAILED, "✘" }, - { cucumber::messages::test_step_result_status::PASSED, "✔" }, - { cucumber::messages::test_step_result_status::PENDING, "■" }, - { cucumber::messages::test_step_result_status::SKIPPED, "↷" }, - { cucumber::messages::test_step_result_status::UNDEFINED, "■" }, - { cucumber::messages::test_step_result_status::UNKNOWN, " " }, - }; - - std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario) + std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, const helper::Theme& theme = helper::CreatePlainTheme()) { - return fmt::format("{}: {}", scenario.keyword, pickle.name); + return helper::TextBuilder{} + .Append(scenario.keyword + ":", theme.scenario.keyword) + .Space() + .Append(pickle.name, theme.scenario.name) + .Build(theme.scenario.all); } - std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step) + std::string FormatPickleLocation(const cucumber::messages::pickle& pickle, const std::optional& location, const helper::Theme& theme) { - return fmt::format("{}{}", step.keyword, pickleStep.text); + helper::TextBuilder builder{}; + + builder.Append("#") + .Space() + .Append(pickle.uri); + if (location.has_value()) + builder.Append(":") + .Append(std::to_string(location.value().line)); + + return builder.Build(theme.location); } - std::string FormatBase64Attachment(const std::string& body, const std::string& mediaType, const std::optional& filename) + std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const helper::Theme& theme) { - if (!filename) - return helper::ColorFunctions::Attachment(fmt::format("Embedding [{} {} bytes]", mediaType, body.length())); + helper::TextBuilder builder{}; + const auto& stepMatchArgumentsLists = testStep.step_match_arguments_lists; + + if (stepMatchArgumentsLists && stepMatchArgumentsLists->size() == 1) + { + const auto& stepMatchArguments = stepMatchArgumentsLists->front().step_match_arguments; + std::size_t currentIndex = 0; + + for (const auto& argument : stepMatchArguments) + { + const auto& group = argument.group; + + if (group.value.has_value() && group.start.has_value()) + { + const auto text = pickleStep.text.substr(currentIndex, group.start.value() - currentIndex); + currentIndex = group.start.value() + group.value->size(); + builder.Append(text, theme.step.text) + .Append(group.value.value(), theme.step.argument); + } + } + if (currentIndex != pickleStep.text.size()) + { + const auto remainingText = pickleStep.text.substr(currentIndex); + builder.Append(remainingText, theme.step.text); + } + } else - return helper::ColorFunctions::Attachment(fmt::format("Embedding {} [{} {} bytes]", filename.value(), mediaType, body.length())); + builder.Append(pickleStep.text, theme.step.text); + + return builder.Build(); } - std::string FormatTextAttachment(const std::string& body) + std::string FormatCodeLocation(const cucumber::messages::step_definition* stepDefinition, const helper::Theme& theme) { - return helper::ColorFunctions::Attachment(body); + if (stepDefinition != nullptr && stepDefinition->source_reference.uri.has_value()) + { + helper::TextBuilder builder{}; + + builder.Append("#") + .Space() + .Append(stepDefinition->source_reference.uri.value()); + + if (stepDefinition->source_reference.location.has_value()) + builder.Append(":") + .Append(std::to_string(stepDefinition->source_reference.location.value().line)); + return builder.Build(theme.location); + } + + return ""; } - std::string FormatAttachment(const cucumber::messages::attachment& attachment) + std::string FormatFeatureTitle(const cucumber::messages::feature& feature, const helper::Theme& theme) { - if (attachment.content_encoding == cucumber::messages::attachment_content_encoding::BASE64) + return helper::TextBuilder{} + .Append(feature.keyword + ":", theme.feature.keyword) + .Space() + .Append(feature.name, theme.feature.name) + .Build(theme.feature.all); + } + + std::string FormatRuleTitle(const cucumber::messages::rule& rule, const helper::Theme& theme) + { + return helper::TextBuilder{} + .Append(rule.keyword + ":", theme.rule.keyword) + .Space() + .Append(rule.name, theme.rule.name) + .Build(theme.rule.all); + } + + std::string FormatPickleTags(const cucumber::messages::pickle& pickle, const helper::Theme& theme) + { + if (!pickle.tags.empty()) { - return FormatBase64Attachment(attachment.body, attachment.media_type, attachment.file_name); + return helper::TextBuilder{} + .Append(fmt::to_string(fmt::join(pickle.tags | std::views::transform([](const auto& tag) + { + return tag.name; + }), + " "))) + .Build(theme.tag); } - else + return ""; + } + + std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, cucumber::messages::test_step_result_status status, const PrettyFormatter::Options& options) + { + auto builder = helper::TextBuilder{}; + if (options.useStatusIcon) + builder.Append(options.theme.status.Icon(status, " "), options.theme.status.All(status)).Space(); + + return builder.Append(helper::TextBuilder{} + .Append(step.keyword, options.theme.step.keyword) + .Append(FormatStepText(testStep, pickleStep, options.theme), options.theme.status.All(status)) + .Build(options.theme.status.All(status))) + .Build(); + } + + std::string FormatTestRunFinishedError(const cucumber::messages::test_run_finished& testRunFinished, const helper::Theme& theme) + { + if (testRunFinished.message) + { + return helper::TextBuilder{} + .Append(testRunFinished.message.value()) + .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); + } + if (testRunFinished.exception && testRunFinished.exception->stack_trace) { - return FormatTextAttachment(attachment.body); + return helper::TextBuilder{} + .Append(testRunFinished.exception->stack_trace.value()) + .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); } + if (testRunFinished.exception && testRunFinished.exception->message) + { + return helper::TextBuilder{} + .Append(testRunFinished.exception->message.value()) + .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); + } + return ""; + } + + std::string FormatBase64Attachment(const std::string& body, const std::string& mediaType, const std::optional& filename, const helper::Theme& theme) + { + helper::TextBuilder builder{}; + builder.Append("Embedding").Space(); + + if (filename) + builder.Append(filename.value()).Space(); + + builder + .Append("[") + .Append(mediaType) + .Space() + .Append(std::to_string(body.length() / 4 * 3)) + .Space() + .Append("bytes]"); + + return builder.Build(theme.attachment); + } + + std::string FormatTextAttachment(const std::string& body, const helper::Theme& theme) + { + return helper::TextBuilder{}.Append(body).Build(theme.attachment); + } + + std::string FormatAttachment(const cucumber::messages::attachment& attachment, const helper::Theme& theme) + { + if (attachment.content_encoding == cucumber::messages::attachment_content_encoding::BASE64) + return FormatBase64Attachment(attachment.body, attachment.media_type, attachment.file_name, theme); + else + return FormatTextAttachment(attachment.body, theme); return ""; } } PrettyFormatter::Options::Options(const nlohmann::json& formatOptions) - : includeAttachments{ formatOptions.contains("pretty") ? formatOptions.at("pretty").value("include_attachments", true) : true } - , includeFeatureLine{ formatOptions.contains("pretty") ? formatOptions.at("pretty").value("include_feature_line", true) : true } - , includeRuleLine{ formatOptions.contains("pretty") ? formatOptions.at("pretty").value("include_rule_line", true) : true } - , useStatusIcon{ formatOptions.contains("pretty") ? formatOptions.at("pretty").value("use_status_icon", true) : true } + : includeAttachments{ formatOptions.value("include_attachments", true) } + , includeFeatureLine{ formatOptions.value("include_feature_line", true) } + , includeRuleLine{ formatOptions.value("include_rule_line", true) } + , useStatusIcon{ formatOptions.value("use_status_icon", true) } + , theme{ helper::CreateTheme(formatOptions.value("theme", std::string_view{ "cucumber" })) } { } @@ -125,7 +257,7 @@ namespace cucumber_cpp::library::formatter const auto& pickle = query.FindPickleBy(testCaseStarted); const auto& lineage = query.FindLineageByPickle(pickle); const auto& scenario = *lineage.scenario; - const auto scenarioLength = FormatPickleTitle(pickle, scenario).length(); + const auto scenarioLength = helper::Unstyled(FormatPickleTitle(pickle, scenario, options.theme)).length(); const auto& testCase = query.FindTestCaseBy(testCaseStarted); @@ -137,7 +269,7 @@ namespace cucumber_cpp::library::formatter { const auto* pickleStep = query.FindPickleStepBy(testStep); const auto& step = query.FindStepBy(*pickleStep); - return FormatStepTitle(testStep, *pickleStep, step).length(); + return helper::Unstyled(FormatStepTitle(testStep, *pickleStep, step, cucumber::messages::test_step_result_status::UNKNOWN, options)).length(); }; auto steplengths = testCase.test_steps | std::views::filter(hasPickleStepId) | std::views::transform(toLength); @@ -180,9 +312,10 @@ namespace cucumber_cpp::library::formatter return; const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(attachment.test_case_started_id.value()); - const auto content = FormatAttachment(attachment); + const auto content = FormatAttachment(attachment, options.theme); + const std::string indentStr(scenarioIndent + gherkinIndentLength + attachmentIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0), ' '); - fmt::print(outputStream, "{:{}}{}\n", "", scenarioIndent + gherkinIndentLength + attachmentIndentLength, content); + fmt::println(outputStream, "\n{}{}\n", indentStr, content); } void PrettyFormatter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) @@ -204,27 +337,28 @@ namespace cucumber_cpp::library::formatter void PrettyFormatter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) { - if (testRunFinished.exception && testRunFinished.exception->stack_trace) - fmt::print(outputStream, "{}\n", helper::ColorFunctions::ForStatus(cucumber::messages::test_step_result_status::FAILED)(testRunFinished.exception->stack_trace.value())); - else if (testRunFinished.exception && testRunFinished.exception->message) - fmt::print(outputStream, "{}\n", helper::ColorFunctions::ForStatus(cucumber::messages::test_step_result_status::FAILED)(testRunFinished.exception->message.value())); + const auto content = FormatTestRunFinishedError(testRunFinished, options.theme); + + if (!content.empty()) + fmt::println(outputStream, "{}", content); } void PrettyFormatter::PrintFeatureLine(const cucumber::messages::feature& feature) { - if (printedFeatureUris.contains(&feature)) + if (options.includeFeatureLine == false || printedFeatureUris.contains(&feature)) return; - fmt::print(outputStream, "{}: {}\n", feature.keyword, feature.name); + fmt::println(outputStream, "\n{}", FormatFeatureTitle(feature, options.theme)); printedFeatureUris.insert(&feature); } void PrettyFormatter::PrintRuleLine(const cucumber::messages::rule& rule) { - if (printedRuleIds.contains(&rule)) + if (options.includeRuleLine == false || printedRuleIds.contains(&rule)) return; - fmt::print(outputStream, "{:{}}{}: {}\n", "", 2, rule.keyword, rule.name); + fmt::println(outputStream, "\n{}{}", std::string(gherkinIndentLength, ' '), FormatRuleTitle(rule, options.theme)); + printedRuleIds.insert(&rule); } @@ -233,43 +367,34 @@ namespace cucumber_cpp::library::formatter if (pickle.tags.empty()) return; - auto tags = pickle.tags | std::views::transform([](const cucumber::messages::pickle_tag& tag) - { - return tag.name; - }); - std::vector tagVec{ tags.begin(), tags.end() }; - fmt::print(outputStream, "{:{}}{}\n", "", scenarioIndent, helper::ColorFunctions::Tag(support::Join(tagVec, " "))); + fmt::println(outputStream, "{}{}", std::string(scenarioIndent, ' '), FormatPickleTags(pickle, options.theme)); } void PrettyFormatter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) { - PrintGherkinLine("", fmt::format("{}: {}", scenario.keyword, pickle.name), nullptr, pickle.uri, scenario.location.line, scenarioIndent, maxContentLength); + PrintGherkinLine( + FormatPickleTitle(pickle, scenario, options.theme), + FormatPickleLocation(pickle, scenario.location, options.theme), + scenarioIndent, + maxContentLength); } void PrettyFormatter::PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength) { - const auto uri = stepDefinition ? std::make_optional(*stepDefinition->source_reference.uri) : std::nullopt; - const auto line = stepDefinition ? std::make_optional(stepDefinition->source_reference.location->line) : std::nullopt; - - PrintGherkinLine(options.useStatusIcon ? iconMap.at(testStepFinished.test_step_result.status) : "", fmt::format("{}{}", step.keyword, pickleStep.text), helper::ColorFunctions::ForStatus(testStepFinished.test_step_result.status), uri, line, scenarioIndent + 2, maxContentLength); + PrintGherkinLine( + FormatStepTitle(testStep, pickleStep, step, testStepFinished.test_step_result.status, options), + FormatCodeLocation(stepDefinition, options.theme), + scenarioIndent + 2, maxContentLength); } - void PrettyFormatter::PrintGherkinLine(std::string_view icon, std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength) + void PrettyFormatter::PrintGherkinLine(const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength) { - if (title.length() > maxContentLength) - throw std::logic_error("maxContentLength is smaller than title length"); - - const auto padding = maxContentLength - title.length(); + const auto unstyledLength = helper::Unstyled(title).length(); + const auto padding = location.has_value() ? (maxContentLength - std::min(unstyledLength, maxContentLength)) + 1 : 0; - if (!formatTitle) - formatTitle = [](std::string_view str) - { - return std::string{ str }; - }; + const std::string indentStr(indent, ' '); + const std::string paddingStr(padding, ' '); - if (uri.has_value() && line.has_value()) - fmt::print(outputStream, "{:{}}{} {}{:{}} {}\n", "", indent, formatTitle(icon), formatTitle(title), "", padding, helper::ColorFunctions::Location(fmt::format("# {}:{}", *uri, *line))); - else - fmt::print(outputStream, "{:{}}{} {}\n", "", indent, formatTitle(icon), formatTitle(title)); + fmt::println(outputStream, "{}{}{}{}", indentStr, title, paddingStr, location.value_or("")); } } diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.hpp b/cucumber_cpp/library/formatter/PrettyFormatter.hpp index 70040da8..9c0efee6 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.hpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.hpp @@ -16,6 +16,7 @@ #include "cucumber/messages/test_step_finished.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" #include "cucumber_cpp/library/query/Query.hpp" #include #include @@ -35,7 +36,6 @@ namespace cucumber_cpp::library::formatter constexpr static auto name = "pretty"; - private: struct Options { explicit Options(const nlohmann::json& formatOptions); @@ -44,8 +44,10 @@ namespace cucumber_cpp::library::formatter const bool includeFeatureLine; const bool includeRuleLine; const bool useStatusIcon; + const helper::Theme theme; }; + private: void OnEnvelope(const cucumber::messages::envelope& envelope) override; void CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted); @@ -61,9 +63,9 @@ namespace cucumber_cpp::library::formatter void PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength); void PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength); - void PrintGherkinLine(std::string_view icon, std::string_view title, std::function formatTitle, std::optional uri, std::optional line, std::size_t indent, std::size_t maxContentLength); + void PrintGherkinLine(const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength); - Options options{ formatOptions }; + Options options{ formatOptions.contains(name) ? formatOptions.at(name) : nlohmann::json::object() }; std::map testCaseStartedIdToScenarioMap; diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index 2e977160..f4f9f2c8 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -25,6 +25,8 @@ target_sources(cucumber_cpp.library.formatter.helper PRIVATE TestCaseAttemptParser.hpp TextBuilder.cpp TextBuilder.hpp + Theme.cpp + Theme.hpp ) target_include_directories(cucumber_cpp.library.formatter.helper PUBLIC diff --git a/cucumber_cpp/library/formatter/helper/Theme.cpp b/cucumber_cpp/library/formatter/helper/Theme.cpp new file mode 100644 index 00000000..af8bfee5 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/Theme.cpp @@ -0,0 +1,114 @@ +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "fmt/color.h" +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + const std::map> statusColors{ + { cucumber::messages::test_step_result_status::AMBIGUOUS, fmt::fg(fmt::color::red) }, + { cucumber::messages::test_step_result_status::FAILED, fmt::fg(fmt::color::red) }, + { cucumber::messages::test_step_result_status::PASSED, fmt::fg(fmt::color::green) }, + { cucumber::messages::test_step_result_status::PENDING, fmt::fg(fmt::color::yellow) }, + { cucumber::messages::test_step_result_status::SKIPPED, fmt::fg(fmt::color::cyan) }, + { cucumber::messages::test_step_result_status::UNDEFINED, fmt::fg(fmt::color::yellow) }, + { cucumber::messages::test_step_result_status::UNKNOWN, fmt::fg(fmt::color::gray) }, + }; + + const std::map> iconMap{ + { cucumber::messages::test_step_result_status::AMBIGUOUS, "✘" }, + { cucumber::messages::test_step_result_status::FAILED, "✘" }, + { cucumber::messages::test_step_result_status::PASSED, "✔" }, + { cucumber::messages::test_step_result_status::PENDING, "■" }, + { cucumber::messages::test_step_result_status::SKIPPED, "↷" }, + { cucumber::messages::test_step_result_status::UNDEFINED, "■" }, + { cucumber::messages::test_step_result_status::UNKNOWN, " " }, + }; + + const std::map> progressIcons{ + { cucumber::messages::test_step_result_status::AMBIGUOUS, "A" }, + { cucumber::messages::test_step_result_status::FAILED, "F" }, + { cucumber::messages::test_step_result_status::PASSED, "." }, + { cucumber::messages::test_step_result_status::PENDING, "P" }, + { cucumber::messages::test_step_result_status::SKIPPED, "-" }, + { cucumber::messages::test_step_result_status::UNDEFINED, "U" }, + { cucumber::messages::test_step_result_status::UNKNOWN, "?" }, + }; + + std::optional GetColorStyle(std::optional def) + { + return def; + } + + const std::regex ansiEscape{ "\033\\[[^m]+m" }; + } + + Theme CreateEmptyTheme() + { + return {}; + } + + Theme CreateCucumberTheme() + { + static const Theme theme{ + .attachment = fmt::fg(fmt::color::cyan), + .feature = { + .keyword = fmt::emphasis::bold, + }, + .location = fmt::fg(fmt::terminal_color::bright_black), + .rule = { + .keyword = fmt::emphasis::bold, + }, + .scenario = { + .keyword = fmt::emphasis::bold, + }, + .status = { + .all = statusColors, + .icon = iconMap, + .progress = progressIcons, + }, + .step = { + .argument = fmt::emphasis::bold, + .keyword = fmt::emphasis::bold, + }, + .symbol = { .bullet = "•" }, + }; + + return theme; + } + + Theme CreatePlainTheme() + { + static const Theme theme{ + .status = { + .icon = iconMap, + .progress = progressIcons, + }, + .symbol = { .bullet = "-" }, + }; + + return theme; + } + + Theme CreateTheme(std::string_view name) + { + if (name == "cucumber") + return CreateCucumberTheme(); + else if (name == "plain") + return CreatePlainTheme(); + else + return CreateEmptyTheme(); + } + + std::string Unstyled(const std::string& str) + { + return std::regex_replace(str, ansiEscape, ""); + } +} diff --git a/cucumber_cpp/library/formatter/helper/Theme.hpp b/cucumber_cpp/library/formatter/helper/Theme.hpp new file mode 100644 index 00000000..876d59ea --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/Theme.hpp @@ -0,0 +1,108 @@ +#ifndef HELPER_THEME_HPP +#define HELPER_THEME_HPP + +#include "cucumber/messages/test_step_result_status.hpp" +#include "fmt/color.h" +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + struct Theme + { + std::optional attachment{}; + + struct + { + std::optional all{}; + std::optional border{}; + std::optional content{}; + } dataTable; + + struct + { + std::optional all{}; + std::optional content{}; + std::optional delimiter{}; + std::optional mediaType{}; + } docString; + + struct + { + std::optional all{}; + std::optional keyword{}; + std::optional name{}; + } feature; + + std::optional location{}; + + struct + { + std::optional all{}; + std::optional keyword{}; + std::optional name{}; + } rule; + + struct + { + std::optional all{}; + std::optional keyword{}; + std::optional name{}; + } scenario; + + struct + { + std::optional>> all{}; + std::optional>> icon{}; + std::optional>> progress{}; + + fmt::text_style All(cucumber::messages::test_step_result_status status, fmt::text_style defaultStyle = {}) const + { + if (all && all->contains(status)) + return all->at(status); + return defaultStyle; + } + + std::string Icon(cucumber::messages::test_step_result_status status, std::string defaultIcon = {}) const + { + if (icon && icon->contains(status)) + return icon->at(status); + return defaultIcon; + } + + std::string Progress(cucumber::messages::test_step_result_status status, std::string defaultProgress = {}) const + { + if (progress && progress->contains(status)) + return progress->at(status); + return defaultProgress; + } + } status{}; + + struct + { + std::optional argument{}; + std::optional keyword{}; + std::optional text{}; + } step; + + std::optional tag{}; + + struct + { + std::string bullet{}; + } symbol; + }; + + Theme CreateEmptyTheme(); + Theme CreateCucumberTheme(); + Theme CreatePlainTheme(); + + Theme CreateTheme(std::string_view name); + + std::string Unstyled(const std::string& str); +} + +#endif From 2f359f4ba6b6420df74c329633c0e3bb487d620d Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Tue, 20 Jan 2026 23:33:40 +0000 Subject: [PATCH 154/196] feat: add support for ambiguous steps and enhance PrettyFormatter for better output --- .../features/test_scenarios.feature | 5 + cucumber_cpp/acceptance_test/steps/Steps.cpp | 10 ++ .../library/formatter/PrettyFormatter.cpp | 134 +++++++++++++++++- .../library/formatter/PrettyFormatter.hpp | 2 + .../library/formatter/helper/TextBuilder.cpp | 7 +- .../library/formatter/helper/TextBuilder.hpp | 3 +- .../library/formatter/helper/Theme.hpp | 2 +- cucumber_cpp/library/query/Query.cpp | 13 ++ cucumber_cpp/library/query/Query.hpp | 1 + 9 files changed, 168 insertions(+), 9 deletions(-) diff --git a/cucumber_cpp/acceptance_test/features/test_scenarios.feature b/cucumber_cpp/acceptance_test/features/test_scenarios.feature index 07a315ee..d476c23b 100644 --- a/cucumber_cpp/acceptance_test/features/test_scenarios.feature +++ b/cucumber_cpp/acceptance_test/features/test_scenarios.feature @@ -20,3 +20,8 @@ Feature: Simple feature file Scenario: A scenario with undefined step Given a missing step Then this should be skipped + + @result:AMBIGUOUS + Scenario: A scenario with ambiguous step + Given an ambiguous step + Then this should be skipped diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index 7b857d02..05784afc 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -8,6 +8,16 @@ #include #include +GIVEN("an ambiguous step") +{ + // empty +} + +GIVEN("a(n) ambiguous step") +{ + // empty +} + GIVEN("a background step") { // empty diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index a88d1031..dc873271 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -1,11 +1,15 @@ #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/attachment_content_encoding.hpp" +#include "cucumber/messages/data_table.hpp" +#include "cucumber/messages/doc_string.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/feature.hpp" #include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_doc_string.hpp" #include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/pickle_table.hpp" #include "cucumber/messages/rule.hpp" #include "cucumber/messages/scenario.hpp" #include "cucumber/messages/step.hpp" @@ -25,8 +29,10 @@ #include #include #include +#include #include #include +#include #include #include #include @@ -39,6 +45,18 @@ namespace cucumber_cpp::library::formatter constexpr auto gherkinIndentLength = 2; constexpr auto stepArgumentIndentLength = 2; constexpr auto attachmentIndentLength = 4; + constexpr auto errorIndentLength = 4; + + const auto transformToString = [](auto subrange) + { + return std::string_view{ subrange.begin(), subrange.end() }; + }; + + void PrintlnIndentedContent(std::ostream& os, std::string_view content, std::size_t indent) + { + const std::string indentStr(indent, ' '); + fmt::println(os, "{}{}", indentStr, fmt::join(content | std::views::split('\n') | std::views::transform(transformToString), "\n" + indentStr)); + } std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, const helper::Theme& theme = helper::CreatePlainTheme()) { @@ -162,6 +180,89 @@ namespace cucumber_cpp::library::formatter .Build(); } + std::string FormatDocString(const cucumber::messages::pickle_doc_string& pickleDocString, const helper::Theme& theme) + { + helper::TextBuilder builder{}; + builder.Append(R"(""")", theme.docString.delimiter); + if (pickleDocString.media_type.has_value()) + builder.Append(pickleDocString.media_type.value(), theme.docString.mediaType); + builder.Line(); + + for (const auto& line : pickleDocString.content | std::views::split('\n') | std::views::transform(transformToString)) + builder.Append(line).Line(); + + builder.Append(R"(""")", theme.docString.delimiter); + + return builder.Build(theme.docString.all, true); + } + + std::vector CalcualteColumnWidths(const cucumber::messages::pickle_table& pickleDataTable) + { + std::vector columnWidths(pickleDataTable.rows.empty() ? 0 : pickleDataTable.rows.front().cells.size(), 0); + + for (const auto& row : pickleDataTable.rows) + for (std::size_t colIndex = 0; colIndex < row.cells.size(); ++colIndex) + { + const auto cellContentLength = row.cells[colIndex].value.length(); + columnWidths[colIndex] = std::max(columnWidths[colIndex], cellContentLength); + } + + return columnWidths; + } + + std::string FormatDataTable(const cucumber::messages::pickle_table& pickleDataTable, const helper::Theme& theme) + { + const auto columnWidths = CalcualteColumnWidths(pickleDataTable); + helper::TextBuilder builder{}; + + for (auto rowIndex = 0; rowIndex != pickleDataTable.rows.size(); ++rowIndex) + { + const auto& row = pickleDataTable.rows[rowIndex]; + + if (rowIndex > 0) + builder.Line(); + builder.Append("|", theme.dataTable.border); + + for (auto colIndex = 0; colIndex != pickleDataTable.rows[rowIndex].cells.size(); ++colIndex) + { + const auto& cell = row.cells[colIndex]; + builder.Append(fmt::format(" {:<{}} ", cell.value, columnWidths[colIndex]), theme.dataTable.content) + .Append("|", theme.dataTable.border); + } + } + + return builder.Build(theme.dataTable.all, true); + } + + std::string FormatPickleStepArgument(const cucumber::messages::pickle_step& pickleStep, const helper::Theme& theme) + { + if (pickleStep.argument && pickleStep.argument->doc_string.has_value()) + return FormatDocString(pickleStep.argument->doc_string.value(), theme); + + if (pickleStep.argument && pickleStep.argument->data_table.has_value()) + return FormatDataTable(pickleStep.argument->data_table.value(), theme); + + return ""; + } + + std::string FormatAmbiguousStep(const std::list& stepDefinitions, const helper::Theme& theme) + { + helper::TextBuilder builder{}; + builder.Append("Multiple matching step definitions found:"); + for (const auto* stepDefinition : stepDefinitions) + { + builder.Line().Append(" " + theme.symbol.bullet + " "); + + if (!stepDefinition->pattern.source.empty()) + builder.Append(stepDefinition->pattern.source); + + const auto location = FormatCodeLocation(stepDefinition, theme); + if (!location.empty()) + builder.Space().Append(location); + } + return builder.Build({}, true); + } + std::string FormatTestRunFinishedError(const cucumber::messages::test_run_finished& testRunFinished, const helper::Theme& theme) { if (testRunFinished.message) @@ -170,13 +271,13 @@ namespace cucumber_cpp::library::formatter .Append(testRunFinished.message.value()) .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); } - if (testRunFinished.exception && testRunFinished.exception->stack_trace) + else if (testRunFinished.exception && testRunFinished.exception->stack_trace) { return helper::TextBuilder{} .Append(testRunFinished.exception->stack_trace.value()) .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); } - if (testRunFinished.exception && testRunFinished.exception->message) + else if (testRunFinished.exception && testRunFinished.exception->message) { return helper::TextBuilder{} .Append(testRunFinished.exception->message.value()) @@ -332,6 +433,8 @@ namespace cucumber_cpp::library::formatter const auto* stepDefinition = (testStep.step_definition_ids && !testStep.step_definition_ids->empty()) ? &query.FindStepDefinitionById(testStep.step_definition_ids->front()) : nullptr; PrintStepLine(testStepFinished, testStep, *pickleStep, step, stepDefinition, scenarioIndent, maxContentLength); + PrintStepArgument(*pickleStep, scenarioIndent, options.theme); + PrintAmbiguousStep(testStepFinished, testStep, scenarioIndent); } } @@ -387,14 +490,37 @@ namespace cucumber_cpp::library::formatter scenarioIndent + 2, maxContentLength); } + void PrettyFormatter::PrintStepArgument(const cucumber::messages::pickle_step& pickleStep, std::size_t scenarioIndent, const helper::Theme& theme) + { + const auto content = FormatPickleStepArgument(pickleStep, options.theme); + if (content.empty()) + return; + + PrintlnIndentedContent(outputStream, content, scenarioIndent + gherkinIndentLength + stepArgumentIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0)); + } + + void PrettyFormatter::PrintAmbiguousStep(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent) + { + if (testStepFinished.test_step_result.status != cucumber::messages::test_step_result_status::AMBIGUOUS) + return; + + const auto list = query.FindStepDefinitionsById(testStep); + const auto content = FormatAmbiguousStep(list, options.theme); + + if (content.empty()) + return; + + PrintlnIndentedContent(outputStream, content, scenarioIndent + gherkinIndentLength + errorIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0)); + } + void PrettyFormatter::PrintGherkinLine(const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength) { const auto unstyledLength = helper::Unstyled(title).length(); const auto padding = location.has_value() ? (maxContentLength - std::min(unstyledLength, maxContentLength)) + 1 : 0; - const std::string indentStr(indent, ' '); const std::string paddingStr(padding, ' '); + const auto content = fmt::format("{}{}{}", title, paddingStr, location.value_or("")); - fmt::println(outputStream, "{}{}{}{}", indentStr, title, paddingStr, location.value_or("")); + PrintlnIndentedContent(outputStream, content, indent); } } diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.hpp b/cucumber_cpp/library/formatter/PrettyFormatter.hpp index 9c0efee6..a7eb01ab 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.hpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.hpp @@ -62,6 +62,8 @@ namespace cucumber_cpp::library::formatter void PrintTags(const cucumber::messages::pickle& pickle, std::size_t scenarioIndent); void PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength); void PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength); + void PrintStepArgument(const cucumber::messages::pickle_step& pickleStep, std::size_t scenarioIndent, const helper::Theme& theme); + void PrintAmbiguousStep(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent); void PrintGherkinLine(const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength); diff --git a/cucumber_cpp/library/formatter/helper/TextBuilder.cpp b/cucumber_cpp/library/formatter/helper/TextBuilder.cpp index 21d78dc2..1ea51551 100644 --- a/cucumber_cpp/library/formatter/helper/TextBuilder.cpp +++ b/cucumber_cpp/library/formatter/helper/TextBuilder.cpp @@ -6,15 +6,16 @@ #include #include #include +#include namespace cucumber_cpp::library::formatter::helper { namespace { - std::string ApplyStyle(const std::string& text, std::optional style) + std::string ApplyStyle(std::string_view text, std::optional style) { if (!style) - return text; + return std::string{ text }; return fmt::format(*style, "{}", text); } @@ -32,7 +33,7 @@ namespace cucumber_cpp::library::formatter::helper return *this; } - TextBuilder& TextBuilder::Append(const std::string& text, std::optional style) + TextBuilder& TextBuilder::Append(std::string_view text, std::optional style) { this->text += ApplyStyle(text, style); return *this; diff --git a/cucumber_cpp/library/formatter/helper/TextBuilder.hpp b/cucumber_cpp/library/formatter/helper/TextBuilder.hpp index c4b3d9e2..fd746f8c 100644 --- a/cucumber_cpp/library/formatter/helper/TextBuilder.hpp +++ b/cucumber_cpp/library/formatter/helper/TextBuilder.hpp @@ -4,6 +4,7 @@ #include "fmt/color.h" #include #include +#include namespace cucumber_cpp::library::formatter::helper { @@ -11,7 +12,7 @@ namespace cucumber_cpp::library::formatter::helper { TextBuilder& Space(); TextBuilder& Line(); - TextBuilder& Append(const std::string& text, std::optional style = std::nullopt); + TextBuilder& Append(std::string_view text, std::optional style = std::nullopt); std::string Build(std::optional style = std::nullopt, bool styleEachLine = false) const; private: diff --git a/cucumber_cpp/library/formatter/helper/Theme.hpp b/cucumber_cpp/library/formatter/helper/Theme.hpp index 876d59ea..4805d20d 100644 --- a/cucumber_cpp/library/formatter/helper/Theme.hpp +++ b/cucumber_cpp/library/formatter/helper/Theme.hpp @@ -92,7 +92,7 @@ namespace cucumber_cpp::library::formatter::helper struct { - std::string bullet{}; + std::string bullet{ " " }; } symbol; }; diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index 6995470c..8395bf55 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -189,6 +189,19 @@ namespace cucumber_cpp::library::query return stepDefinitionById.at(id); } + std::list Query::FindStepDefinitionsById(const cucumber::messages::test_step& testStep) const + { + if (!testStep.step_definition_ids.has_value()) + return {}; + + auto view = testStep.step_definition_ids.value() | std::views::transform([this](const std::string& id) + { + return &FindStepDefinitionById(id); + }); + + return { view.begin(), view.end() }; + } + const cucumber::messages::location& Query::FindLocationOf(const cucumber::messages::pickle& pickle) const { const auto& lineage = FindLineageByUri(pickle.uri); diff --git a/cucumber_cpp/library/query/Query.hpp b/cucumber_cpp/library/query/Query.hpp index b524c2a1..c6e45b06 100644 --- a/cucumber_cpp/library/query/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -143,6 +143,7 @@ namespace cucumber_cpp::library::query const cucumber::messages::step& FindStepBy(const cucumber::messages::pickle_step& pickleStep) const; const cucumber::messages::step_definition& FindStepDefinitionById(const std::string& id) const; + std::list FindStepDefinitionsById(const cucumber::messages::test_step& testStep) const; const cucumber::messages::location& FindLocationOf(const cucumber::messages::pickle& pickle) const; From 366d9d292fd6a09896415ad92f617ecb07bbf08a Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 00:02:17 +0000 Subject: [PATCH 155/196] feat: update FindLocationOf to return optional location for better error handling --- cucumber_cpp/library/query/Query.cpp | 6 ++++-- cucumber_cpp/library/query/Query.hpp | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index 8395bf55..be3760d7 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -202,12 +202,14 @@ namespace cucumber_cpp::library::query return { view.begin(), view.end() }; } - const cucumber::messages::location& Query::FindLocationOf(const cucumber::messages::pickle& pickle) const + std::optional Query::FindLocationOf(const cucumber::messages::pickle& pickle) const { const auto& lineage = FindLineageByUri(pickle.uri); if (lineage.tableRow) return lineage.tableRow->location; - return lineage.scenario->location; + if (lineage.scenario) + return lineage.scenario->location; + return std::nullopt; } const std::map>& Query::TestCaseStarted() const diff --git a/cucumber_cpp/library/query/Query.hpp b/cucumber_cpp/library/query/Query.hpp index c6e45b06..45d1e16e 100644 --- a/cucumber_cpp/library/query/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -145,7 +145,7 @@ namespace cucumber_cpp::library::query const cucumber::messages::step_definition& FindStepDefinitionById(const std::string& id) const; std::list FindStepDefinitionsById(const cucumber::messages::test_step& testStep) const; - const cucumber::messages::location& FindLocationOf(const cucumber::messages::pickle& pickle) const; + std::optional FindLocationOf(const cucumber::messages::pickle& pickle) const; const std::map>& TestCaseStarted() const; const std::map>& TestCaseFinishedByTestCaseStartedId() const; From 1dff606fddae6ed39960ec8a87c776961ff353c9 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 00:02:28 +0000 Subject: [PATCH 156/196] feat: refactor PrettyFormatter to improve error handling and streamline includes --- .../library/formatter/PrettyFormatter.cpp | 63 +++++++++++++++---- .../library/formatter/PrettyFormatter.hpp | 4 +- 2 files changed, 52 insertions(+), 15 deletions(-) diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index dc873271..2f1b589c 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -1,8 +1,6 @@ #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/attachment_content_encoding.hpp" -#include "cucumber/messages/data_table.hpp" -#include "cucumber/messages/doc_string.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/feature.hpp" #include "cucumber/messages/location.hpp" @@ -18,6 +16,7 @@ #include "cucumber/messages/test_run_finished.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/TextBuilder.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" @@ -28,7 +27,6 @@ #include #include #include -#include #include #include #include @@ -52,6 +50,16 @@ namespace cucumber_cpp::library::formatter return std::string_view{ subrange.begin(), subrange.end() }; }; + [[nodiscard]] std::string Trim(const std::string& str) + { + const auto start = str.find_first_not_of(" \t\n\r"); + if (start == std::string::npos) + return ""; + + const auto end = str.find_last_not_of(" \t\n\r"); + return str.substr(start, end - start + 1); + } + void PrintlnIndentedContent(std::ostream& os, std::string_view content, std::size_t indent) { const std::string indentStr(indent, ' '); @@ -263,26 +271,48 @@ namespace cucumber_cpp::library::formatter return builder.Build({}, true); } - std::string FormatTestRunFinishedError(const cucumber::messages::test_run_finished& testRunFinished, const helper::Theme& theme) + std::string FormatTestStepResultError(const cucumber::messages::test_step_result& testStepResult, const helper::Theme& theme) { - if (testRunFinished.message) + if (testStepResult.exception.has_value() && testStepResult.exception.value().stack_trace.has_value()) { return helper::TextBuilder{} - .Append(testRunFinished.message.value()) - .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); + .Append(Trim(testStepResult.exception.value().stack_trace.value())) + .Build(theme.status.All(testStepResult.status), true); } - else if (testRunFinished.exception && testRunFinished.exception->stack_trace) + + if (testStepResult.exception.has_value() && testStepResult.exception.value().message.has_value()) { return helper::TextBuilder{} - .Append(testRunFinished.exception->stack_trace.value()) + .Append(Trim(testStepResult.exception.value().message.value())) + .Build(theme.status.All(testStepResult.status), true); + } + + if (testStepResult.message.has_value()) + { + return helper::TextBuilder{} + .Append(Trim(testStepResult.message.value())) + .Build(theme.status.All(testStepResult.status), true); + } + + return ""; + } + + std::string FormatTestRunFinishedError(const cucumber::messages::test_run_finished& testRunFinished, const helper::Theme& theme) + { + if (testRunFinished.exception && testRunFinished.exception->stack_trace) + { + return helper::TextBuilder{} + .Append(Trim(testRunFinished.exception->stack_trace.value())) .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); } - else if (testRunFinished.exception && testRunFinished.exception->message) + + if (testRunFinished.exception && testRunFinished.exception->message) { return helper::TextBuilder{} - .Append(testRunFinished.exception->message.value()) + .Append(Trim(testRunFinished.exception->message.value())) .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); } + return ""; } @@ -390,7 +420,6 @@ namespace cucumber_cpp::library::formatter void PrettyFormatter::HandleTestCaseStarted(const cucumber::messages::test_case_started& testCaseStarted) { const auto& pickle = query.FindPickleBy(testCaseStarted); - const auto& location = query.FindLocationOf(pickle); const auto& lineage = query.FindLineageByPickle(pickle); const auto& scenario = lineage.scenario; const auto& rule = lineage.rule; @@ -436,6 +465,7 @@ namespace cucumber_cpp::library::formatter PrintStepArgument(*pickleStep, scenarioIndent, options.theme); PrintAmbiguousStep(testStepFinished, testStep, scenarioIndent); } + PrintError(testStepFinished, scenarioIndent); } void PrettyFormatter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) @@ -513,6 +543,15 @@ namespace cucumber_cpp::library::formatter PrintlnIndentedContent(outputStream, content, scenarioIndent + gherkinIndentLength + errorIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0)); } + void PrettyFormatter::PrintError(const cucumber::messages::test_step_finished& testStepFinished, std::size_t scenarioIndent) + { + const auto content = FormatTestStepResultError(testStepFinished.test_step_result, options.theme); + if (content.empty()) + return; + + PrintlnIndentedContent(outputStream, content, scenarioIndent + gherkinIndentLength + errorIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0)); + } + void PrettyFormatter::PrintGherkinLine(const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength) { const auto unstyledLength = helper::Unstyled(title).length(); diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.hpp b/cucumber_cpp/library/formatter/PrettyFormatter.hpp index a7eb01ab..74031f55 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.hpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.hpp @@ -17,15 +17,12 @@ #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" -#include "cucumber_cpp/library/query/Query.hpp" #include -#include #include #include #include #include #include -#include namespace cucumber_cpp::library::formatter { @@ -64,6 +61,7 @@ namespace cucumber_cpp::library::formatter void PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength); void PrintStepArgument(const cucumber::messages::pickle_step& pickleStep, std::size_t scenarioIndent, const helper::Theme& theme); void PrintAmbiguousStep(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent); + void PrintError(const cucumber::messages::test_step_finished& testStepFinished, std::size_t scenarioIndent); void PrintGherkinLine(const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength); From 2dc68e074e4420ed5a79576257df0bd34d04f623 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 00:02:32 +0000 Subject: [PATCH 157/196] feat: enhance sanitizer options for better memory error detection --- CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index dede778e..adc3cd62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -26,8 +26,8 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) if (CCR_STANDALONE) if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) - add_compile_options(-fsanitize=address) - add_link_options(-fsanitize=address) + add_compile_options(-fsanitize=address -fsanitize=undefined) + add_link_options(-fsanitize=address -fsanitize=undefined) endif() endif() From 49fd5f599bf506422bcf909c7b6a5f637e1dc462 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 00:12:29 +0000 Subject: [PATCH 158/196] feat: enhance PrettyFormatter to include feature and rule line options --- cucumber_cpp/library/formatter/PrettyFormatter.cpp | 10 +++++++--- cucumber_cpp/library/formatter/helper/Theme.cpp | 2 ++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index 2f1b589c..74e7b57c 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -411,9 +411,13 @@ namespace cucumber_cpp::library::formatter maxContentLengthByTestCaseStartedId[testCaseStarted.id] = std::max(scenarioLength, options.useStatusIcon ? maxStepLength + 2 : maxStepLength); std::size_t scenarioIndent{ 0 }; - scenarioIndent += 2; - if (lineage.rule) - scenarioIndent += 2; + if (options.includeFeatureLine) + { + scenarioIndent += gherkinIndentLength; + if (options.includeRuleLine && lineage.rule) + scenarioIndent += gherkinIndentLength; + } + scenarioIndentByTestCaseStartedId[testCaseStarted.id] = scenarioIndent; } diff --git a/cucumber_cpp/library/formatter/helper/Theme.cpp b/cucumber_cpp/library/formatter/helper/Theme.cpp index af8bfee5..84d5d6c3 100644 --- a/cucumber_cpp/library/formatter/helper/Theme.cpp +++ b/cucumber_cpp/library/formatter/helper/Theme.cpp @@ -103,6 +103,8 @@ namespace cucumber_cpp::library::formatter::helper return CreateCucumberTheme(); else if (name == "plain") return CreatePlainTheme(); + else if (name == "none") + return CreateEmptyTheme(); else return CreateEmptyTheme(); } From 320e3d260e58a83dcbeea1c2942d45bb60c64d50 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 00:15:15 +0000 Subject: [PATCH 159/196] feat: add logging for running features with specified tags --- cucumber_cpp/library/Application.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index f1fe414f..1e8acf08 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -7,6 +7,7 @@ #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include "cucumber_cpp/library/support/Types.hpp" #include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "fmt/base.h" #include "fmt/format.h" #include "fmt/ranges.h" #include @@ -182,6 +183,7 @@ namespace cucumber_cpp::library int Application::RunFeatures() { fmt::println("Running with tags: {}", options.tags); + const auto runOptions = support::RunOptions{ .sources = { .paths = GetFeatureFiles(options), From 35bbd08dc9f3fbebe8e783a728ea68849b84970a Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 20:46:54 +0000 Subject: [PATCH 160/196] feat: remove Rtrim implementation and replace with util::Trim for consistency --- cucumber_cpp/library/CMakeLists.txt | 2 -- cucumber_cpp/library/Rtrim.cpp | 17 --------------- cucumber_cpp/library/Rtrim.hpp | 11 ---------- .../library/formatter/JunitXmlFormatter.cpp | 13 ++---------- .../library/formatter/PrettyFormatter.cpp | 21 ++++++------------- cucumber_cpp/library/util/CMakeLists.txt | 2 +- cucumber_cpp/library/util/Trim.hpp | 19 +++++++++++++++++ 7 files changed, 28 insertions(+), 57 deletions(-) delete mode 100644 cucumber_cpp/library/Rtrim.cpp delete mode 100644 cucumber_cpp/library/Rtrim.hpp create mode 100644 cucumber_cpp/library/util/Trim.hpp diff --git a/cucumber_cpp/library/CMakeLists.txt b/cucumber_cpp/library/CMakeLists.txt index e893b440..6e12e776 100644 --- a/cucumber_cpp/library/CMakeLists.txt +++ b/cucumber_cpp/library/CMakeLists.txt @@ -8,8 +8,6 @@ target_sources(cucumber_cpp.library PRIVATE Errors.hpp Hooks.hpp Parameter.hpp - Rtrim.cpp - Rtrim.hpp Steps.hpp ) diff --git a/cucumber_cpp/library/Rtrim.cpp b/cucumber_cpp/library/Rtrim.cpp deleted file mode 100644 index 5acd7591..00000000 --- a/cucumber_cpp/library/Rtrim.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "cucumber_cpp/library/Rtrim.hpp" -#include -#include -#include - -namespace cucumber_cpp::library -{ - void Rtrim(std::string& s) - { - s.erase(std::find_if(s.rbegin(), s.rend(), [](unsigned char ch) - { - return std::isspace(ch) == 0; - }) - .base(), - s.end()); - } -} diff --git a/cucumber_cpp/library/Rtrim.hpp b/cucumber_cpp/library/Rtrim.hpp deleted file mode 100644 index afd0bbe0..00000000 --- a/cucumber_cpp/library/Rtrim.hpp +++ /dev/null @@ -1,11 +0,0 @@ -#ifndef CUCUMBER_CPP_RTRIM_HPP -#define CUCUMBER_CPP_RTRIM_HPP - -#include - -namespace cucumber_cpp::library -{ - void Rtrim(std::string& s); -} - -#endif diff --git a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp index e34830f2..b0c62879 100644 --- a/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp +++ b/cucumber_cpp/library/formatter/JunitXmlFormatter.cpp @@ -10,6 +10,7 @@ #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/util/Duration.hpp" #include "cucumber_cpp/library/util/Timestamp.hpp" +#include "cucumber_cpp/library/util/Trim.hpp" #include "fmt/format.h" #include "fmt/ranges.h" #include "nlohmann/json_fwd.hpp" @@ -28,16 +29,6 @@ namespace cucumber_cpp::library::formatter { namespace { - [[nodiscard]] std::string trim(const std::string& str) - { - const auto start = str.find_first_not_of(" \t\n\r"); - if (start == std::string::npos) - return ""; - - const auto end = str.find_last_not_of(" \t\n\r"); - return str.substr(start, end - start + 1); - } - enum class FailureKind { failure, @@ -97,7 +88,7 @@ namespace cucumber_cpp::library::formatter return std::tolower(c); }); - return fmt::format("{:.<76}{}", trim(gherkinStep.keyword) + " " + trim(pickleStep.text), statusString); + return fmt::format("{:.<76}{}", util::Trim(gherkinStep.keyword) + " " + util::Trim(pickleStep.text), statusString); } std::string MakeOutput(query::Query& query, const cucumber::messages::test_case_started& testCaseStarted) diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index 74e7b57c..f051ca4b 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -20,6 +20,7 @@ #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/TextBuilder.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include "cucumber_cpp/library/util/Trim.hpp" #include "fmt/format.h" #include "fmt/ostream.h" #include "fmt/ranges.h" @@ -50,16 +51,6 @@ namespace cucumber_cpp::library::formatter return std::string_view{ subrange.begin(), subrange.end() }; }; - [[nodiscard]] std::string Trim(const std::string& str) - { - const auto start = str.find_first_not_of(" \t\n\r"); - if (start == std::string::npos) - return ""; - - const auto end = str.find_last_not_of(" \t\n\r"); - return str.substr(start, end - start + 1); - } - void PrintlnIndentedContent(std::ostream& os, std::string_view content, std::size_t indent) { const std::string indentStr(indent, ' '); @@ -276,21 +267,21 @@ namespace cucumber_cpp::library::formatter if (testStepResult.exception.has_value() && testStepResult.exception.value().stack_trace.has_value()) { return helper::TextBuilder{} - .Append(Trim(testStepResult.exception.value().stack_trace.value())) + .Append(util::Trim(testStepResult.exception.value().stack_trace.value())) .Build(theme.status.All(testStepResult.status), true); } if (testStepResult.exception.has_value() && testStepResult.exception.value().message.has_value()) { return helper::TextBuilder{} - .Append(Trim(testStepResult.exception.value().message.value())) + .Append(util::Trim(testStepResult.exception.value().message.value())) .Build(theme.status.All(testStepResult.status), true); } if (testStepResult.message.has_value()) { return helper::TextBuilder{} - .Append(Trim(testStepResult.message.value())) + .Append(util::Trim(testStepResult.message.value())) .Build(theme.status.All(testStepResult.status), true); } @@ -302,14 +293,14 @@ namespace cucumber_cpp::library::formatter if (testRunFinished.exception && testRunFinished.exception->stack_trace) { return helper::TextBuilder{} - .Append(Trim(testRunFinished.exception->stack_trace.value())) + .Append(util::Trim(testRunFinished.exception->stack_trace.value())) .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); } if (testRunFinished.exception && testRunFinished.exception->message) { return helper::TextBuilder{} - .Append(Trim(testRunFinished.exception->message.value())) + .Append(util::Trim(testRunFinished.exception->message.value())) .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); } diff --git a/cucumber_cpp/library/util/CMakeLists.txt b/cucumber_cpp/library/util/CMakeLists.txt index df9d532f..6f4411a3 100644 --- a/cucumber_cpp/library/util/CMakeLists.txt +++ b/cucumber_cpp/library/util/CMakeLists.txt @@ -10,7 +10,7 @@ target_sources(cucumber_cpp.library.util PRIVATE Immoveable.hpp Timestamp.cpp Timestamp.hpp - + Trim.hpp ) target_include_directories(cucumber_cpp.library.util PUBLIC diff --git a/cucumber_cpp/library/util/Trim.hpp b/cucumber_cpp/library/util/Trim.hpp new file mode 100644 index 00000000..5155c68e --- /dev/null +++ b/cucumber_cpp/library/util/Trim.hpp @@ -0,0 +1,19 @@ +#ifndef UTIL_TRIM_HPP +#define UTIL_TRIM_HPP + +#include + +namespace cucumber_cpp::library::util +{ + [[nodiscard]] inline std::string Trim(const std::string& str, const char* whitespace = " \t\n\r") + { + const auto start = str.find_first_not_of(whitespace); + if (start == std::string::npos) + return ""; + + const auto end = str.find_last_not_of(whitespace); + return str.substr(start, end - start + 1); + } +} + +#endif From 973f947ca86d45e96ffef0f83b599c6e934802c1 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 21:27:09 +0000 Subject: [PATCH 161/196] feat: refactor PrettyFormatter to use helper functions for message formatting --- .../library/formatter/PrettyFormatter.cpp | 298 +--------------- .../library/formatter/helper/CMakeLists.txt | 2 + .../formatter/helper/FormatMessages.cpp | 324 ++++++++++++++++++ .../formatter/helper/FormatMessages.hpp | 45 +++ 4 files changed, 376 insertions(+), 293 deletions(-) create mode 100644 cucumber_cpp/library/formatter/helper/FormatMessages.cpp create mode 100644 cucumber_cpp/library/formatter/helper/FormatMessages.hpp diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index f051ca4b..a2a1e4e3 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -1,13 +1,9 @@ #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber/messages/attachment.hpp" -#include "cucumber/messages/attachment_content_encoding.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/feature.hpp" -#include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_doc_string.hpp" #include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/pickle_table.hpp" #include "cucumber/messages/rule.hpp" #include "cucumber/messages/scenario.hpp" #include "cucumber/messages/step.hpp" @@ -18,9 +14,8 @@ #include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/formatter/helper/TextBuilder.hpp" +#include "cucumber_cpp/library/formatter/helper/FormatMessages.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" -#include "cucumber_cpp/library/util/Trim.hpp" #include "fmt/format.h" #include "fmt/ostream.h" #include "fmt/ranges.h" @@ -28,7 +23,6 @@ #include #include #include -#include #include #include #include @@ -57,288 +51,6 @@ namespace cucumber_cpp::library::formatter fmt::println(os, "{}{}", indentStr, fmt::join(content | std::views::split('\n') | std::views::transform(transformToString), "\n" + indentStr)); } - std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, const helper::Theme& theme = helper::CreatePlainTheme()) - { - return helper::TextBuilder{} - .Append(scenario.keyword + ":", theme.scenario.keyword) - .Space() - .Append(pickle.name, theme.scenario.name) - .Build(theme.scenario.all); - } - - std::string FormatPickleLocation(const cucumber::messages::pickle& pickle, const std::optional& location, const helper::Theme& theme) - { - helper::TextBuilder builder{}; - - builder.Append("#") - .Space() - .Append(pickle.uri); - if (location.has_value()) - builder.Append(":") - .Append(std::to_string(location.value().line)); - - return builder.Build(theme.location); - } - - std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const helper::Theme& theme) - { - helper::TextBuilder builder{}; - const auto& stepMatchArgumentsLists = testStep.step_match_arguments_lists; - - if (stepMatchArgumentsLists && stepMatchArgumentsLists->size() == 1) - { - const auto& stepMatchArguments = stepMatchArgumentsLists->front().step_match_arguments; - std::size_t currentIndex = 0; - - for (const auto& argument : stepMatchArguments) - { - const auto& group = argument.group; - - if (group.value.has_value() && group.start.has_value()) - { - const auto text = pickleStep.text.substr(currentIndex, group.start.value() - currentIndex); - currentIndex = group.start.value() + group.value->size(); - builder.Append(text, theme.step.text) - .Append(group.value.value(), theme.step.argument); - } - } - if (currentIndex != pickleStep.text.size()) - { - const auto remainingText = pickleStep.text.substr(currentIndex); - builder.Append(remainingText, theme.step.text); - } - } - else - builder.Append(pickleStep.text, theme.step.text); - - return builder.Build(); - } - - std::string FormatCodeLocation(const cucumber::messages::step_definition* stepDefinition, const helper::Theme& theme) - { - if (stepDefinition != nullptr && stepDefinition->source_reference.uri.has_value()) - { - helper::TextBuilder builder{}; - - builder.Append("#") - .Space() - .Append(stepDefinition->source_reference.uri.value()); - - if (stepDefinition->source_reference.location.has_value()) - builder.Append(":") - .Append(std::to_string(stepDefinition->source_reference.location.value().line)); - return builder.Build(theme.location); - } - - return ""; - } - - std::string FormatFeatureTitle(const cucumber::messages::feature& feature, const helper::Theme& theme) - { - return helper::TextBuilder{} - .Append(feature.keyword + ":", theme.feature.keyword) - .Space() - .Append(feature.name, theme.feature.name) - .Build(theme.feature.all); - } - - std::string FormatRuleTitle(const cucumber::messages::rule& rule, const helper::Theme& theme) - { - return helper::TextBuilder{} - .Append(rule.keyword + ":", theme.rule.keyword) - .Space() - .Append(rule.name, theme.rule.name) - .Build(theme.rule.all); - } - - std::string FormatPickleTags(const cucumber::messages::pickle& pickle, const helper::Theme& theme) - { - if (!pickle.tags.empty()) - { - return helper::TextBuilder{} - .Append(fmt::to_string(fmt::join(pickle.tags | std::views::transform([](const auto& tag) - { - return tag.name; - }), - " "))) - .Build(theme.tag); - } - return ""; - } - - std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, cucumber::messages::test_step_result_status status, const PrettyFormatter::Options& options) - { - auto builder = helper::TextBuilder{}; - if (options.useStatusIcon) - builder.Append(options.theme.status.Icon(status, " "), options.theme.status.All(status)).Space(); - - return builder.Append(helper::TextBuilder{} - .Append(step.keyword, options.theme.step.keyword) - .Append(FormatStepText(testStep, pickleStep, options.theme), options.theme.status.All(status)) - .Build(options.theme.status.All(status))) - .Build(); - } - - std::string FormatDocString(const cucumber::messages::pickle_doc_string& pickleDocString, const helper::Theme& theme) - { - helper::TextBuilder builder{}; - builder.Append(R"(""")", theme.docString.delimiter); - if (pickleDocString.media_type.has_value()) - builder.Append(pickleDocString.media_type.value(), theme.docString.mediaType); - builder.Line(); - - for (const auto& line : pickleDocString.content | std::views::split('\n') | std::views::transform(transformToString)) - builder.Append(line).Line(); - - builder.Append(R"(""")", theme.docString.delimiter); - - return builder.Build(theme.docString.all, true); - } - - std::vector CalcualteColumnWidths(const cucumber::messages::pickle_table& pickleDataTable) - { - std::vector columnWidths(pickleDataTable.rows.empty() ? 0 : pickleDataTable.rows.front().cells.size(), 0); - - for (const auto& row : pickleDataTable.rows) - for (std::size_t colIndex = 0; colIndex < row.cells.size(); ++colIndex) - { - const auto cellContentLength = row.cells[colIndex].value.length(); - columnWidths[colIndex] = std::max(columnWidths[colIndex], cellContentLength); - } - - return columnWidths; - } - - std::string FormatDataTable(const cucumber::messages::pickle_table& pickleDataTable, const helper::Theme& theme) - { - const auto columnWidths = CalcualteColumnWidths(pickleDataTable); - helper::TextBuilder builder{}; - - for (auto rowIndex = 0; rowIndex != pickleDataTable.rows.size(); ++rowIndex) - { - const auto& row = pickleDataTable.rows[rowIndex]; - - if (rowIndex > 0) - builder.Line(); - builder.Append("|", theme.dataTable.border); - - for (auto colIndex = 0; colIndex != pickleDataTable.rows[rowIndex].cells.size(); ++colIndex) - { - const auto& cell = row.cells[colIndex]; - builder.Append(fmt::format(" {:<{}} ", cell.value, columnWidths[colIndex]), theme.dataTable.content) - .Append("|", theme.dataTable.border); - } - } - - return builder.Build(theme.dataTable.all, true); - } - - std::string FormatPickleStepArgument(const cucumber::messages::pickle_step& pickleStep, const helper::Theme& theme) - { - if (pickleStep.argument && pickleStep.argument->doc_string.has_value()) - return FormatDocString(pickleStep.argument->doc_string.value(), theme); - - if (pickleStep.argument && pickleStep.argument->data_table.has_value()) - return FormatDataTable(pickleStep.argument->data_table.value(), theme); - - return ""; - } - - std::string FormatAmbiguousStep(const std::list& stepDefinitions, const helper::Theme& theme) - { - helper::TextBuilder builder{}; - builder.Append("Multiple matching step definitions found:"); - for (const auto* stepDefinition : stepDefinitions) - { - builder.Line().Append(" " + theme.symbol.bullet + " "); - - if (!stepDefinition->pattern.source.empty()) - builder.Append(stepDefinition->pattern.source); - - const auto location = FormatCodeLocation(stepDefinition, theme); - if (!location.empty()) - builder.Space().Append(location); - } - return builder.Build({}, true); - } - - std::string FormatTestStepResultError(const cucumber::messages::test_step_result& testStepResult, const helper::Theme& theme) - { - if (testStepResult.exception.has_value() && testStepResult.exception.value().stack_trace.has_value()) - { - return helper::TextBuilder{} - .Append(util::Trim(testStepResult.exception.value().stack_trace.value())) - .Build(theme.status.All(testStepResult.status), true); - } - - if (testStepResult.exception.has_value() && testStepResult.exception.value().message.has_value()) - { - return helper::TextBuilder{} - .Append(util::Trim(testStepResult.exception.value().message.value())) - .Build(theme.status.All(testStepResult.status), true); - } - - if (testStepResult.message.has_value()) - { - return helper::TextBuilder{} - .Append(util::Trim(testStepResult.message.value())) - .Build(theme.status.All(testStepResult.status), true); - } - - return ""; - } - - std::string FormatTestRunFinishedError(const cucumber::messages::test_run_finished& testRunFinished, const helper::Theme& theme) - { - if (testRunFinished.exception && testRunFinished.exception->stack_trace) - { - return helper::TextBuilder{} - .Append(util::Trim(testRunFinished.exception->stack_trace.value())) - .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); - } - - if (testRunFinished.exception && testRunFinished.exception->message) - { - return helper::TextBuilder{} - .Append(util::Trim(testRunFinished.exception->message.value())) - .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); - } - - return ""; - } - - std::string FormatBase64Attachment(const std::string& body, const std::string& mediaType, const std::optional& filename, const helper::Theme& theme) - { - helper::TextBuilder builder{}; - builder.Append("Embedding").Space(); - - if (filename) - builder.Append(filename.value()).Space(); - - builder - .Append("[") - .Append(mediaType) - .Space() - .Append(std::to_string(body.length() / 4 * 3)) - .Space() - .Append("bytes]"); - - return builder.Build(theme.attachment); - } - - std::string FormatTextAttachment(const std::string& body, const helper::Theme& theme) - { - return helper::TextBuilder{}.Append(body).Build(theme.attachment); - } - - std::string FormatAttachment(const cucumber::messages::attachment& attachment, const helper::Theme& theme) - { - if (attachment.content_encoding == cucumber::messages::attachment_content_encoding::BASE64) - return FormatBase64Attachment(attachment.body, attachment.media_type, attachment.file_name, theme); - else - return FormatTextAttachment(attachment.body, theme); - return ""; - } } PrettyFormatter::Options::Options(const nlohmann::json& formatOptions) @@ -379,7 +91,7 @@ namespace cucumber_cpp::library::formatter const auto& pickle = query.FindPickleBy(testCaseStarted); const auto& lineage = query.FindLineageByPickle(pickle); const auto& scenario = *lineage.scenario; - const auto scenarioLength = helper::Unstyled(FormatPickleTitle(pickle, scenario, options.theme)).length(); + const auto scenarioLength = helper::Unstyled(helper::FormatPickleTitle(pickle, scenario, options.theme)).length(); const auto& testCase = query.FindTestCaseBy(testCaseStarted); @@ -391,7 +103,7 @@ namespace cucumber_cpp::library::formatter { const auto* pickleStep = query.FindPickleStepBy(testStep); const auto& step = query.FindStepBy(*pickleStep); - return helper::Unstyled(FormatStepTitle(testStep, *pickleStep, step, cucumber::messages::test_step_result_status::UNKNOWN, options)).length(); + return helper::Unstyled(helper::FormatStepTitle(testStep, *pickleStep, step, cucumber::messages::test_step_result_status::UNKNOWN, options.useStatusIcon, options.theme)).length(); }; auto steplengths = testCase.test_steps | std::views::filter(hasPickleStepId) | std::views::transform(toLength); @@ -510,8 +222,8 @@ namespace cucumber_cpp::library::formatter void PrettyFormatter::PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength) { PrintGherkinLine( - FormatStepTitle(testStep, pickleStep, step, testStepFinished.test_step_result.status, options), - FormatCodeLocation(stepDefinition, options.theme), + helper::FormatStepTitle(testStep, pickleStep, step, testStepFinished.test_step_result.status, options.useStatusIcon, options.theme), + helper::FormatCodeLocation(stepDefinition, options.theme), scenarioIndent + 2, maxContentLength); } diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index f4f9f2c8..8a3a7b85 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -3,6 +3,8 @@ add_library(cucumber_cpp.library.formatter.helper ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.formatter.helper PRIVATE EventDataCollector.cpp EventDataCollector.hpp + FormatMessages.cpp + FormatMessages.hpp GetColorFunctions.hpp GetColorFunctions.cpp GherkinDocumentParser.cpp diff --git a/cucumber_cpp/library/formatter/helper/FormatMessages.cpp b/cucumber_cpp/library/formatter/helper/FormatMessages.cpp new file mode 100644 index 00000000..042c8c28 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/FormatMessages.cpp @@ -0,0 +1,324 @@ +#include "cucumber_cpp/library/formatter/helper/FormatMessages.hpp" +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/attachment_content_encoding.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_doc_string.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/pickle_table.hpp" +#include "cucumber/messages/rule.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/formatter/helper/TextBuilder.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include "cucumber_cpp/library/util/Trim.hpp" +#include "fmt/format.h" +#include "fmt/ranges.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + std::vector CalculateColumnWidths(const cucumber::messages::pickle_table& pickleDataTable) + { + std::vector columnWidths(pickleDataTable.rows.empty() ? 0 : pickleDataTable.rows.front().cells.size(), 0); + + for (const auto& row : pickleDataTable.rows) + for (std::size_t colIndex = 0; colIndex < row.cells.size(); ++colIndex) + { + const auto cellContentLength = row.cells[colIndex].value.length(); + columnWidths[colIndex] = std::max(columnWidths[colIndex], cellContentLength); + } + + return columnWidths; + } + + const auto transformToString = [](auto subrange) + { + return std::string_view{ subrange.begin(), subrange.end() }; + }; + } + + std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, const Theme& theme) + { + return TextBuilder{} + .Append(scenario.keyword + ":", theme.scenario.keyword) + .Space() + .Append(pickle.name, theme.scenario.name) + .Build(theme.scenario.all); + } + + std::string FormatPickleLocation(const cucumber::messages::pickle& pickle, const std::optional& location, const Theme& theme) + { + TextBuilder builder{}; + + builder.Append("#") + .Space() + .Append(pickle.uri); + if (location.has_value()) + builder.Append(":") + .Append(std::to_string(location.value().line)); + + return builder.Build(theme.location); + } + + std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const Theme& theme) + { + TextBuilder builder{}; + const auto& stepMatchArgumentsLists = testStep.step_match_arguments_lists; + + if (stepMatchArgumentsLists && stepMatchArgumentsLists->size() == 1) + { + const auto& stepMatchArguments = stepMatchArgumentsLists->front().step_match_arguments; + std::size_t currentIndex = 0; + + for (const auto& argument : stepMatchArguments) + { + const auto& group = argument.group; + + if (group.value.has_value() && group.start.has_value()) + { + const auto text = pickleStep.text.substr(currentIndex, group.start.value() - currentIndex); + currentIndex = group.start.value() + group.value->size(); + builder.Append(text, theme.step.text) + .Append(group.value.value(), theme.step.argument); + } + } + if (currentIndex != pickleStep.text.size()) + { + const auto remainingText = pickleStep.text.substr(currentIndex); + builder.Append(remainingText, theme.step.text); + } + } + else + builder.Append(pickleStep.text, theme.step.text); + + return builder.Build(); + } + + std::string FormatCodeLocation(const cucumber::messages::step_definition* stepDefinition, const Theme& theme) + { + if (stepDefinition != nullptr && stepDefinition->source_reference.uri.has_value()) + { + TextBuilder builder{}; + + builder.Append("#") + .Space() + .Append(stepDefinition->source_reference.uri.value()); + + if (stepDefinition->source_reference.location.has_value()) + builder.Append(":") + .Append(std::to_string(stepDefinition->source_reference.location.value().line)); + return builder.Build(theme.location); + } + + return ""; + } + + std::string FormatFeatureTitle(const cucumber::messages::feature& feature, const Theme& theme) + { + return TextBuilder{} + .Append(feature.keyword + ":", theme.feature.keyword) + .Space() + .Append(feature.name, theme.feature.name) + .Build(theme.feature.all); + } + + std::string FormatRuleTitle(const cucumber::messages::rule& rule, const Theme& theme) + { + return TextBuilder{} + .Append(rule.keyword + ":", theme.rule.keyword) + .Space() + .Append(rule.name, theme.rule.name) + .Build(theme.rule.all); + } + + std::string FormatPickleTags(const cucumber::messages::pickle& pickle, const Theme& theme) + { + if (!pickle.tags.empty()) + { + return TextBuilder{} + .Append(fmt::to_string(fmt::join(pickle.tags | std::views::transform([](const auto& tag) + { + return tag.name; + }), + " "))) + .Build(theme.tag); + } + return ""; + } + + std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, cucumber::messages::test_step_result_status status, bool useStatusIcon, const Theme& theme) + { + auto builder = TextBuilder{}; + if (useStatusIcon) + builder.Append(theme.status.Icon(status, " "), theme.status.All(status)).Space(); + + return builder.Append(TextBuilder{} + .Append(step.keyword, theme.step.keyword) + .Append(FormatStepText(testStep, pickleStep, theme), theme.status.All(status)) + .Build(theme.status.All(status))) + .Build(); + } + + std::string FormatDocString(const cucumber::messages::pickle_doc_string& pickleDocString, const Theme& theme) + { + TextBuilder builder{}; + builder.Append(R"(""")", theme.docString.delimiter); + if (pickleDocString.media_type.has_value()) + builder.Append(pickleDocString.media_type.value(), theme.docString.mediaType); + builder.Line(); + + for (const auto& line : pickleDocString.content | std::views::split('\n') | std::views::transform(transformToString)) + builder.Append(line).Line(); + + builder.Append(R"(""")", theme.docString.delimiter); + + return builder.Build(theme.docString.all, true); + } + + std::string FormatDataTable(const cucumber::messages::pickle_table& pickleDataTable, const Theme& theme) + { + const auto columnWidths = CalculateColumnWidths(pickleDataTable); + TextBuilder builder{}; + + for (auto rowIndex = 0; rowIndex != pickleDataTable.rows.size(); ++rowIndex) + { + const auto& row = pickleDataTable.rows[rowIndex]; + + if (rowIndex > 0) + builder.Line(); + builder.Append("|", theme.dataTable.border); + + for (auto colIndex = 0; colIndex != pickleDataTable.rows[rowIndex].cells.size(); ++colIndex) + { + const auto& cell = row.cells[colIndex]; + builder.Append(fmt::format(" {:<{}} ", cell.value, columnWidths[colIndex]), theme.dataTable.content) + .Append("|", theme.dataTable.border); + } + } + + return builder.Build(theme.dataTable.all, true); + } + + std::string FormatPickleStepArgument(const cucumber::messages::pickle_step& pickleStep, const Theme& theme) + { + if (pickleStep.argument && pickleStep.argument->doc_string.has_value()) + return FormatDocString(pickleStep.argument->doc_string.value(), theme); + + if (pickleStep.argument && pickleStep.argument->data_table.has_value()) + return FormatDataTable(pickleStep.argument->data_table.value(), theme); + + return ""; + } + + std::string FormatAmbiguousStep(const std::list& stepDefinitions, const Theme& theme) + { + TextBuilder builder{}; + builder.Append("Multiple matching step definitions found:"); + for (const auto* stepDefinition : stepDefinitions) + { + builder.Line().Append(" " + theme.symbol.bullet + " "); + + if (!stepDefinition->pattern.source.empty()) + builder.Append(stepDefinition->pattern.source); + + const auto location = FormatCodeLocation(stepDefinition, theme); + if (!location.empty()) + builder.Space().Append(location); + } + return builder.Build({}, true); + } + + std::string FormatTestStepResultError(const cucumber::messages::test_step_result& testStepResult, const Theme& theme) + { + if (testStepResult.exception.has_value() && testStepResult.exception.value().stack_trace.has_value()) + { + return TextBuilder{} + .Append(util::Trim(testStepResult.exception.value().stack_trace.value())) + .Build(theme.status.All(testStepResult.status), true); + } + + if (testStepResult.exception.has_value() && testStepResult.exception.value().message.has_value()) + { + return TextBuilder{} + .Append(util::Trim(testStepResult.exception.value().message.value())) + .Build(theme.status.All(testStepResult.status), true); + } + + if (testStepResult.message.has_value()) + { + return TextBuilder{} + .Append(util::Trim(testStepResult.message.value())) + .Build(theme.status.All(testStepResult.status), true); + } + + return ""; + } + + std::string FormatTestRunFinishedError(const cucumber::messages::test_run_finished& testRunFinished, const Theme& theme) + { + if (testRunFinished.exception && testRunFinished.exception->stack_trace) + { + return TextBuilder{} + .Append(util::Trim(testRunFinished.exception->stack_trace.value())) + .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); + } + + if (testRunFinished.exception && testRunFinished.exception->message) + { + return TextBuilder{} + .Append(util::Trim(testRunFinished.exception->message.value())) + .Build(theme.status.All(cucumber::messages::test_step_result_status::FAILED)); + } + + return ""; + } + + std::string FormatBase64Attachment(const std::string& body, const std::string& mediaType, const std::optional& filename, const Theme& theme) + { + TextBuilder builder{}; + builder.Append("Embedding").Space(); + + if (filename) + builder.Append(filename.value()).Space(); + + builder + .Append("[") + .Append(mediaType) + .Space() + .Append(std::to_string(body.length() / 4 * 3)) + .Space() + .Append("bytes]"); + + return builder.Build(theme.attachment); + } + + std::string FormatTextAttachment(const std::string& body, const Theme& theme) + { + return TextBuilder{}.Append(body).Build(theme.attachment); + } + + std::string FormatAttachment(const cucumber::messages::attachment& attachment, const Theme& theme) + { + if (attachment.content_encoding == cucumber::messages::attachment_content_encoding::BASE64) + return FormatBase64Attachment(attachment.body, attachment.media_type, attachment.file_name, theme); + else + return FormatTextAttachment(attachment.body, theme); + return ""; + } +} diff --git a/cucumber_cpp/library/formatter/helper/FormatMessages.hpp b/cucumber_cpp/library/formatter/helper/FormatMessages.hpp new file mode 100644 index 00000000..740d6534 --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/FormatMessages.hpp @@ -0,0 +1,45 @@ +#ifndef HELPER_FORMAT_MESSAGES_HPP +#define HELPER_FORMAT_MESSAGES_HPP + +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_doc_string.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/pickle_table.hpp" +#include "cucumber/messages/rule.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/test_run_finished.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_result.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, const Theme& theme); + std::string FormatPickleLocation(const cucumber::messages::pickle& pickle, const std::optional& location, const Theme& theme); + std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const Theme& theme); + std::string FormatCodeLocation(const cucumber::messages::step_definition* stepDefinition, const Theme& theme); + std::string FormatFeatureTitle(const cucumber::messages::feature& feature, const Theme& theme); + std::string FormatRuleTitle(const cucumber::messages::rule& rule, const Theme& theme); + std::string FormatPickleTags(const cucumber::messages::pickle& pickle, const Theme& theme); + std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, cucumber::messages::test_step_result_status status, bool useStatusIcon, const Theme& theme); + std::string FormatDocString(const cucumber::messages::pickle_doc_string& pickleDocString, const Theme& theme); + std::string FormatDataTable(const cucumber::messages::pickle_table& pickleDataTable, const Theme& theme); + std::string FormatPickleStepArgument(const cucumber::messages::pickle_step& pickleStep, const Theme& theme); + std::string FormatAmbiguousStep(const std::list& stepDefinitions, const Theme& theme); + std::string FormatTestStepResultError(const cucumber::messages::test_step_result& testStepResult, const Theme& theme); + std::string FormatTestRunFinishedError(const cucumber::messages::test_run_finished& testRunFinished, const Theme& theme); + std::string FormatBase64Attachment(const std::string& body, const std::string& mediaType, const std::optional& filename, const Theme& theme); + std::string FormatTextAttachment(const std::string& body, const Theme& theme); + std::string FormatAttachment(const cucumber::messages::attachment& attachment, const Theme& theme); +} + +#endif From 35a135130484497c5e2f9e83f1d32bdb46376f23 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 22:01:54 +0000 Subject: [PATCH 162/196] feat: refactor PrettyFormatter to utilize helper functions for message printing --- .../library/formatter/PrettyFormatter.cpp | 152 +++--------------- .../library/formatter/PrettyFormatter.hpp | 11 -- .../library/formatter/helper/CMakeLists.txt | 3 + .../formatter/helper/PrintMessages.cpp | 129 +++++++++++++++ .../formatter/helper/PrintMessages.hpp | 41 +++++ 5 files changed, 193 insertions(+), 143 deletions(-) create mode 100644 cucumber_cpp/library/formatter/helper/PrintMessages.cpp create mode 100644 cucumber_cpp/library/formatter/helper/PrintMessages.hpp diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index a2a1e4e3..53f0c9b8 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -1,24 +1,15 @@ #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/feature.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/rule.hpp" -#include "cucumber/messages/scenario.hpp" -#include "cucumber/messages/step.hpp" -#include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/test_case_started.hpp" #include "cucumber/messages/test_run_finished.hpp" #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" -#include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/FormatMessages.hpp" +#include "cucumber_cpp/library/formatter/helper/PrintMessages.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" -#include "fmt/format.h" #include "fmt/ostream.h" -#include "fmt/ranges.h" #include "nlohmann/json_fwd.hpp" #include #include @@ -33,26 +24,6 @@ namespace cucumber_cpp::library::formatter { - namespace - { - constexpr auto gherkinIndentLength = 2; - constexpr auto stepArgumentIndentLength = 2; - constexpr auto attachmentIndentLength = 4; - constexpr auto errorIndentLength = 4; - - const auto transformToString = [](auto subrange) - { - return std::string_view{ subrange.begin(), subrange.end() }; - }; - - void PrintlnIndentedContent(std::ostream& os, std::string_view content, std::size_t indent) - { - const std::string indentStr(indent, ' '); - fmt::println(os, "{}{}", indentStr, fmt::join(content | std::views::split('\n') | std::views::transform(transformToString), "\n" + indentStr)); - } - - } - PrettyFormatter::Options::Options(const nlohmann::json& formatOptions) : includeAttachments{ formatOptions.value("include_attachments", true) } , includeFeatureLine{ formatOptions.value("include_feature_line", true) } @@ -116,9 +87,9 @@ namespace cucumber_cpp::library::formatter std::size_t scenarioIndent{ 0 }; if (options.includeFeatureLine) { - scenarioIndent += gherkinIndentLength; + scenarioIndent += helper::gherkinIndentLength; if (options.includeRuleLine && lineage.rule) - scenarioIndent += gherkinIndentLength; + scenarioIndent += helper::gherkinIndentLength; } scenarioIndentByTestCaseStartedId[testCaseStarted.id] = scenarioIndent; @@ -135,12 +106,19 @@ namespace cucumber_cpp::library::formatter const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(testCaseStarted.id); const auto maxContentLength = maxContentLengthByTestCaseStartedId.at(testCaseStarted.id); - PrintFeatureLine(*feature); - if (rule) - PrintRuleLine(*rule); + if (options.includeFeatureLine && !printedFeatureUris.contains(feature.get())) + helper::PrintFeatureLine(outputStream, *feature, options.theme); + + if (options.includeRuleLine && !printedRuleIds.contains(rule.get())) + helper::PrintRuleLine(outputStream, *rule, options.theme); + outputStream << "\n"; - PrintTags(pickle, scenarioIndent); - PrintScenarioLine(pickle, *scenario, scenarioIndent, maxContentLength); + + helper::PrintTags(outputStream, pickle, scenarioIndent, options.theme); + helper::PrintScenarioLine(outputStream, pickle, *scenario, scenarioIndent, maxContentLength, options.theme); + + printedFeatureUris.insert(feature.get()); + printedRuleIds.insert(rule.get()); } void PrettyFormatter::HandleAttachment(const cucumber::messages::attachment& attachment) @@ -148,11 +126,7 @@ namespace cucumber_cpp::library::formatter if (!options.includeAttachments) return; - const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(attachment.test_case_started_id.value()); - const auto content = FormatAttachment(attachment, options.theme); - const std::string indentStr(scenarioIndent + gherkinIndentLength + attachmentIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0), ' '); - - fmt::println(outputStream, "\n{}{}\n", indentStr, content); + helper::PrintAttachment(outputStream, attachment, scenarioIndentByTestCaseStartedId.at(attachment.test_case_started_id.value()), options.useStatusIcon, options.theme); } void PrettyFormatter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) @@ -168,11 +142,11 @@ namespace cucumber_cpp::library::formatter const auto& step = query.FindStepBy(*pickleStep); const auto* stepDefinition = (testStep.step_definition_ids && !testStep.step_definition_ids->empty()) ? &query.FindStepDefinitionById(testStep.step_definition_ids->front()) : nullptr; - PrintStepLine(testStepFinished, testStep, *pickleStep, step, stepDefinition, scenarioIndent, maxContentLength); - PrintStepArgument(*pickleStep, scenarioIndent, options.theme); - PrintAmbiguousStep(testStepFinished, testStep, scenarioIndent); + helper::PrintStepLine(outputStream, testStepFinished, testStep, *pickleStep, step, stepDefinition, scenarioIndent, maxContentLength, options.useStatusIcon, options.theme); + helper::PrintStepArgument(outputStream, *pickleStep, scenarioIndent, options.useStatusIcon, options.theme); + helper::PrintAmbiguousStep(outputStream, query, testStepFinished, testStep, scenarioIndent, options.useStatusIcon, options.theme); } - PrintError(testStepFinished, scenarioIndent); + helper::PrintError(outputStream, testStepFinished, scenarioIndent, options.useStatusIcon, options.theme); } void PrettyFormatter::HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished) @@ -183,90 +157,4 @@ namespace cucumber_cpp::library::formatter fmt::println(outputStream, "{}", content); } - void PrettyFormatter::PrintFeatureLine(const cucumber::messages::feature& feature) - { - if (options.includeFeatureLine == false || printedFeatureUris.contains(&feature)) - return; - - fmt::println(outputStream, "\n{}", FormatFeatureTitle(feature, options.theme)); - printedFeatureUris.insert(&feature); - } - - void PrettyFormatter::PrintRuleLine(const cucumber::messages::rule& rule) - { - if (options.includeRuleLine == false || printedRuleIds.contains(&rule)) - return; - - fmt::println(outputStream, "\n{}{}", std::string(gherkinIndentLength, ' '), FormatRuleTitle(rule, options.theme)); - - printedRuleIds.insert(&rule); - } - - void PrettyFormatter::PrintTags(const cucumber::messages::pickle& pickle, std::size_t scenarioIndent) - { - if (pickle.tags.empty()) - return; - - fmt::println(outputStream, "{}{}", std::string(scenarioIndent, ' '), FormatPickleTags(pickle, options.theme)); - } - - void PrettyFormatter::PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength) - { - PrintGherkinLine( - FormatPickleTitle(pickle, scenario, options.theme), - FormatPickleLocation(pickle, scenario.location, options.theme), - scenarioIndent, - maxContentLength); - } - - void PrettyFormatter::PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength) - { - PrintGherkinLine( - helper::FormatStepTitle(testStep, pickleStep, step, testStepFinished.test_step_result.status, options.useStatusIcon, options.theme), - helper::FormatCodeLocation(stepDefinition, options.theme), - scenarioIndent + 2, maxContentLength); - } - - void PrettyFormatter::PrintStepArgument(const cucumber::messages::pickle_step& pickleStep, std::size_t scenarioIndent, const helper::Theme& theme) - { - const auto content = FormatPickleStepArgument(pickleStep, options.theme); - if (content.empty()) - return; - - PrintlnIndentedContent(outputStream, content, scenarioIndent + gherkinIndentLength + stepArgumentIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0)); - } - - void PrettyFormatter::PrintAmbiguousStep(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent) - { - if (testStepFinished.test_step_result.status != cucumber::messages::test_step_result_status::AMBIGUOUS) - return; - - const auto list = query.FindStepDefinitionsById(testStep); - const auto content = FormatAmbiguousStep(list, options.theme); - - if (content.empty()) - return; - - PrintlnIndentedContent(outputStream, content, scenarioIndent + gherkinIndentLength + errorIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0)); - } - - void PrettyFormatter::PrintError(const cucumber::messages::test_step_finished& testStepFinished, std::size_t scenarioIndent) - { - const auto content = FormatTestStepResultError(testStepFinished.test_step_result, options.theme); - if (content.empty()) - return; - - PrintlnIndentedContent(outputStream, content, scenarioIndent + gherkinIndentLength + errorIndentLength + (options.useStatusIcon ? gherkinIndentLength : 0)); - } - - void PrettyFormatter::PrintGherkinLine(const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength) - { - const auto unstyledLength = helper::Unstyled(title).length(); - const auto padding = location.has_value() ? (maxContentLength - std::min(unstyledLength, maxContentLength)) + 1 : 0; - - const std::string paddingStr(padding, ' '); - const auto content = fmt::format("{}{}{}", title, paddingStr, location.value_or("")); - - PrintlnIndentedContent(outputStream, content, indent); - } } diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.hpp b/cucumber_cpp/library/formatter/PrettyFormatter.hpp index 74031f55..7dfda3f3 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.hpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.hpp @@ -54,17 +54,6 @@ namespace cucumber_cpp::library::formatter void HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished); void HandleTestRunFinished(const cucumber::messages::test_run_finished& testRunFinished); - void PrintFeatureLine(const cucumber::messages::feature& feature); - void PrintRuleLine(const cucumber::messages::rule& rule); - void PrintTags(const cucumber::messages::pickle& pickle, std::size_t scenarioIndent); - void PrintScenarioLine(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength); - void PrintStepLine(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength); - void PrintStepArgument(const cucumber::messages::pickle_step& pickleStep, std::size_t scenarioIndent, const helper::Theme& theme); - void PrintAmbiguousStep(const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent); - void PrintError(const cucumber::messages::test_step_finished& testStepFinished, std::size_t scenarioIndent); - - void PrintGherkinLine(const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength); - Options options{ formatOptions.contains(name) ? formatOptions.at(name) : nlohmann::json::object() }; std::map testCaseStartedIdToScenarioMap; diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index 8a3a7b85..743bb681 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -19,6 +19,8 @@ target_sources(cucumber_cpp.library.formatter.helper PRIVATE LocationHelpers.hpp PickleParser.cpp PickleParser.hpp + PrintMessages.cpp + PrintMessages.hpp SummaryHelpers.hpp SummaryHelpers.cpp TestCaseAttemptFormatter.cpp @@ -38,6 +40,7 @@ target_include_directories(cucumber_cpp.library.formatter.helper PUBLIC target_link_libraries(cucumber_cpp.library.formatter.helper PUBLIC cucumber_cpp.library.cucumber_expression cucumber_cpp.library.support + cucumber_cpp.library.query fmt-header-only ) diff --git a/cucumber_cpp/library/formatter/helper/PrintMessages.cpp b/cucumber_cpp/library/formatter/helper/PrintMessages.cpp new file mode 100644 index 00000000..aa8bd24b --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/PrintMessages.cpp @@ -0,0 +1,129 @@ +#include "cucumber_cpp/library/formatter/helper/PrintMessages.hpp" +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/rule.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/formatter/helper/FormatMessages.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include "cucumber_cpp/library/query/Query.hpp" +#include "fmt/format.h" +#include "fmt/ostream.h" +#include "fmt/ranges.h" +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + namespace + { + const auto transformToString = [](auto subrange) + { + return std::string_view{ subrange.begin(), subrange.end() }; + }; + + void PrintlnIndentedContent(std::ostream& os, std::string_view content, std::size_t indent) + { + const std::string indentStr(indent, ' '); + fmt::println(os, "{}{}", indentStr, fmt::join(content | std::views::split('\n') | std::views::transform(transformToString), "\n" + indentStr)); + } + } + + void PrintFeatureLine(std::ostream& stream, const cucumber::messages::feature& feature, const Theme& theme) + { + fmt::println(stream, "\n{}", FormatFeatureTitle(feature, theme)); + } + + void PrintRuleLine(std::ostream& stream, const cucumber::messages::rule& rule, const Theme& theme) + { + fmt::println(stream, "\n{}{}", std::string(gherkinIndentLength, ' '), FormatRuleTitle(rule, theme)); + } + + void PrintTags(std::ostream& stream, const cucumber::messages::pickle& pickle, std::size_t scenarioIndent, const Theme& theme) + { + if (pickle.tags.empty()) + return; + + fmt::println(stream, "{}{}", std::string(scenarioIndent, ' '), FormatPickleTags(pickle, theme)); + } + + void PrintScenarioLine(std::ostream& stream, const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength, const Theme& theme) + { + PrintGherkinLine(stream, + FormatPickleTitle(pickle, scenario, theme), + FormatPickleLocation(pickle, scenario.location, theme), + scenarioIndent, + maxContentLength, theme); + } + + void PrintStepLine(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength, bool useStatusIcon, const Theme& theme) + { + PrintGherkinLine(stream, + helper::FormatStepTitle(testStep, pickleStep, step, testStepFinished.test_step_result.status, useStatusIcon, theme), + helper::FormatCodeLocation(stepDefinition, theme), + scenarioIndent + 2, maxContentLength, theme); + } + + void PrintStepArgument(std::ostream& stream, const cucumber::messages::pickle_step& pickleStep, std::size_t scenarioIndent, bool useStatusIcon, const helper::Theme& theme) + { + const auto content = FormatPickleStepArgument(pickleStep, theme); + if (content.empty()) + return; + + PrintlnIndentedContent(stream, content, scenarioIndent + gherkinIndentLength + stepArgumentIndentLength + (useStatusIcon ? gherkinIndentLength : 0)); + } + + void PrintAmbiguousStep(std::ostream& stream, query::Query& query, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme) + { + if (testStepFinished.test_step_result.status != cucumber::messages::test_step_result_status::AMBIGUOUS) + return; + + const auto list = query.FindStepDefinitionsById(testStep); + const auto content = FormatAmbiguousStep(list, theme); + + if (content.empty()) + return; + + PrintlnIndentedContent(stream, content, scenarioIndent + gherkinIndentLength + errorIndentLength + (useStatusIcon ? gherkinIndentLength : 0)); + } + + void PrintError(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme) + { + const auto content = FormatTestStepResultError(testStepFinished.test_step_result, theme); + if (content.empty()) + return; + + PrintlnIndentedContent(stream, content, scenarioIndent + gherkinIndentLength + errorIndentLength + (useStatusIcon ? gherkinIndentLength : 0)); + } + + void PrintGherkinLine(std::ostream& stream, const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength, const Theme& theme) + { + const auto unstyledLength = helper::Unstyled(title).length(); + const auto padding = location.has_value() ? (maxContentLength - std::min(unstyledLength, maxContentLength)) + 1 : 0; + + const std::string paddingStr(padding, ' '); + const auto content = fmt::format("{}{}{}", title, paddingStr, location.value_or("")); + + PrintlnIndentedContent(stream, content, indent); + } + + void PrintAttachment(std::ostream& stream, const cucumber::messages::attachment& attachment, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme) + { + const auto content = FormatAttachment(attachment, theme); + const std::string indentStr(scenarioIndent + helper::gherkinIndentLength + helper::attachmentIndentLength + (useStatusIcon ? helper::gherkinIndentLength : 0), ' '); + + fmt::println(stream, "\n{}{}\n", indentStr, content); + } +} diff --git a/cucumber_cpp/library/formatter/helper/PrintMessages.hpp b/cucumber_cpp/library/formatter/helper/PrintMessages.hpp new file mode 100644 index 00000000..5c17b71d --- /dev/null +++ b/cucumber_cpp/library/formatter/helper/PrintMessages.hpp @@ -0,0 +1,41 @@ +#ifndef HELPER_PRINT_MESSAGES_HPP +#define HELPER_PRINT_MESSAGES_HPP + +#include "cucumber/messages/attachment.hpp" +#include "cucumber/messages/feature.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/pickle_step.hpp" +#include "cucumber/messages/rule.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/step.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include "cucumber_cpp/library/query/Query.hpp" +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter::helper +{ + + static constexpr auto gherkinIndentLength = 2; + static constexpr auto stepArgumentIndentLength = 2; + static constexpr auto attachmentIndentLength = 4; + static constexpr auto errorIndentLength = 4; + + void PrintFeatureLine(std::ostream& stream, const cucumber::messages::feature& feature, const Theme& theme); + void PrintRuleLine(std::ostream& stream, const cucumber::messages::rule& rule, const Theme& theme); + void PrintTags(std::ostream& stream, const cucumber::messages::pickle& pickle, std::size_t scenarioIndent, const Theme& theme); + void PrintScenarioLine(std::ostream& stream, const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength, const Theme& theme); + void PrintStepLine(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength, bool useStatusIcon, const Theme& theme); + void PrintStepArgument(std::ostream& stream, const cucumber::messages::pickle_step& pickleStep, std::size_t scenarioIndent, bool useStatusIcon, const helper::Theme& theme); + void PrintAmbiguousStep(std::ostream& stream, query::Query& query, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme); + void PrintError(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme); + void PrintGherkinLine(std::ostream& stream, const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength, const Theme& theme); + void PrintAttachment(std::ostream& stream, const cucumber::messages::attachment& attachment, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme); +} + +#endif From dc636b817a9dc9a38634c2e5418551cb89379b19 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 22:18:42 +0000 Subject: [PATCH 163/196] feat: update PrettyFormatter to conditionally print feature and rule lines based on rule presence --- cucumber_cpp/library/formatter/PrettyFormatter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index 53f0c9b8..1d3ad52a 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -106,10 +106,10 @@ namespace cucumber_cpp::library::formatter const auto scenarioIndent = scenarioIndentByTestCaseStartedId.at(testCaseStarted.id); const auto maxContentLength = maxContentLengthByTestCaseStartedId.at(testCaseStarted.id); - if (options.includeFeatureLine && !printedFeatureUris.contains(feature.get())) + if (options.includeFeatureLine && rule && !printedFeatureUris.contains(feature.get())) helper::PrintFeatureLine(outputStream, *feature, options.theme); - if (options.includeRuleLine && !printedRuleIds.contains(rule.get())) + if (options.includeRuleLine && rule && !printedRuleIds.contains(rule.get())) helper::PrintRuleLine(outputStream, *rule, options.theme); outputStream << "\n"; From b4ebaaec87f1575c4c6b57e2cfe12304083f196c Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 22:36:21 +0000 Subject: [PATCH 164/196] feat: update launch configuration to include summary and retry options for tag execution --- .vscode/launch.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.vscode/launch.json b/.vscode/launch.json index 5da11011..66890a0b 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -46,6 +46,9 @@ "args": [ "--format", "pretty", + "summary", + "--retry", + "1", "--tags", "${input:tag}", "--", From e9f4e08876c684762cacea49d3de37e2b8c49dfe Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Wed, 21 Jan 2026 22:37:05 +0000 Subject: [PATCH 165/196] feat: update SummaryFormatter to include test_case_started message and simplify OnEnvelope logic --- .../library/formatter/SummaryFormatter.cpp | 32 ++++++++++++------- .../library/formatter/SummaryFormatter.hpp | 3 -- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index 8d2094cc..ef9558ea 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -1,6 +1,7 @@ #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber/messages/duration.hpp" #include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/test_case_started.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" @@ -10,7 +11,9 @@ #include "fmt/ostream.h" #include #include +#include #include +#include #include namespace cucumber_cpp::library::formatter @@ -33,18 +36,8 @@ namespace cucumber_cpp::library::formatter void SummaryFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) { - if (envelope.test_run_started) - { - testRunStartedAt = envelope.test_run_started->timestamp; - } - if (envelope.test_run_finished) - { - const auto testRunFinishedAt = envelope.test_run_finished->timestamp; - const auto duration = testRunFinishedAt - testRunStartedAt; - - LogSummary(duration); - } + LogSummary(query.FindTestRunDuration()); } void SummaryFormatter::LogSummary(const cucumber::messages::duration& testRunDuration) @@ -52,6 +45,23 @@ namespace cucumber_cpp::library::formatter std::list failures{}; std::list warnings{}; + // WIP + // auto testCases = query.TestCaseStarted(); + // std::map failedTestStepResults{}; + // std::map warningTestStepResults{}; + + // for (const auto& [id, testCaseStarted] : testCases) + // { + // const auto& testCaseFinished = query.TestCaseFinishedByTestCaseStartedId().at(testCaseStarted.id); + // const auto* testStepResult = query.FindMostSevereTestStepResultBy(testCaseStarted).value_or(nullptr); + + // if (testStepResult != nullptr && IsFailure(testStepResult->status, testCaseFinished.will_be_retried)) + // failedTestStepResults[id] = &testCaseStarted; + + // if (testStepResult != nullptr && IsWarning(testStepResult->status, testCaseFinished.will_be_retried)) + // warningTestStepResults[id] = &testCaseStarted; + // } + const auto attempts = eventDataCollector.GetTestCaseAttempts(); for (const auto& attempt : attempts) { diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.hpp b/cucumber_cpp/library/formatter/SummaryFormatter.hpp index 795f8a74..53086022 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.hpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.hpp @@ -3,7 +3,6 @@ #include "cucumber/messages/duration.hpp" #include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/timestamp.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include @@ -22,8 +21,6 @@ namespace cucumber_cpp::library::formatter void OnEnvelope(const cucumber::messages::envelope& envelope) override; void LogSummary(const cucumber::messages::duration& testRunDuration); void LogIssues(const std::list& attempts, std::string_view title); - - cucumber::messages::timestamp testRunStartedAt{}; }; } From 80c7af766957d17db5a0298b59543deaccf2ce1e Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 22 Jan 2026 14:41:28 +0000 Subject: [PATCH 166/196] feat: enhance formatters to support hook messages and improve output styling --- cucumber_cpp/acceptance_test/hooks/Hooks.cpp | 2 +- .../library/formatter/PrettyFormatter.cpp | 6 - .../library/formatter/SummaryFormatter.cpp | 211 ++++++++++++++---- .../library/formatter/SummaryFormatter.hpp | 7 +- .../formatter/helper/FormatMessages.cpp | 58 ++++- .../formatter/helper/FormatMessages.hpp | 6 + .../formatter/helper/PrintMessages.cpp | 20 +- .../formatter/helper/PrintMessages.hpp | 5 +- .../library/formatter/helper/Theme.cpp | 1 + .../library/formatter/helper/Theme.hpp | 1 + cucumber_cpp/library/query/Query.cpp | 5 + cucumber_cpp/library/query/Query.hpp | 2 + cucumber_cpp/library/util/ToLower.hpp | 20 ++ 13 files changed, 285 insertions(+), 59 deletions(-) create mode 100644 cucumber_cpp/library/util/ToLower.hpp diff --git a/cucumber_cpp/acceptance_test/hooks/Hooks.cpp b/cucumber_cpp/acceptance_test/hooks/Hooks.cpp index 3d4dc920..de7fc35e 100644 --- a/cucumber_cpp/acceptance_test/hooks/Hooks.cpp +++ b/cucumber_cpp/acceptance_test/hooks/Hooks.cpp @@ -52,7 +52,7 @@ HOOK_BEFORE_SCENARIO("@throw_scenariohook") throw std::string{ "error" }; } -HOOK_BEFORE_SCENARIO() +HOOK_BEFORE_SCENARIO(.name = "fail if --failprogramhook is set") { if (context.Contains("--failprogramhook") && context.Get("--failprogramhook")) std::cout << "should not be executed\n"; diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index 1d3ad52a..e8b090cd 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -42,19 +42,13 @@ namespace cucumber_cpp::library::formatter } if (envelope.attachment) - { HandleAttachment(envelope.attachment.value()); - } if (envelope.test_step_finished) - { HandleTestStepFinished(envelope.test_step_finished.value()); - } if (envelope.test_run_finished) - { HandleTestRunFinished(envelope.test_run_finished.value()); - } } void PrettyFormatter::CalculateIndent(const cucumber::messages::test_case_started& testCaseStarted) diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index ef9558ea..8c360f06 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -1,20 +1,31 @@ #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" #include "cucumber/messages/duration.hpp" #include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/pickle.hpp" +#include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/test_case.hpp" +#include "cucumber/messages/test_case_finished.hpp" #include "cucumber/messages/test_case_started.hpp" +#include "cucumber/messages/test_step.hpp" +#include "cucumber/messages/test_step_finished.hpp" #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber_cpp/library/formatter/helper/IssueHelpers.hpp" -#include "cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp" -#include "cucumber_cpp/library/util/Timestamp.hpp" +#include "cucumber_cpp/library/formatter/helper/FormatMessages.hpp" +#include "cucumber_cpp/library/formatter/helper/PrintMessages.hpp" +#include "cucumber_cpp/library/formatter/helper/TextBuilder.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include "cucumber_cpp/library/query/Query.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" +#include "cucumber_cpp/library/util/ToLower.hpp" #include "fmt/ostream.h" +#include "fmt/ranges.h" +#include #include -#include #include -#include +#include +#include +#include #include -#include namespace cucumber_cpp::library::formatter { @@ -32,6 +43,123 @@ namespace cucumber_cpp::library::formatter return status == cucumber::messages::test_step_result_status::PENDING || (status == cucumber::messages::test_step_result_status::FAILED && willBeRetried); } + + std::size_t CalculateLength(const query::Query& query, const cucumber::messages::pickle& pickle, const cucumber::messages::test_case_started& testCaseStarted, const cucumber::messages::test_case_finished& testCaseFinished, const cucumber::messages::scenario& scenario, const cucumber::messages::test_case& testCase, bool useStatusIcon, const helper::Theme& theme) + { + const auto scenarioLength = helper::Unstyled(helper::FormatPickleAttemptTitle(pickle, testCaseStarted.attempt, testCaseFinished.will_be_retried, scenario, theme)).length(); + + const auto toLength = [&query, useStatusIcon, &theme, isBeforeHook = true](const cucumber::messages::test_step& testStep) mutable -> std::size_t + { + if (testStep.hook_id.has_value()) + { + const auto& hook = query.FindHookById(testStep.hook_id.value()); + return helper::Unstyled(helper::FormatHookTitle(hook, cucumber::messages::test_step_result_status::UNKNOWN, isBeforeHook, useStatusIcon, theme)).length(); + } + else if (testStep.hook_id.has_value()) + { + isBeforeHook = false; + + const auto* pickleStep = query.FindPickleStepBy(testStep); + const auto& step = query.FindStepBy(*pickleStep); + return helper::Unstyled(helper::FormatStepTitle(testStep, *pickleStep, step, cucumber::messages::test_step_result_status::UNKNOWN, useStatusIcon, theme)).length(); + } + return 0; + }; + + auto steplengths = testCase.test_steps | std::views::transform(toLength); + + const auto maxStepLengthIter = std::ranges::max_element(steplengths); + const auto maxStepLength = (maxStepLengthIter != steplengths.end()) ? *maxStepLengthIter : 0; + const auto maxContentLength = std::max(scenarioLength, useStatusIcon ? maxStepLength + 2 : maxStepLength); + + return maxContentLength; + } + + void HandleHookStep(std::ostream& stream, const query::Query& query, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent, std::size_t maxContentLength, bool isBeforeHook, bool useStatusIcon, const helper::Theme& theme) + { + const auto& hook = query.FindHookById(testStep.hook_id.value()); + helper::PrintHookLine(stream, testStepFinished, hook, scenarioIndent, maxContentLength, isBeforeHook, useStatusIcon, theme); + } + + void HandleTestStep(std::ostream& stream, const query::Query& query, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent, std::size_t maxContentLength, bool useStatusIcon, const helper::Theme& theme) + { + const auto* pickleStep = query.FindPickleStepBy(testStep); + const auto& step = query.FindStepBy(*pickleStep); + const auto* stepDefinition = (testStep.step_definition_ids && !testStep.step_definition_ids->empty()) ? &query.FindStepDefinitionById(testStep.step_definition_ids->front()) : nullptr; + + helper::PrintStepLine(stream, testStepFinished, testStep, *pickleStep, step, stepDefinition, scenarioIndent, maxContentLength, useStatusIcon, theme); + + helper::PrintStepArgument(stream, *pickleStep, scenarioIndent, useStatusIcon, theme); + helper::PrintAmbiguousStep(stream, query, testStepFinished, testStep, scenarioIndent, useStatusIcon, theme); + + helper::PrintError(stream, testStepFinished, scenarioIndent, useStatusIcon, theme); + } + + void HandleTestSteps(std::ostream& stream, const query::Query& query, const cucumber::messages::test_case_started& testCaseStarted, std::size_t scenarioIndent, std::size_t maxContentLength, bool useStatusIcon, const helper::Theme& theme) + { + const auto& testStepFinishedAndTestStep = query.FindTestStepFinishedAndTestStepBy(testCaseStarted); + + auto isBeforeHook = true; + for (const auto [testStepFinished, testStep] : testStepFinishedAndTestStep) + { + if (testStep->hook_id.has_value()) + HandleHookStep(stream, query, *testStepFinished, *testStep, scenarioIndent, maxContentLength, isBeforeHook, useStatusIcon, theme); + else + { + isBeforeHook = false; + HandleTestStep(stream, query, *testStepFinished, *testStep, scenarioIndent, maxContentLength, useStatusIcon, theme); + } + } + } + + void HandleTestCaseStarted(std::ostream& stream, const query::Query& query, const cucumber::messages::test_case_started& testCaseStarted, bool useStatusIcon, const helper::Theme& theme) + { + auto scenarioIndent = 0; + + const auto& testCaseFinished = query.TestCaseFinishedByTestCaseStartedId().at(testCaseStarted.id); + const auto& pickle = query.FindPickleBy(testCaseStarted); + const auto& lineage = query.FindLineageByPickle(pickle); + const auto& scenario = lineage.scenario; + const auto& rule = lineage.rule; + const auto& feature = lineage.feature; + const auto& testCase = query.FindTestCaseBy(testCaseStarted); + + const auto maxContentLength = CalculateLength(query, pickle, testCaseStarted, testCaseFinished, *scenario, testCase, useStatusIcon, theme); + + fmt::println(stream, ""); + helper::PrintScenarioAttemptLine(stream, pickle, testCaseStarted.attempt, testCaseFinished.will_be_retried, *scenario, scenarioIndent, maxContentLength, theme); + HandleTestSteps(stream, query, testCaseStarted, scenarioIndent, maxContentLength, useStatusIcon, theme); + } + + void HandleTestCaseStartedList(std::ostream& stream, const query::Query& query, const std::string& title, const std::map testCaseStartedList, bool useStatusIcon, const helper::Theme& theme) + { + if (testCaseStartedList.empty()) + return; + + fmt::println(stream, "\n{}:", title); + + for (const auto& [id, testCaseStarted] : testCaseStartedList) + HandleTestCaseStarted(stream, query, *testCaseStarted, useStatusIcon, theme); + } + + void HandleSummary(std::ostream& stream, const std::string& summary, const std::map& counts, const helper::Theme& theme) + { + const auto countToStatusString = [&theme](const auto& pair) + { + const auto& [status, count] = pair; + return helper::TextBuilder{} + .Append(std::to_string(count)) + .Space() + .Append(util::ToLower(std::string{ cucumber::messages::to_string(status) })) + .Build(theme.status.All(status)); + }; + + auto countsValues = counts | std::views::values; + const auto totalCount = std::accumulate(countsValues.begin(), countsValues.end(), std::size_t{ 0u }); + + fmt::println(stream, "{} {} {}", totalCount, summary, + fmt::join(counts | std::views::transform(countToStatusString), ", ")); + } } void SummaryFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) @@ -42,51 +170,46 @@ namespace cucumber_cpp::library::formatter void SummaryFormatter::LogSummary(const cucumber::messages::duration& testRunDuration) { - std::list failures{}; - std::list warnings{}; - - // WIP - // auto testCases = query.TestCaseStarted(); - // std::map failedTestStepResults{}; - // std::map warningTestStepResults{}; + std::map warningTestStepResults{}; + std::map failedTestStepResults{}; - // for (const auto& [id, testCaseStarted] : testCases) - // { - // const auto& testCaseFinished = query.TestCaseFinishedByTestCaseStartedId().at(testCaseStarted.id); - // const auto* testStepResult = query.FindMostSevereTestStepResultBy(testCaseStarted).value_or(nullptr); + std::map scenarioCounts; + std::map stepCounts; - // if (testStepResult != nullptr && IsFailure(testStepResult->status, testCaseFinished.will_be_retried)) - // failedTestStepResults[id] = &testCaseStarted; + cucumber::messages::duration totalStepDuration{}; - // if (testStepResult != nullptr && IsWarning(testStepResult->status, testCaseFinished.will_be_retried)) - // warningTestStepResults[id] = &testCaseStarted; - // } - - const auto attempts = eventDataCollector.GetTestCaseAttempts(); - for (const auto& attempt : attempts) + for (const auto& [id, testCaseStarted] : query.TestCaseStarted()) { - if (IsFailure(attempt.worstTestStepResult.status, attempt.willBeRetried)) - failures.emplace_back(attempt); + const auto& testCaseFinished = query.TestCaseFinishedByTestCaseStartedId().at(testCaseStarted.id); + const auto* testStepResult = query.FindMostSevereTestStepResultBy(testCaseStarted).value_or(nullptr); - if (IsWarning(attempt.worstTestStepResult.status, attempt.willBeRetried)) - warnings.emplace_back(attempt); - } + if (testStepResult != nullptr && IsWarning(testStepResult->status, testCaseFinished.will_be_retried)) + warningTestStepResults[id] = &testCaseStarted; - LogIssues(failures, "Failures"); - LogIssues(warnings, "Warnings"); + if (testStepResult != nullptr && IsFailure(testStepResult->status, testCaseFinished.will_be_retried)) + failedTestStepResults[id] = &testCaseStarted; - outputStream << helper::FormatSummary(attempts, testRunDuration); - } + if (!testCaseFinished.will_be_retried) + { + ++scenarioCounts[query.FindMostSevereTestStepResultBy(testCaseFinished).value()->status]; - void SummaryFormatter::LogIssues(const std::list& attempts, std::string_view title) - { - if (!attempts.empty()) - { - fmt::print(outputStream, "{}:\n\n", title); - - auto nr = 1; - for (const auto& issue : attempts) - helper::FormatIssue(outputStream, nr++, issue, supportCodeLibrary) << "\n"; + const auto& testStepFinishedAndTestStep = query.FindTestStepFinishedAndTestStepBy(testCaseStarted); + for (const auto& [testStepFinished, testStep] : testStepFinishedAndTestStep) + { + if (testStep->pickle_step_id.has_value()) + { + ++stepCounts[testStepFinished->test_step_result.status]; + totalStepDuration += testStepFinished->test_step_result.duration; + } + } + } } + + HandleTestCaseStartedList(outputStream, query, "Warnings", warningTestStepResults, useStatusIcon, theme); + HandleTestCaseStartedList(outputStream, query, "Failures", failedTestStepResults, useStatusIcon, theme); + HandleSummary(outputStream, "scenarios", scenarioCounts, theme); + HandleSummary(outputStream, "steps", stepCounts, theme); + + fmt::println(outputStream, "{:%Mm %S}s (executing steps: {:%Mm %S}s)", util::DurationToMilliseconds(testRunDuration), util::DurationToMilliseconds(totalStepDuration)); } } diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.hpp b/cucumber_cpp/library/formatter/SummaryFormatter.hpp index 53086022..bb38b4b3 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.hpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.hpp @@ -5,6 +5,8 @@ #include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include #include #include @@ -20,7 +22,10 @@ namespace cucumber_cpp::library::formatter private: void OnEnvelope(const cucumber::messages::envelope& envelope) override; void LogSummary(const cucumber::messages::duration& testRunDuration); - void LogIssues(const std::list& attempts, std::string_view title); + + const helper::Theme theme = helper::CreateCucumberTheme(); + const bool useStatusIcon = true; + const std::size_t scenarioIndent = 0; }; } diff --git a/cucumber_cpp/library/formatter/helper/FormatMessages.cpp b/cucumber_cpp/library/formatter/helper/FormatMessages.cpp index 042c8c28..d5a797c5 100644 --- a/cucumber_cpp/library/formatter/helper/FormatMessages.cpp +++ b/cucumber_cpp/library/formatter/helper/FormatMessages.cpp @@ -2,6 +2,7 @@ #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/attachment_content_encoding.hpp" #include "cucumber/messages/feature.hpp" +#include "cucumber/messages/hook.hpp" #include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_doc_string.hpp" @@ -9,6 +10,7 @@ #include "cucumber/messages/pickle_table.hpp" #include "cucumber/messages/rule.hpp" #include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/step.hpp" #include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/test_run_finished.hpp" @@ -18,6 +20,7 @@ #include "cucumber_cpp/library/formatter/helper/TextBuilder.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" #include "cucumber_cpp/library/util/Trim.hpp" +#include "fmt/color.h" #include "fmt/format.h" #include "fmt/ranges.h" #include @@ -51,6 +54,13 @@ namespace cucumber_cpp::library::formatter::helper { return std::string_view{ subrange.begin(), subrange.end() }; }; + + std::string GetAttemptText(std::size_t attempt, bool willBeRetried) + { + if (attempt > 0 || willBeRetried) + return fmt::format("(attempt {}{})", attempt + 1, willBeRetried ? ", retried" : ""); + return ""; + } } std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, const Theme& theme) @@ -62,6 +72,21 @@ namespace cucumber_cpp::library::formatter::helper .Build(theme.scenario.all); } + std::string FormatPickleAttemptTitle(const cucumber::messages::pickle& pickle, std::size_t attempt, bool retry, const cucumber::messages::scenario& scenario, const Theme& theme) + { + auto attemptText = GetAttemptText(attempt, retry); + + TextBuilder builder{}; + builder.Append(scenario.keyword + ":", theme.scenario.keyword) + .Space() + .Append(pickle.name, theme.scenario.name); + + if (!attemptText.empty()) + builder.Space().Append(attemptText, theme.scenario.attempt); + + return builder.Build(theme.scenario.all); + } + std::string FormatPickleLocation(const cucumber::messages::pickle& pickle, const std::optional& location, const Theme& theme) { TextBuilder builder{}; @@ -110,25 +135,33 @@ namespace cucumber_cpp::library::formatter::helper return builder.Build(); } - std::string FormatCodeLocation(const cucumber::messages::step_definition* stepDefinition, const Theme& theme) + std::string FormatCodeLocation(const cucumber::messages::source_reference& sourceReference, const Theme& theme) { - if (stepDefinition != nullptr && stepDefinition->source_reference.uri.has_value()) + if (sourceReference.uri.has_value()) { TextBuilder builder{}; builder.Append("#") .Space() - .Append(stepDefinition->source_reference.uri.value()); + .Append(sourceReference.uri.value()); - if (stepDefinition->source_reference.location.has_value()) + if (sourceReference.location.has_value()) builder.Append(":") - .Append(std::to_string(stepDefinition->source_reference.location.value().line)); + .Append(std::to_string(sourceReference.location.value().line)); return builder.Build(theme.location); } return ""; } + std::string FormatCodeLocation(const cucumber::messages::step_definition* stepDefinition, const Theme& theme) + { + if (stepDefinition != nullptr) + return FormatCodeLocation(stepDefinition->source_reference, theme); + + return ""; + } + std::string FormatFeatureTitle(const cucumber::messages::feature& feature, const Theme& theme) { return TextBuilder{} @@ -162,6 +195,21 @@ namespace cucumber_cpp::library::formatter::helper return ""; } + std::string FormatHookTitle(const cucumber::messages::hook& hook, cucumber::messages::test_step_result_status status, bool isBeforeHook, bool useStatusIcon, const Theme& theme) + { + TextBuilder builder{}; + + if (useStatusIcon) + builder.Append(theme.status.Icon(status, " "), theme.status.All(status)).Space(); + + builder.Append((isBeforeHook ? "Before" : "After"), theme.step.keyword.value_or(fmt::text_style{}) | theme.status.All(status)); + + if (hook.name.has_value()) + builder.Space().Append('(' + hook.name.value() + ')', theme.status.All(status)); + + return builder.Build(theme.status.All(status)); + } + std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, cucumber::messages::test_step_result_status status, bool useStatusIcon, const Theme& theme) { auto builder = TextBuilder{}; diff --git a/cucumber_cpp/library/formatter/helper/FormatMessages.hpp b/cucumber_cpp/library/formatter/helper/FormatMessages.hpp index 740d6534..641ba347 100644 --- a/cucumber_cpp/library/formatter/helper/FormatMessages.hpp +++ b/cucumber_cpp/library/formatter/helper/FormatMessages.hpp @@ -3,6 +3,7 @@ #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/feature.hpp" +#include "cucumber/messages/hook.hpp" #include "cucumber/messages/location.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_doc_string.hpp" @@ -10,6 +11,7 @@ #include "cucumber/messages/pickle_table.hpp" #include "cucumber/messages/rule.hpp" #include "cucumber/messages/scenario.hpp" +#include "cucumber/messages/source_reference.hpp" #include "cucumber/messages/step.hpp" #include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/test_run_finished.hpp" @@ -17,6 +19,7 @@ #include "cucumber/messages/test_step_result.hpp" #include "cucumber/messages/test_step_result_status.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include #include #include #include @@ -24,12 +27,15 @@ namespace cucumber_cpp::library::formatter::helper { std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, const Theme& theme); + std::string FormatPickleAttemptTitle(const cucumber::messages::pickle& pickle, std::size_t attempt, bool retry, const cucumber::messages::scenario& scenario, const Theme& theme); std::string FormatPickleLocation(const cucumber::messages::pickle& pickle, const std::optional& location, const Theme& theme); std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const Theme& theme); + std::string FormatCodeLocation(const cucumber::messages::source_reference& sourceReference, const Theme& theme); std::string FormatCodeLocation(const cucumber::messages::step_definition* stepDefinition, const Theme& theme); std::string FormatFeatureTitle(const cucumber::messages::feature& feature, const Theme& theme); std::string FormatRuleTitle(const cucumber::messages::rule& rule, const Theme& theme); std::string FormatPickleTags(const cucumber::messages::pickle& pickle, const Theme& theme); + std::string FormatHookTitle(const cucumber::messages::hook& hook, cucumber::messages::test_step_result_status status, bool isBeforeHook, bool useStatusIcon, const Theme& theme); std::string FormatStepTitle(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, cucumber::messages::test_step_result_status status, bool useStatusIcon, const Theme& theme); std::string FormatDocString(const cucumber::messages::pickle_doc_string& pickleDocString, const Theme& theme); std::string FormatDataTable(const cucumber::messages::pickle_table& pickleDataTable, const Theme& theme); diff --git a/cucumber_cpp/library/formatter/helper/PrintMessages.cpp b/cucumber_cpp/library/formatter/helper/PrintMessages.cpp index aa8bd24b..84b92ae0 100644 --- a/cucumber_cpp/library/formatter/helper/PrintMessages.cpp +++ b/cucumber_cpp/library/formatter/helper/PrintMessages.cpp @@ -1,6 +1,7 @@ #include "cucumber_cpp/library/formatter/helper/PrintMessages.hpp" #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/feature.hpp" +#include "cucumber/messages/hook.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_step.hpp" #include "cucumber/messages/rule.hpp" @@ -68,6 +69,23 @@ namespace cucumber_cpp::library::formatter::helper maxContentLength, theme); } + void PrintScenarioAttemptLine(std::ostream& stream, const cucumber::messages::pickle& pickle, std::size_t attempt, bool retry, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength, const Theme& theme) + { + PrintGherkinLine(stream, + FormatPickleAttemptTitle(pickle, attempt, retry, scenario, theme), + FormatPickleLocation(pickle, scenario.location, theme), + scenarioIndent, + maxContentLength, theme); + } + + void PrintHookLine(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::hook& hook, std::size_t scenarioIndent, std::size_t maxContentLength, bool isBeforeHook, bool useStatusIcon, const Theme& theme) + { + PrintGherkinLine(stream, + helper::FormatHookTitle(hook, testStepFinished.test_step_result.status, isBeforeHook, useStatusIcon, theme), + helper::FormatCodeLocation(hook.source_reference, theme), + scenarioIndent + 2, maxContentLength, theme); + } + void PrintStepLine(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength, bool useStatusIcon, const Theme& theme) { PrintGherkinLine(stream, @@ -85,7 +103,7 @@ namespace cucumber_cpp::library::formatter::helper PrintlnIndentedContent(stream, content, scenarioIndent + gherkinIndentLength + stepArgumentIndentLength + (useStatusIcon ? gherkinIndentLength : 0)); } - void PrintAmbiguousStep(std::ostream& stream, query::Query& query, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme) + void PrintAmbiguousStep(std::ostream& stream, const query::Query& query, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme) { if (testStepFinished.test_step_result.status != cucumber::messages::test_step_result_status::AMBIGUOUS) return; diff --git a/cucumber_cpp/library/formatter/helper/PrintMessages.hpp b/cucumber_cpp/library/formatter/helper/PrintMessages.hpp index 5c17b71d..5598d645 100644 --- a/cucumber_cpp/library/formatter/helper/PrintMessages.hpp +++ b/cucumber_cpp/library/formatter/helper/PrintMessages.hpp @@ -3,6 +3,7 @@ #include "cucumber/messages/attachment.hpp" #include "cucumber/messages/feature.hpp" +#include "cucumber/messages/hook.hpp" #include "cucumber/messages/pickle.hpp" #include "cucumber/messages/pickle_step.hpp" #include "cucumber/messages/rule.hpp" @@ -30,9 +31,11 @@ namespace cucumber_cpp::library::formatter::helper void PrintRuleLine(std::ostream& stream, const cucumber::messages::rule& rule, const Theme& theme); void PrintTags(std::ostream& stream, const cucumber::messages::pickle& pickle, std::size_t scenarioIndent, const Theme& theme); void PrintScenarioLine(std::ostream& stream, const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength, const Theme& theme); + void PrintScenarioAttemptLine(std::ostream& stream, const cucumber::messages::pickle& pickle, std::size_t attempt, bool retry, const cucumber::messages::scenario& scenario, std::size_t scenarioIndent, std::size_t maxContentLength, const Theme& theme); + void PrintHookLine(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::hook& hook, std::size_t scenarioIndent, std::size_t maxContentLength, bool isBeforeHook, bool useStatusIcon, const Theme& theme); void PrintStepLine(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const cucumber::messages::step& step, const cucumber::messages::step_definition* stepDefinition, std::size_t scenarioIndent, std::size_t maxContentLength, bool useStatusIcon, const Theme& theme); void PrintStepArgument(std::ostream& stream, const cucumber::messages::pickle_step& pickleStep, std::size_t scenarioIndent, bool useStatusIcon, const helper::Theme& theme); - void PrintAmbiguousStep(std::ostream& stream, query::Query& query, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme); + void PrintAmbiguousStep(std::ostream& stream, const query::Query& query, const cucumber::messages::test_step_finished& testStepFinished, const cucumber::messages::test_step& testStep, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme); void PrintError(std::ostream& stream, const cucumber::messages::test_step_finished& testStepFinished, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme); void PrintGherkinLine(std::ostream& stream, const std::string& title, const std::optional& location, std::size_t indent, std::size_t maxContentLength, const Theme& theme); void PrintAttachment(std::ostream& stream, const cucumber::messages::attachment& attachment, std::size_t scenarioIndent, bool useStatusIcon, const Theme& theme); diff --git a/cucumber_cpp/library/formatter/helper/Theme.cpp b/cucumber_cpp/library/formatter/helper/Theme.cpp index 84d5d6c3..8b5ed233 100644 --- a/cucumber_cpp/library/formatter/helper/Theme.cpp +++ b/cucumber_cpp/library/formatter/helper/Theme.cpp @@ -68,6 +68,7 @@ namespace cucumber_cpp::library::formatter::helper }, .scenario = { .keyword = fmt::emphasis::bold, + .attempt = fmt::emphasis::italic, }, .status = { .all = statusColors, diff --git a/cucumber_cpp/library/formatter/helper/Theme.hpp b/cucumber_cpp/library/formatter/helper/Theme.hpp index 4805d20d..167877d1 100644 --- a/cucumber_cpp/library/formatter/helper/Theme.hpp +++ b/cucumber_cpp/library/formatter/helper/Theme.hpp @@ -51,6 +51,7 @@ namespace cucumber_cpp::library::formatter::helper std::optional all{}; std::optional keyword{}; std::optional name{}; + std::optional attempt{}; } scenario; struct diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index be3760d7..6959fffa 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -202,6 +202,11 @@ namespace cucumber_cpp::library::query return { view.begin(), view.end() }; } + const cucumber::messages::hook& Query::FindHookById(const std::string& id) const + { + return hooksById.at(id); + } + std::optional Query::FindLocationOf(const cucumber::messages::pickle& pickle) const { const auto& lineage = FindLineageByUri(pickle.uri); diff --git a/cucumber_cpp/library/query/Query.hpp b/cucumber_cpp/library/query/Query.hpp index 45d1e16e..7e074c39 100644 --- a/cucumber_cpp/library/query/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -145,6 +145,8 @@ namespace cucumber_cpp::library::query const cucumber::messages::step_definition& FindStepDefinitionById(const std::string& id) const; std::list FindStepDefinitionsById(const cucumber::messages::test_step& testStep) const; + const cucumber::messages::hook& FindHookById(const std::string& id) const; + std::optional FindLocationOf(const cucumber::messages::pickle& pickle) const; const std::map>& TestCaseStarted() const; diff --git a/cucumber_cpp/library/util/ToLower.hpp b/cucumber_cpp/library/util/ToLower.hpp new file mode 100644 index 00000000..d7dacec5 --- /dev/null +++ b/cucumber_cpp/library/util/ToLower.hpp @@ -0,0 +1,20 @@ +#ifndef UTIL_TO_LOWER_HPP +#define UTIL_TO_LOWER_HPP + +#include +#include +#include + +namespace cucumber_cpp::library::util +{ + [[nodiscard]] inline std::string ToLower(std::string str) + { + std::transform(str.begin(), str.end(), str.begin(), [](unsigned char c) + { + return std::tolower(c); + }); + return str; + } +} + +#endif From 3d79b0172d27f5790beb17f11e94d43e612299ce Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 22 Jan 2026 15:01:59 +0000 Subject: [PATCH 167/196] Refactor cucumber_cpp formatter helper library - Removed unused EventDataCollector, GetColorFunctions, GherkinDocumentParser, IssueHelpers, KeywordType, LocationHelpers, PickleParser, SummaryHelpers, TestCaseAttemptFormatter, and TestCaseAttemptParser files and their associated headers. - Cleaned up CMakeLists.txt to exclude removed files from the build. - Streamlined the formatter helper library for improved maintainability and reduced complexity. --- cucumber_cpp/library/api/Formatters.cpp | 7 +- cucumber_cpp/library/api/Formatters.hpp | 9 +- cucumber_cpp/library/api/RunCucumber.cpp | 4 +- cucumber_cpp/library/formatter/Formatter.cpp | 4 +- cucumber_cpp/library/formatter/Formatter.hpp | 4 +- .../library/formatter/PrettyFormatter.hpp | 3 - .../library/formatter/SummaryFormatter.hpp | 1 - .../library/formatter/helper/CMakeLists.txt | 20 --- .../formatter/helper/EventDataCollector.cpp | 141 ------------------ .../formatter/helper/EventDataCollector.hpp | 72 --------- .../formatter/helper/GetColorFunctions.cpp | 76 ---------- .../formatter/helper/GetColorFunctions.hpp | 24 --- .../helper/GherkinDocumentParser.cpp | 117 --------------- .../helper/GherkinDocumentParser.hpp | 22 --- .../library/formatter/helper/IssueHelpers.cpp | 36 ----- .../library/formatter/helper/IssueHelpers.hpp | 15 -- .../library/formatter/helper/KeywordType.cpp | 47 ------ .../library/formatter/helper/KeywordType.hpp | 19 --- .../formatter/helper/LocationHelpers.cpp | 17 --- .../formatter/helper/LocationHelpers.hpp | 15 -- .../library/formatter/helper/PickleParser.cpp | 37 ----- .../library/formatter/helper/PickleParser.hpp | 18 --- .../formatter/helper/SummaryHelpers.cpp | 97 ------------ .../formatter/helper/SummaryHelpers.hpp | 14 -- .../helper/TestCaseAttemptFormatter.cpp | 98 ------------ .../helper/TestCaseAttemptFormatter.hpp | 13 -- .../helper/TestCaseAttemptParser.cpp | 139 ----------------- .../helper/TestCaseAttemptParser.hpp | 53 ------- 28 files changed, 10 insertions(+), 1112 deletions(-) delete mode 100644 cucumber_cpp/library/formatter/helper/EventDataCollector.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/EventDataCollector.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/IssueHelpers.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/IssueHelpers.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/KeywordType.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/KeywordType.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/LocationHelpers.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/LocationHelpers.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/PickleParser.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/PickleParser.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp delete mode 100644 cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp delete mode 100644 cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index d07de8e9..cb9b5355 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -4,7 +4,6 @@ #include "cucumber_cpp/library/formatter/MessageFormatter.hpp" #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "nlohmann/json_fwd.hpp" @@ -47,7 +46,7 @@ namespace cucumber_cpp::library::api return { view.begin(), view.end() }; } - std::list> Formatters::EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output) + std::list> Formatters::EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, std::ostream& output) { std::list> activeFormatters; @@ -56,14 +55,14 @@ namespace cucumber_cpp::library::api const FormatterOption option{ formatterName }; if (option.output.empty()) - activeFormatters.emplace_back(availableFormatters.at(option.name).factory(supportCodeLibrary, query, eventDataCollector, formatOptions, output)); + activeFormatters.emplace_back(availableFormatters.at(option.name).factory(supportCodeLibrary, query, formatOptions, output)); else { const auto absolutePath = std::filesystem::absolute(std::filesystem::path{ option.output }).string(); if (!customOutputFiles.contains(absolutePath)) customOutputFiles.try_emplace(absolutePath, std::make_unique(absolutePath)); - activeFormatters.emplace_back(availableFormatters.at(option.name).factory(supportCodeLibrary, query, eventDataCollector, formatOptions, *customOutputFiles.at(absolutePath))); + activeFormatters.emplace_back(availableFormatters.at(option.name).factory(supportCodeLibrary, query, formatOptions, *customOutputFiles.at(absolutePath))); } } diff --git a/cucumber_cpp/library/api/Formatters.hpp b/cucumber_cpp/library/api/Formatters.hpp index 1ade2382..268e9feb 100644 --- a/cucumber_cpp/library/api/Formatters.hpp +++ b/cucumber_cpp/library/api/Formatters.hpp @@ -2,7 +2,6 @@ #define API_FORMATTERS_HPP #include "cucumber_cpp/library/formatter/Formatter.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "nlohmann/json_fwd.hpp" @@ -29,7 +28,7 @@ namespace cucumber_cpp::library::api struct RegisteredFormatter { - std::function(support::SupportCodeLibrary&, query::Query&, const formatter::helper::EventDataCollector&, const nlohmann::json& formatOptions, std::ostream&)> factory; + std::function(support::SupportCodeLibrary&, query::Query&, const nlohmann::json& formatOptions, std::ostream&)> factory; bool hasOutput{ false }; }; @@ -42,7 +41,7 @@ namespace cucumber_cpp::library::api std::set> GetAvailableFormatterNames() const; - [[nodiscard]] std::list> EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const formatter::helper::EventDataCollector& eventDataCollector, std::ostream& output = std::cout); + [[nodiscard]] std::list> EnableFormatters(const std::set>& format, const nlohmann::json& formatOptions, support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, std::ostream& output = std::cout); private: std::map> availableFormatters; @@ -56,9 +55,9 @@ namespace cucumber_cpp::library::api template void Formatters::RegisterFormatter(bool hasOutput) { - availableFormatters.try_emplace(T::name, [](support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const formatter::helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& output) + availableFormatters.try_emplace(T::name, [](support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const nlohmann::json& formatOptions, std::ostream& output) { - return std::make_unique(supportCodeLibrary, query, eventDataCollector, formatOptions, output); + return std::make_unique(supportCodeLibrary, query, formatOptions, output); }, hasOutput); } diff --git a/cucumber_cpp/library/api/RunCucumber.cpp b/cucumber_cpp/library/api/RunCucumber.cpp index 3908dc27..bfcf3274 100644 --- a/cucumber_cpp/library/api/RunCucumber.cpp +++ b/cucumber_cpp/library/api/RunCucumber.cpp @@ -9,7 +9,6 @@ #include "cucumber_cpp/library/api/Formatters.hpp" #include "cucumber_cpp/library/api/Gherkin.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/runtime/MakeRuntime.hpp" #include "cucumber_cpp/library/support/HookRegistry.hpp" @@ -184,11 +183,10 @@ namespace cucumber_cpp::library::api .undefinedParameters = undefinedParameters, }; - formatter::helper::EventDataCollector eventDataCollector{ broadcaster }; query::Query query{ broadcaster }; const auto formatOptionsJson = formatOptions.empty() ? nlohmann::json::object() : nlohmann::json::parse(formatOptions); - const auto activeFormatters = formatters.EnableFormatters(format, formatOptionsJson, supportCodeLibrary, query, eventDataCollector); + const auto activeFormatters = formatters.EnableFormatters(format, formatOptionsJson, supportCodeLibrary, query); const auto pickleSources = CollectPickles(options.sources, idGenerator, broadcaster); const auto orderedPickles = OrderPickles(options.sources, pickleSources | std::views::filter(FilterByTagExpression(options.sources))); diff --git a/cucumber_cpp/library/formatter/Formatter.cpp b/cucumber_cpp/library/formatter/Formatter.cpp index e172a82f..4e97ce02 100644 --- a/cucumber_cpp/library/formatter/Formatter.cpp +++ b/cucumber_cpp/library/formatter/Formatter.cpp @@ -1,6 +1,5 @@ #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber/messages/envelope.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -10,14 +9,13 @@ namespace cucumber_cpp::library::formatter { - Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& outputStream) + Formatter::Formatter(support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const nlohmann::json& formatOptions, std::ostream& outputStream) : util::Listener{ query, [this](const cucumber::messages::envelope& envelope) { OnEnvelope(envelope); } } , supportCodeLibrary{ supportCodeLibrary } , query{ query } - , eventDataCollector{ eventDataCollector } , formatOptions{ formatOptions } , outputStream{ outputStream } { diff --git a/cucumber_cpp/library/formatter/Formatter.hpp b/cucumber_cpp/library/formatter/Formatter.hpp index 83447601..97145494 100644 --- a/cucumber_cpp/library/formatter/Formatter.hpp +++ b/cucumber_cpp/library/formatter/Formatter.hpp @@ -2,7 +2,6 @@ #define FORMATTER_FORMATTER_HPP #include "cucumber/messages/envelope.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" @@ -16,7 +15,7 @@ namespace cucumber_cpp::library::formatter struct Formatter : util::Listener { - Formatter(support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const helper::EventDataCollector& eventDataCollector, const nlohmann::json& formatOptions, std::ostream& outputStream = std::cout); + Formatter(support::SupportCodeLibrary& supportCodeLibrary, query::Query& query, const nlohmann::json& formatOptions, std::ostream& outputStream = std::cout); virtual ~Formatter() = default; protected: @@ -24,7 +23,6 @@ namespace cucumber_cpp::library::formatter support::SupportCodeLibrary& supportCodeLibrary; query::Query& query; - const helper::EventDataCollector& eventDataCollector; const nlohmann::json& formatOptions; std::ostream& outputStream; }; diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.hpp b/cucumber_cpp/library/formatter/PrettyFormatter.hpp index 7dfda3f3..05a32313 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.hpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.hpp @@ -15,7 +15,6 @@ #include "cucumber/messages/test_step.hpp" #include "cucumber/messages/test_step_finished.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" -#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" #include #include @@ -56,8 +55,6 @@ namespace cucumber_cpp::library::formatter Options options{ formatOptions.contains(name) ? formatOptions.at(name) : nlohmann::json::object() }; - std::map testCaseStartedIdToScenarioMap; - std::map maxContentLengthByTestCaseStartedId; std::map scenarioIndentByTestCaseStartedId; diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.hpp b/cucumber_cpp/library/formatter/SummaryFormatter.hpp index bb38b4b3..b2c80899 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.hpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.hpp @@ -4,7 +4,6 @@ #include "cucumber/messages/duration.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" #include #include diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index 743bb681..22044bb7 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -1,32 +1,12 @@ add_library(cucumber_cpp.library.formatter.helper ${CCR_EXCLUDE_FROM_ALL}) target_sources(cucumber_cpp.library.formatter.helper PRIVATE - EventDataCollector.cpp - EventDataCollector.hpp FormatMessages.cpp FormatMessages.hpp - GetColorFunctions.hpp - GetColorFunctions.cpp - GherkinDocumentParser.cpp - GherkinDocumentParser.hpp IndentString.cpp IndentString.hpp - IssueHelpers.cpp - IssueHelpers.hpp - KeywordType.cpp - KeywordType.hpp - LocationHelpers.cpp - LocationHelpers.hpp - PickleParser.cpp - PickleParser.hpp PrintMessages.cpp PrintMessages.hpp - SummaryHelpers.hpp - SummaryHelpers.cpp - TestCaseAttemptFormatter.cpp - TestCaseAttemptFormatter.hpp - TestCaseAttemptParser.cpp - TestCaseAttemptParser.hpp TextBuilder.cpp TextBuilder.hpp Theme.cpp diff --git a/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp b/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp deleted file mode 100644 index f2459bec..00000000 --- a/cucumber_cpp/library/formatter/helper/EventDataCollector.cpp +++ /dev/null @@ -1,141 +0,0 @@ -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber/messages/attachment.hpp" -#include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/test_case.hpp" -#include "cucumber/messages/test_case_finished.hpp" -#include "cucumber/messages/test_case_started.hpp" -#include "cucumber/messages/test_step_finished.hpp" -#include "cucumber/messages/test_step_result.hpp" -#include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/util/Broadcaster.hpp" -#include "cucumber_cpp/library/util/GetWorstTestStepResult.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - EventDataCollector::EventDataCollector(util::Broadcaster& broadcaster) - : util::Listener{ broadcaster, [this](const cucumber::messages::envelope& envelope) - { - OnEnvelope(envelope); - } } - {} - - const cucumber::messages::gherkin_document& EventDataCollector::GetGherkinDocument(std::string uri) const - { - return gherkinDocumentMap.at(uri); - } - - const cucumber::messages::pickle& EventDataCollector::GetPickle(std::string id) const - { - return pickleMap.at(id); - } - - const cucumber::messages::test_case& EventDataCollector::GetTestCase(std::string id) const - { - return testCaseMap.at(id); - } - - std::vector EventDataCollector::GetTestCaseAttempts() const - { - std::vector attempts{}; - for (const auto& key : testCaseAttemptDataMap | std::views::keys) - attempts.emplace_back(GetTestCaseAttempt(key)); - return attempts; - } - - void EventDataCollector::OnEnvelope(const cucumber::messages::envelope& envelope) - { - if (envelope.gherkin_document) - { - gherkinDocumentMap.emplace(envelope.gherkin_document->uri.value(), *envelope.gherkin_document); - } - else if (envelope.pickle) - { - pickleMap.emplace(envelope.pickle->id, *envelope.pickle); - } - else if (envelope.undefined_parameter_type) - { - undefinedParameterTypes.emplace_back(*envelope.undefined_parameter_type); - } - else if (envelope.test_case) - { - testCaseMap.emplace(envelope.test_case->id, *envelope.test_case); - } - else if (envelope.test_case_started) - { - InitTestCaseAttempt(*envelope.test_case_started); - } - else if (envelope.attachment) - { - StoreAttachment(*envelope.attachment); - } - else if (envelope.test_step_finished) - { - StoreTestStepResult(*envelope.test_step_finished); - } - else if (envelope.test_case_finished) - { - StoreTestCaseResult(*envelope.test_case_finished); - } - } - - void EventDataCollector::InitTestCaseAttempt(const cucumber::messages::test_case_started& testCaseStarted) - { - testCaseAttemptDataMap.emplace(testCaseStarted.id, - TestCaseAttemptData{ - .attempt = testCaseStarted.attempt, - .willBeRetried = false, - .testCaseId = testCaseStarted.test_case_id, - .worstTestStepResult = { - .status = cucumber::messages::test_step_result_status::UNKNOWN, - }, - }); - } - - void EventDataCollector::StoreAttachment(const cucumber::messages::attachment& attachment) - { - if (attachment.test_case_started_id && attachment.test_step_id) - { - auto& testCaseAttemptData = testCaseAttemptDataMap.at(*attachment.test_case_started_id); - testCaseAttemptData.stepAttachments[*attachment.test_step_id].emplace_back(attachment); - } - } - - void EventDataCollector::StoreTestStepResult(const cucumber::messages::test_step_finished& testStepFinished) - { - auto& testCaseAttemptData = testCaseAttemptDataMap.at(testStepFinished.test_case_started_id); - testCaseAttemptData.stepResults[testStepFinished.test_step_id] = testStepFinished.test_step_result; - } - - void EventDataCollector::StoreTestCaseResult(const cucumber::messages::test_case_finished& testCaseFinished) - { - auto& testCaseAttemptData = testCaseAttemptDataMap.at(testCaseFinished.test_case_started_id); - - const auto allStepResults = testCaseAttemptData.stepResults | std::views::values; - std::vector stepResults{ allStepResults.begin(), allStepResults.end() }; - - testCaseAttemptData.worstTestStepResult = util::GetWorstTestStepResult(stepResults); - testCaseAttemptData.willBeRetried = testCaseFinished.will_be_retried; - } - - TestCaseAttempt EventDataCollector::GetTestCaseAttempt(const std::string& testCaseStartedId) const - { - const auto& testCaseAttemptData = testCaseAttemptDataMap.at(testCaseStartedId); - const auto& testCase = testCaseMap.at(testCaseAttemptData.testCaseId); - const auto& pickle = pickleMap.at(testCase.pickle_id); - return { - .attempt = testCaseAttemptData.attempt, - .willBeRetried = testCaseAttemptData.willBeRetried, - .gherkinDocument = gherkinDocumentMap.at(pickle.uri), - .pickle = pickle, - .stepAttachments = testCaseAttemptData.stepAttachments, - .stepResults = testCaseAttemptData.stepResults, - .testCase = testCase, - .worstTestStepResult = testCaseAttemptData.worstTestStepResult, - }; - } -} diff --git a/cucumber_cpp/library/formatter/helper/EventDataCollector.hpp b/cucumber_cpp/library/formatter/helper/EventDataCollector.hpp deleted file mode 100644 index a25ce4de..00000000 --- a/cucumber_cpp/library/formatter/helper/EventDataCollector.hpp +++ /dev/null @@ -1,72 +0,0 @@ -#ifndef HELPER_EVENT_DATA_COLLECTOR_HPP -#define HELPER_EVENT_DATA_COLLECTOR_HPP - -#include "cucumber/messages/attachment.hpp" -#include "cucumber/messages/envelope.hpp" -#include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/test_case.hpp" -#include "cucumber/messages/test_case_finished.hpp" -#include "cucumber/messages/test_case_started.hpp" -#include "cucumber/messages/test_step_finished.hpp" -#include "cucumber/messages/test_step_result.hpp" -#include "cucumber/messages/undefined_parameter_type.hpp" -#include "cucumber_cpp/library/util/Broadcaster.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - struct TestCaseAttemptData - { - std::size_t attempt; - bool willBeRetried; - std::string testCaseId; - std::map> stepAttachments; - std::map stepResults; - cucumber::messages::test_step_result worstTestStepResult; - }; - - struct TestCaseAttempt - { - std::size_t attempt; - bool willBeRetried; - const cucumber::messages::gherkin_document& gherkinDocument; - const cucumber::messages::pickle& pickle; - const std::map>& stepAttachments; - const std::map& stepResults; - const cucumber::messages::test_case& testCase; - cucumber::messages::test_step_result worstTestStepResult; - }; - - struct EventDataCollector : util::Listener - { - explicit EventDataCollector(util::Broadcaster& broadcaster); - - const cucumber::messages::gherkin_document& GetGherkinDocument(std::string uri) const; - const cucumber::messages::pickle& GetPickle(std::string id) const; - const cucumber::messages::test_case& GetTestCase(std::string id) const; - - std::vector GetTestCaseAttempts() const; - - private: - void OnEnvelope(const cucumber::messages::envelope& envelope); - - void InitTestCaseAttempt(const cucumber::messages::test_case_started& testCaseStarted); - void StoreAttachment(const cucumber::messages::attachment& attachment); - void StoreTestStepResult(const cucumber::messages::test_step_finished& testStepFinished); - void StoreTestCaseResult(const cucumber::messages::test_case_finished& testCaseFinished); - - TestCaseAttempt GetTestCaseAttempt(const std::string& testCaseStartedId) const; - - std::map gherkinDocumentMap; - std::map pickleMap; - std::map testCaseMap; - std::map testCaseAttemptDataMap; - std::vector undefinedParameterTypes; - }; -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp b/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp deleted file mode 100644 index 2529cce3..00000000 --- a/cucumber_cpp/library/formatter/helper/GetColorFunctions.cpp +++ /dev/null @@ -1,76 +0,0 @@ - -#include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" -#include "cucumber/messages/test_step_result_status.hpp" -#include "fmt/color.h" -#include "fmt/format.h" -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - namespace - { - template - std::string ColorStringFmt(std::string_view sv) - { - return fmt::format("{}", fmt::styled(sv, fmt::fg(colour))); - } - } - - std::function ColorFunctions::ForStatus(cucumber::messages::test_step_result_status status) - { - using enum cucumber::messages::test_step_result_status; - - switch (status) - { - case PASSED: - return ColorStringFmt; - case SKIPPED: - return ColorStringFmt; - case UNKNOWN: - case PENDING: - case UNDEFINED: - return ColorStringFmt; - case AMBIGUOUS: - case FAILED: - default: - return ColorStringFmt; - } - } - - std::string ColorFunctions::Attachment(std::string_view sv) - { - return ColorStringFmt(sv); - } - - std::string ColorFunctions::Location(std::string_view sv) - { - return ColorStringFmt(sv); - } - - std::string ColorFunctions::Tag(std::string_view sv) - { - return ColorStringFmt(sv); - } - - std::string ColorFunctions::DiffAdded(std::string_view sv) - { - return ColorStringFmt(sv); - } - - std::string ColorFunctions::DiffRemoved(std::string_view sv) - { - return ColorStringFmt(sv); - } - - std::string ColorFunctions::ErrorMessage(std::string_view sv) - { - return ColorStringFmt(sv); - } - - std::string ColorFunctions::ErrorStack(std::string_view sv) - { - return ColorStringFmt(sv); - } -} diff --git a/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp b/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp deleted file mode 100644 index 5f5526bc..00000000 --- a/cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#ifndef FORMATTER_GET_COLOR_FUNCTIONS_HPP -#define FORMATTER_GET_COLOR_FUNCTIONS_HPP - -#include "cucumber/messages/test_step_result_status.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - struct ColorFunctions - { - static std::function ForStatus(cucumber::messages::test_step_result_status status); - static std::string Attachment(std::string_view sv); - static std::string Location(std::string_view sv); - static std::string Tag(std::string_view sv); - static std::string DiffAdded(std::string_view sv); - static std::string DiffRemoved(std::string_view sv); - static std::string ErrorMessage(std::string_view sv); - static std::string ErrorStack(std::string_view sv); - }; -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp deleted file mode 100644 index 7572e805..00000000 --- a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.cpp +++ /dev/null @@ -1,117 +0,0 @@ -#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" -#include "cucumber/messages/background.hpp" -#include "cucumber/messages/feature_child.hpp" -#include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/rule.hpp" -#include "cucumber/messages/rule_child.hpp" -#include "cucumber/messages/scenario.hpp" -#include "cucumber/messages/step.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - namespace - { - const std::vector& ExtractSteps(const std::variant& scenarioOrBackground) - { - return std::visit([](const auto& item) -> const std::vector& - { - return item.steps; - }, - scenarioOrBackground); - } - - std::vector ExtractScenarioFromRuleChild(const cucumber::messages::rule_child& child) - { - if (child.scenario) - return { *child.scenario }; - return {}; - } - - std::vector ExtractRulesFeatureChild(const cucumber::messages::feature_child& child) - { - if (child.rule) - return { *child.rule }; - return {}; - } - - std::vector ExtractScenarioContainers(const cucumber::messages::feature_child& child) - { - if (child.rule) - { - std::vector result; - for (const auto& scenario : child.rule->children | std::views::transform(ExtractScenarioFromRuleChild) | std::views::join) - result.push_back(scenario); - return result; - } - - if (child.scenario) - return { *child.scenario }; - - return {}; - } - - std::variant ExtractRuleContainers(const cucumber::messages::rule_child& child) - { - if (child.background) - return *child.background; - else - return *child.scenario; - } - - std::vector> ExtractStepContainers(const cucumber::messages::feature_child& child) - { - if (child.background) - return { *child.background }; - if (child.rule) - { - auto iter = child.rule->children | std::views::transform(ExtractRuleContainers); - return { iter.begin(), iter.end() }; - } - - return { *child.scenario }; - } - } - - GherkinStepMap GetGherkinStepMap(const cucumber::messages::gherkin_document& gherkinDocument) - { - auto steps = gherkinDocument.feature->children | - std::views::transform(ExtractStepContainers) | std::views::join | - std::views::transform(ExtractSteps) | std::views::join; - - GherkinStepMap map; - - for (const auto& step : steps) - map.emplace(step.id, step); - - return map; - } - - GherkinScenarioMap GetGherkinScenarioMap(const cucumber::messages::gherkin_document& gherkinDocument) - { - GherkinScenarioMap map; - - for (const auto& scenario : gherkinDocument.feature->children | std::views::transform(ExtractScenarioContainers) | std::views::join) - map.emplace(scenario.id, scenario); - - return map; - } - - GherkinScenarioLocationMap GetGherkinScenarioLocationMap(const cucumber::messages::gherkin_document& gherkinDocument) - { - GherkinScenarioLocationMap locationMap; - GherkinScenarioMap scenarioMap = GetGherkinScenarioMap(gherkinDocument); - - for (const auto& [id, scenario] : scenarioMap) - { - locationMap.emplace(id, scenario.location); - for (const auto& example : scenario.examples) - for (const auto& tableRow : example.table_body) - locationMap.emplace(tableRow.id, tableRow.location); - } - - return locationMap; - } -} diff --git a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp b/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp deleted file mode 100644 index 49106a7f..00000000 --- a/cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp +++ /dev/null @@ -1,22 +0,0 @@ -#ifndef HELPER_GHERKIN_DOCUMENT_PARSER_HPP -#define HELPER_GHERKIN_DOCUMENT_PARSER_HPP - -#include "cucumber/messages/gherkin_document.hpp" -#include "cucumber/messages/location.hpp" -#include "cucumber/messages/scenario.hpp" -#include "cucumber/messages/step.hpp" -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - using GherkinStepMap = std::map; - using GherkinScenarioMap = std::map; - using GherkinScenarioLocationMap = std::map; - - GherkinStepMap GetGherkinStepMap(const cucumber::messages::gherkin_document& gherkinDocument); - GherkinScenarioMap GetGherkinScenarioMap(const cucumber::messages::gherkin_document& gherkinDocument); - GherkinScenarioLocationMap GetGherkinScenarioLocationMap(const cucumber::messages::gherkin_document& gherkinDocument); -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp b/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp deleted file mode 100644 index 9a53f84d..00000000 --- a/cucumber_cpp/library/formatter/helper/IssueHelpers.cpp +++ /dev/null @@ -1,36 +0,0 @@ - -#include "cucumber_cpp/library/formatter/helper/IssueHelpers.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp" -#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include "fmt/ostream.h" -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - std::ostream& FormatIssue(std::ostream& outputStream, std::size_t number, const TestCaseAttempt& testCaseAttempt, support::SupportCodeLibrary& supportCodeLibrary, bool printAttachments) - { - using std::operator""sv; - - const auto prefix = std::format("{}) ", number); - const auto formattedTestCaseAttempt = FormatTestCaseAttempt(supportCodeLibrary, testCaseAttempt, printAttachments); - - auto lines = formattedTestCaseAttempt | std::views::split("\n"sv); - - if (std::ranges::distance(lines) == 0) - return outputStream; - - fmt::print(outputStream, "{}{}\n", prefix, std::string_view{ lines.front().begin(), lines.front().end() }); - - for (const auto line : lines | std::views::drop(1)) - fmt::print(outputStream, "{:{}}{}\n", "", prefix.size(), std::string_view{ line.begin(), line.end() }); - - return outputStream; - } -} diff --git a/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp b/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp deleted file mode 100644 index 6ff37037..00000000 --- a/cucumber_cpp/library/formatter/helper/IssueHelpers.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef HELPER_ISSUE_HELPERS_HPP -#define HELPER_ISSUE_HELPERS_HPP - -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - std::ostream& FormatIssue(std::ostream& outputStream, std::size_t number, const TestCaseAttempt& testCaseAttempt, support::SupportCodeLibrary& supportCodeLibrary, bool printAttachments = true); -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/KeywordType.cpp b/cucumber_cpp/library/formatter/helper/KeywordType.cpp deleted file mode 100644 index 44ba340d..00000000 --- a/cucumber_cpp/library/formatter/helper/KeywordType.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "cucumber_cpp/library/formatter/helper/KeywordType.hpp" -#include "cucumber/gherkin/dialect.hpp" -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - namespace - { - constexpr std::array stepKeywords{ - "given", - "when", - "then", - "and", - "but", - }; - } - - KeywordType GetStepKeywordType(std::string_view keyword, std::string_view language, std::optional previousKeywordType) - { - const auto& dialect = cucumber::gherkin::keywords(language); - const auto typeIter = std::ranges::find_if(stepKeywords, [&dialect, keyword](std::string_view stepKeyword) - { - return std::ranges::find(dialect.at(stepKeyword), keyword) != dialect.at(stepKeyword).end(); - }); - - switch (std::distance(stepKeywords.begin(), typeIter)) - { - case 0: // given - return KeywordType::precondition; - case 1: // when - return KeywordType::event; - case 2: // then - return KeywordType::outcome; - case 3: // and - case 4: // but - if (previousKeywordType.has_value()) - return *previousKeywordType; - [[fallthrough]]; - default: - return KeywordType::precondition; - } - } -} diff --git a/cucumber_cpp/library/formatter/helper/KeywordType.hpp b/cucumber_cpp/library/formatter/helper/KeywordType.hpp deleted file mode 100644 index 5fa9328f..00000000 --- a/cucumber_cpp/library/formatter/helper/KeywordType.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#ifndef HELPER_KEYWORD_TYPE_HPP -#define HELPER_KEYWORD_TYPE_HPP - -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - enum class KeywordType - { - precondition, - event, - outcome - }; - - KeywordType GetStepKeywordType(std::string_view keyword, std::string_view language, std::optional previousKeywordType); -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/LocationHelpers.cpp b/cucumber_cpp/library/formatter/helper/LocationHelpers.cpp deleted file mode 100644 index 112f8f08..00000000 --- a/cucumber_cpp/library/formatter/helper/LocationHelpers.cpp +++ /dev/null @@ -1,17 +0,0 @@ -#include "cucumber_cpp/library/formatter/helper/LocationHelpers.hpp" -#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - std::string FormatLocation(LineAndUri obj, std::optional cwd) - { - std::string uri = obj.uri; - if (cwd) - uri = std::filesystem::relative(obj.uri, *cwd).string(); - return std::format("{}:{}", uri, obj.line); - } -} diff --git a/cucumber_cpp/library/formatter/helper/LocationHelpers.hpp b/cucumber_cpp/library/formatter/helper/LocationHelpers.hpp deleted file mode 100644 index 37453654..00000000 --- a/cucumber_cpp/library/formatter/helper/LocationHelpers.hpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef HELPER_FORMAT_LOCATION_HPP -#define HELPER_FORMAT_LOCATION_HPP - -#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - - std::string FormatLocation(LineAndUri obj, std::optional cwd = std::nullopt); -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/PickleParser.cpp b/cucumber_cpp/library/formatter/helper/PickleParser.cpp deleted file mode 100644 index c81093f3..00000000 --- a/cucumber_cpp/library/formatter/helper/PickleParser.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include "cucumber_cpp/library/formatter/helper/PickleParser.hpp" -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - namespace - { - constexpr auto toPair = [](const cucumber::messages::pickle_step& step) -> std::pair - { - return { step.id, step }; - }; - } - - std::map GetPickleStepMap(const cucumber::messages::pickle& pickle) - { - auto range = pickle.steps | std::views::transform(toPair); - return { range.begin(), range.end() }; - } - - std::string_view GetStepKeyword(const cucumber::messages::pickle_step& pickleStep, const GherkinStepMap& gherkinStepMap) - { - auto first = std::ranges::find_if(pickleStep.ast_node_ids, [&gherkinStepMap](const std::string& id) - { - return gherkinStepMap.contains(id); - }); - return gherkinStepMap.at(*first).keyword; - } -} diff --git a/cucumber_cpp/library/formatter/helper/PickleParser.hpp b/cucumber_cpp/library/formatter/helper/PickleParser.hpp deleted file mode 100644 index f7cdab76..00000000 --- a/cucumber_cpp/library/formatter/helper/PickleParser.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef HELPER_PICKLE_PARSER_HPP -#define HELPER_PICKLE_PARSER_HPP - -#include "cucumber/messages/pickle.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - std::map GetPickleStepMap(const cucumber::messages::pickle& pickle); - - std::string_view GetStepKeyword(const cucumber::messages::pickle_step& pickleStep, const GherkinStepMap& gherkinStepMap); -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp b/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp deleted file mode 100644 index 2adc6c5c..00000000 --- a/cucumber_cpp/library/formatter/helper/SummaryHelpers.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp" -#include "cucumber/messages/duration.hpp" -#include "cucumber/messages/test_step.hpp" -#include "cucumber/messages/test_step_result.hpp" -#include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" -#include "cucumber_cpp/library/support/Join.hpp" -#include "cucumber_cpp/library/util/Duration.hpp" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - namespace - { - std::string GetCountSummary(std::span results, std::string_view type) - { - ColorFunctions colorFunctions{}; - std::map counts; - - for (const auto& result : results) - ++counts[result.status]; - - auto values = counts | std::views::values; - const auto total = std::accumulate(values.begin(), values.end(), std::size_t{ 0u }); - - std::string text = std::format("{} {}{}", total, type, (total == 1 ? "" : "s")); - - if (total > 0) - { - std::vector details; - for (const auto& [status, count] : counts) - { - auto statusStr = std::string{ cucumber::messages::to_string(status) }; - std::transform(statusStr.begin(), statusStr.end(), statusStr.begin(), [](unsigned char c) - { - return std::tolower(c); - }); - details.emplace_back(colorFunctions.ForStatus(status)(std::format("{} {}", count, statusStr))); - } - - text += " " + support::Join(details, ", "); - } - - return text; - } - - std::string GetDurationSummary(const cucumber::messages::duration& duration) - { - const auto total = util::DurationToMilliseconds(duration); - return std::format("{:%Mm %S}s", total); - } - - bool HasPickleStepId(const cucumber::messages::test_step& testStep) - { - return testStep.pickle_step_id.has_value(); - } - } - - std::string FormatSummary(std::span testCaseAttempts, cucumber::messages::duration testRunDuration) - { - std::vector testCaseResults; - std::vector testStepResults; - cucumber::messages::duration totalStepDuration{}; - - for (const auto& testCaseAttempt : testCaseAttempts) - { - for (const auto& [_, stepResult] : testCaseAttempt.stepResults) - totalStepDuration += stepResult.duration; - - if (!testCaseAttempt.willBeRetried) - { - testCaseResults.emplace_back(testCaseAttempt.worstTestStepResult); - - for (const auto& testStep : testCaseAttempt.testCase.test_steps | - std::views::filter(HasPickleStepId)) - testStepResults.emplace_back(testCaseAttempt.stepResults.at(testStep.id)); - } - } - - const auto scenarioSummary = GetCountSummary(testCaseResults, "scenario"); - const auto stepSummary = GetCountSummary(testStepResults, "step"); - const auto durationSummary = std::format("{} (executing steps: {})\n", GetDurationSummary(testRunDuration), GetDurationSummary(totalStepDuration)); - - return support::Join({ scenarioSummary, stepSummary, durationSummary }, "\n"); - } -} diff --git a/cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp b/cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp deleted file mode 100644 index fea98149..00000000 --- a/cucumber_cpp/library/formatter/helper/SummaryHelpers.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef HELPER_SUMMARY_HELPERS_HPP -#define HELPER_SUMMARY_HELPERS_HPP - -#include "cucumber/messages/duration.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - std::string FormatSummary(std::span testCaseAttempts, cucumber::messages::duration testRunDuration); -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp deleted file mode 100644 index 17664554..00000000 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.cpp +++ /dev/null @@ -1,98 +0,0 @@ -#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp" -#include "cucumber/messages/test_step_result_status.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber_cpp/library/formatter/helper/GetColorFunctions.hpp" -#include "cucumber_cpp/library/formatter/helper/IndentString.hpp" -#include "cucumber_cpp/library/formatter/helper/LocationHelpers.hpp" -#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" -#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - namespace - { - std::string GetAttemptText(std::size_t attempt, bool willBeRetried) - { - if (attempt > 0 || willBeRetried) - return std::format(" (attempt {}{})", attempt + 1, willBeRetried ? ", retried" : ""); - return ""; - } - - std::string GetStepMessage(const ParsedTestStep& testStep) - { - using enum cucumber::messages::test_step_result_status; - - switch (testStep.result.status) - { - case AMBIGUOUS: - case FAILED: - return testStep.result.message.value_or(""); - - case UNDEFINED: - return "Undefined. Implement with the following snippet: ''"; - - case PENDING: - return "Pending"; - - default: - return {}; - } - } - - std::string FormatStep(const ParsedTestStep& testStep, bool printAttachments) - { - ColorFunctions colorFunctions; - const auto colorStatus = colorFunctions.ForStatus(testStep.result.status); - - auto identifier = std::format("{}{}", testStep.keyword, testStep.text.value_or("")); - auto text = colorStatus(std::format("{} {}", cucumber::messages::to_string(testStep.result.status), identifier)); - if (testStep.name) - text += colorStatus(*testStep.name); - - if (testStep.actionLocation) - text += std::format(" # {}", colorFunctions.Location(FormatLocation(*testStep.actionLocation, std::filesystem::current_path()))); - - text += '\n'; - - if (testStep.argument) - // const argumentsText = formatStepArgument(testStep.argument) - // text += indentString(`${colorFn(argumentsText)}\n`, 4) - ; - - // if (valueOrDefault(printAttachments, true)) { - // attachments.forEach(({ body, mediaType, fileName }) => { - // let message = '' - // if (mediaType === 'text/plain') { - // message = `: ${body}` - // } else if (fileName) { - // message = `: ${fileName}` - // } - // text += indentString(`Attachment (${mediaType})${message}\n`, 4) - // }) - // } - - auto message = GetStepMessage(testStep); - if (!message.empty()) - text += IndentString(colorStatus(message), 4) + "\n"; - - return text; - } - } - - std::string FormatTestCaseAttempt(support::SupportCodeLibrary& supportCodeLibrary, const TestCaseAttempt& testCaseAttempt, bool printAttachments) - { - const auto parsed = ParseTestCaseAttempt(supportCodeLibrary, testCaseAttempt); - - auto text = std::format("Scenario: {}", parsed.parsedTestCase.name); - text += GetAttemptText(parsed.parsedTestCase.attempt, testCaseAttempt.willBeRetried); - text += std::format(" {}\n", FormatLocation(parsed.parsedTestCase.sourceLocation.value())); - for (auto const& testStep : parsed.parsedTestSteps) - text += FormatStep(testStep, printAttachments); - return text + '\n'; - } -} diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp deleted file mode 100644 index 95fa7e1e..00000000 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptFormatter.hpp +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef FORMATTER_TEST_CASE_ATTEMPT_FORMATTER_HPP -#define FORMATTER_TEST_CASE_ATTEMPT_FORMATTER_HPP - -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include - -namespace cucumber_cpp::library::formatter::helper -{ - std::string FormatTestCaseAttempt(support::SupportCodeLibrary& supportCodeLibrary, const TestCaseAttempt& testCaseAttempt, bool printAttachments); -} - -#endif diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp deleted file mode 100644 index 69e197e7..00000000 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.cpp +++ /dev/null @@ -1,139 +0,0 @@ -#include "cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp" -#include "cucumber/messages/attachment.hpp" -#include "cucumber/messages/pickle_step.hpp" -#include "cucumber/messages/test_step.hpp" -#include "cucumber/messages/test_step_result.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber_cpp/library/formatter/helper/GherkinDocumentParser.hpp" -#include "cucumber_cpp/library/formatter/helper/KeywordType.hpp" -#include "cucumber_cpp/library/formatter/helper/PickleParser.hpp" -#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include -#include -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - namespace - { - ParsedTestStep ParseStep(bool isBeforeHook, - const GherkinStepMap& gherkinStepMap, - std::string_view keyword, - KeywordType keywordType, - const std::map& pickleStepMap, - std::filesystem::path pickleUri, - support::SupportCodeLibrary supportCode, - const cucumber::messages::test_step& testStep, - const cucumber::messages::test_step_result& testStepResult, - std::span attachments) - { - ParsedTestStep parsedTestStep{ - .attachments = attachments, - .keyword = std::string{ testStep.pickle_step_id ? keyword : (isBeforeHook ? "Before" : "After") }, - .result = testStepResult, - }; - - if (testStep.hook_id) - { - const auto& definition = supportCode.hookRegistry.GetDefinitionById(testStep.hook_id.value()); - parsedTestStep.actionLocation = { - .uri = definition.hook.source_reference.uri.value(), - .line = definition.hook.source_reference.location->line, - }; - parsedTestStep.name = std::format(" [Hook]"); - } - - if (testStep.step_definition_ids && testStep.step_definition_ids->size() == 1) - { - const auto& definition = supportCode.stepRegistry.GetDefinitionById(testStep.step_definition_ids->front()); - parsedTestStep.actionLocation = { - .uri = definition.uri.string(), - .line = definition.line, - }; - } - - if (testStep.pickle_step_id) - { - const auto& pickleStep = pickleStepMap.at(*testStep.pickle_step_id); - - parsedTestStep.location = { - .uri = pickleUri.string(), - .line = gherkinStepMap.at(pickleStep.ast_node_ids.front()).location.line - }; - parsedTestStep.text = pickleStep.text; - if (pickleStep.argument) - parsedTestStep.argument = *pickleStep.argument; - } - - // if (testStepResult.status == cucumber::messages::test_step_result_status::UNDEFINED) - // parsedTestStep.snippet = "not supporting snippet generation yet"; - - return parsedTestStep; - } - } - - ParsedTestCaseAttempt ParseTestCaseAttempt(support::SupportCodeLibrary& supportCodeLibrary, const TestCaseAttempt& testCaseAttempt) - { - const auto& testCase = testCaseAttempt.testCase; - const auto& pickle = testCaseAttempt.pickle; - const auto& gherkinDocument = testCaseAttempt.gherkinDocument; - - const auto gherkinStepMap = GetGherkinStepMap(gherkinDocument); - const auto gherkinScenarioLocationMap = GetGherkinScenarioLocationMap(gherkinDocument); - const auto pickleStepMap = GetPickleStepMap(pickle); - const auto relativePickleUri = pickle.uri; - - const ParsedTestCase parsedTestCase{ - .attempt = testCaseAttempt.attempt, - .name = pickle.name, - .sourceLocation = LineAndUri{ - .uri = relativePickleUri, - .line = gherkinScenarioLocationMap.at(pickle.ast_node_ids[0]).line, - }, - .worstStepStepResult = testCaseAttempt.worstTestStepResult - }; - - std::vector parsedTestSteps{}; - parsedTestSteps.reserve(testCase.test_steps.size()); - - bool isBeforeHook = true; - std::optional previousKeyWordType = KeywordType::precondition; - - for (const auto& testStep : testCase.test_steps) - { - const auto& testStepResult = testCaseAttempt.stepResults.at(testStep.id); - isBeforeHook = isBeforeHook && testStep.hook_id.has_value(); - - std::string_view keyword{}; - KeywordType keywordType{}; - if (testStep.pickle_step_id) - { - const auto& pickleStep = pickleStepMap.at(*testStep.pickle_step_id); - keyword = GetStepKeyword(pickleStep, gherkinStepMap); - keywordType = GetStepKeywordType(keyword, gherkinDocument.feature->language, previousKeyWordType); - } - - parsedTestSteps.emplace_back(ParseStep(isBeforeHook, - gherkinStepMap, - keyword, - keywordType, - pickleStepMap, - relativePickleUri, - supportCodeLibrary, - testStep, - testStepResult, - testCaseAttempt.stepAttachments.contains(testStep.id) ? testCaseAttempt.stepAttachments.at(testStep.id) : std::span{})); - previousKeyWordType = keywordType; - } - - return { - .parsedTestCase = parsedTestCase, - .parsedTestSteps = parsedTestSteps, - }; - } -} diff --git a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp b/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp deleted file mode 100644 index 8e786056..00000000 --- a/cucumber_cpp/library/formatter/helper/TestCaseAttemptParser.hpp +++ /dev/null @@ -1,53 +0,0 @@ -#ifndef FORMATTER_TEST_CASE_ATTEMPT_PARSER_HPP -#define FORMATTER_TEST_CASE_ATTEMPT_PARSER_HPP - -#include "cucumber/messages/attachment.hpp" -#include "cucumber/messages/pickle_step_argument.hpp" -#include "cucumber/messages/test_step_result.hpp" -#include "cucumber_cpp/library/formatter/helper/EventDataCollector.hpp" -#include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" -#include -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::formatter::helper -{ - struct LineAndUri - { - std::string uri; - std::size_t line; - }; - - struct ParsedTestCase - { - std::size_t attempt; - std::string name; - std::optional sourceLocation; - cucumber::messages::test_step_result worstStepStepResult; - }; - - struct ParsedTestStep - { - std::optional actionLocation; - std::optional argument; - std::span attachments; - std::string keyword; - std::optional name; - const cucumber::messages::test_step_result& result; - std::optional location; - std::optional text; - }; - - struct ParsedTestCaseAttempt - { - ParsedTestCase parsedTestCase; - std::vector parsedTestSteps; - }; - - ParsedTestCaseAttempt ParseTestCaseAttempt(support::SupportCodeLibrary& supportCodeLibrary, const TestCaseAttempt& testCaseAttempt); -} - -#endif From e0380796fbbc745d4755f2d6e4f953a48fc1a73a Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 22 Jan 2026 15:15:00 +0000 Subject: [PATCH 168/196] feat: update SummaryFormatter to support custom format options and improve output handling --- cucumber_cpp/acceptance_test/test.bats | 26 +++++++++---------- .../library/formatter/SummaryFormatter.cpp | 16 +++++++++--- .../library/formatter/SummaryFormatter.hpp | 15 +++++++---- 3 files changed, 35 insertions(+), 22 deletions(-) diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index 0f9f5525..6a9c772e 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -29,11 +29,11 @@ teardown() { assert_failure } -@test "Undefined tests" { - run $acceptance_test --format summary pretty message junit --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features +@test "■ tests" { + run $acceptance_test --format summary --format-options "{ \"summary\": {\"theme\":\"plain\"} }" --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "UNDEFINED Given a missing step" - assert_output --partial "SKIPPED Then this should be skipped" + assert_output --partial "■ Given a missing step" + assert_output --partial "↷ Then this should be skipped" } @test "No tests" { @@ -54,7 +54,7 @@ teardown() { assert_output --partial "--required is required" } -@test "Second feature file does not overwrite success with an undefined status" { +@test "Second feature file does not overwrite success with an ■ status" { run $acceptance_test --format summary pretty message junit --tags @undefinedsuccess -- cucumber_cpp/acceptance_test/features/test_undefined_success_1.feature cucumber_cpp/acceptance_test/features/test_undefined_success_2.feature assert_failure } @@ -118,9 +118,9 @@ teardown() { run $acceptance_test --format summary pretty message junit --tags "@result:UNDEFINED" -- cucumber_cpp/acceptance_test/features assert_failure - run $acceptance_test --format summary pretty message junit --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --format-options "{ \"summary\": {\"theme\":\"plain\"} }" --tags "@result:UNDEFINED" --dry-run cucumber_cpp/acceptance_test/features assert_success - assert_output --partial "UNDEFINED Given a missing step" + assert_output --partial "■ Given a missing step" } @test "Test the and keyword" { @@ -152,21 +152,21 @@ teardown() { } @test "Test failing hook before results in error" { - run $acceptance_test --format summary pretty message junit --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --format-options "{ \"summary\": {\"theme\":\"plain\"} }" --tags "@fail_scenariohook_before" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "FAILED Before" + assert_output --partial "✘ Before" } @test "Test failing hook after results in error" { - run $acceptance_test --format summary pretty message junit --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --format-options "{ \"summary\": {\"theme\":\"plain\"} }" --tags "@fail_scenariohook_after" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "FAILED After" + assert_output --partial "✘ After" } @test "Test throwing hook results in error" { - run $acceptance_test --format summary pretty message junit --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary --format-options "{ \"summary\": {\"theme\":\"plain\"} }" --tags "@throw_scenariohook" -- cucumber_cpp/acceptance_test/features assert_failure - assert_output --partial "FAILED Before" + assert_output --partial "✘ Before" } @test "Test error program hook results in error and skipped steps" { diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index 8c360f06..ea67fdca 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -19,6 +19,7 @@ #include "cucumber_cpp/library/util/ToLower.hpp" #include "fmt/ostream.h" #include "fmt/ranges.h" +#include "nlohmann/json_fwd.hpp" #include #include #include @@ -26,6 +27,7 @@ #include #include #include +#include namespace cucumber_cpp::library::formatter { @@ -162,6 +164,12 @@ namespace cucumber_cpp::library::formatter } } + SummaryFormatter::Options::Options(const nlohmann::json& formatOptions) + : useStatusIcon{ formatOptions.value("use_status_icon", true) } + , theme{ helper::CreateTheme(formatOptions.value("theme", std::string_view{ "cucumber" })) } + { + } + void SummaryFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) { if (envelope.test_run_finished) @@ -205,10 +213,10 @@ namespace cucumber_cpp::library::formatter } } - HandleTestCaseStartedList(outputStream, query, "Warnings", warningTestStepResults, useStatusIcon, theme); - HandleTestCaseStartedList(outputStream, query, "Failures", failedTestStepResults, useStatusIcon, theme); - HandleSummary(outputStream, "scenarios", scenarioCounts, theme); - HandleSummary(outputStream, "steps", stepCounts, theme); + HandleTestCaseStartedList(outputStream, query, "Warnings", warningTestStepResults, options.useStatusIcon, options.theme); + HandleTestCaseStartedList(outputStream, query, "Failures", failedTestStepResults, options.useStatusIcon, options.theme); + HandleSummary(outputStream, "scenarios", scenarioCounts, options.theme); + HandleSummary(outputStream, "steps", stepCounts, options.theme); fmt::println(outputStream, "{:%Mm %S}s (executing steps: {:%Mm %S}s)", util::DurationToMilliseconds(testRunDuration), util::DurationToMilliseconds(totalStepDuration)); } diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.hpp b/cucumber_cpp/library/formatter/SummaryFormatter.hpp index b2c80899..7d85bebb 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.hpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.hpp @@ -5,9 +5,8 @@ #include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" #include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include "nlohmann/json_fwd.hpp" #include -#include -#include namespace cucumber_cpp::library::formatter { @@ -19,12 +18,18 @@ namespace cucumber_cpp::library::formatter constexpr static auto name = "summary"; private: + struct Options + { + explicit Options(const nlohmann::json& formatOptions); + + const bool useStatusIcon; + const helper::Theme theme; + }; + void OnEnvelope(const cucumber::messages::envelope& envelope) override; void LogSummary(const cucumber::messages::duration& testRunDuration); - const helper::Theme theme = helper::CreateCucumberTheme(); - const bool useStatusIcon = true; - const std::size_t scenarioIndent = 0; + Options options{ formatOptions.contains(name) ? formatOptions.at(name) : nlohmann::json::object() }; }; } From b3f8d36737cf13b59b47233da60c45cbd67f4e97 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 22 Jan 2026 15:49:23 +0000 Subject: [PATCH 169/196] refactor: simplify paths storage type and clean up unused includes --- cucumber_cpp/library/Application.cpp | 4 +--- cucumber_cpp/library/Application.hpp | 2 +- cucumber_cpp/library/formatter/helper/PrintMessages.cpp | 1 - 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 1e8acf08..1caad35d 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -27,11 +27,9 @@ #include #include #include -#include #include #include #include -#include #include namespace cucumber_cpp::library @@ -122,7 +120,7 @@ namespace cucumber_cpp::library CLI::deprecate_option(cli.add_option("--tag", options.tags, "Cucumber tag expression"), "-t,--tags"); cli.add_option("-t,--tags", options.tags, "Cucumber tag expression"); - CLI::deprecate_option(cli.add_option("-f,--feature", options.paths, "Paths to where your feature files are")->check(CLI::ExistingPath), "paths"); + CLI::deprecate_option(cli.add_option("-f,--feature", options.paths, "Paths to where your feature files are"), "paths"); cli.add_option("paths", options.paths, "Paths to where your feature files are, defaults to \"./features\"")->default_val(options.paths); ProgramContext().InsertRef(options); diff --git a/cucumber_cpp/library/Application.hpp b/cucumber_cpp/library/Application.hpp index 08836555..eba0ca84 100644 --- a/cucumber_cpp/library/Application.hpp +++ b/cucumber_cpp/library/Application.hpp @@ -31,7 +31,7 @@ namespace cucumber_cpp::library { bool dumpConfig{ false }; - std::set> paths{ { std::filesystem::path(".") / "features" } }; + std::set> paths{ { (std::filesystem::path(".") / "features").string() } }; bool dryRun{ false }; bool failFast{ false }; diff --git a/cucumber_cpp/library/formatter/helper/PrintMessages.cpp b/cucumber_cpp/library/formatter/helper/PrintMessages.cpp index 84b92ae0..fba75dcf 100644 --- a/cucumber_cpp/library/formatter/helper/PrintMessages.cpp +++ b/cucumber_cpp/library/formatter/helper/PrintMessages.cpp @@ -18,7 +18,6 @@ #include "fmt/ostream.h" #include "fmt/ranges.h" #include -#include #include #include #include From c7ff960f26e686f083aca7d58ccf184527b43b74 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Thu, 22 Jan 2026 16:14:06 +0000 Subject: [PATCH 170/196] feat: update FormatStepText to include test_step_result_status for improved formatting --- .../library/formatter/helper/FormatMessages.cpp | 12 ++++++------ .../library/formatter/helper/FormatMessages.hpp | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cucumber_cpp/library/formatter/helper/FormatMessages.cpp b/cucumber_cpp/library/formatter/helper/FormatMessages.cpp index d5a797c5..bf1d3f9a 100644 --- a/cucumber_cpp/library/formatter/helper/FormatMessages.cpp +++ b/cucumber_cpp/library/formatter/helper/FormatMessages.cpp @@ -101,7 +101,7 @@ namespace cucumber_cpp::library::formatter::helper return builder.Build(theme.location); } - std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const Theme& theme) + std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, cucumber::messages::test_step_result_status status, const Theme& theme) { TextBuilder builder{}; const auto& stepMatchArgumentsLists = testStep.step_match_arguments_lists; @@ -119,18 +119,18 @@ namespace cucumber_cpp::library::formatter::helper { const auto text = pickleStep.text.substr(currentIndex, group.start.value() - currentIndex); currentIndex = group.start.value() + group.value->size(); - builder.Append(text, theme.step.text) - .Append(group.value.value(), theme.step.argument); + builder.Append(text, theme.step.text.value_or(fmt::text_style{}) | theme.status.All(status)) + .Append(group.value.value(), theme.step.argument.value_or(fmt::text_style{}) | theme.status.All(status)); } } if (currentIndex != pickleStep.text.size()) { const auto remainingText = pickleStep.text.substr(currentIndex); - builder.Append(remainingText, theme.step.text); + builder.Append(remainingText, theme.step.text.value_or(fmt::text_style{}) | theme.status.All(status)); } } else - builder.Append(pickleStep.text, theme.step.text); + builder.Append(pickleStep.text, theme.step.text.value_or(fmt::text_style{}) | theme.status.All(status)); return builder.Build(); } @@ -218,7 +218,7 @@ namespace cucumber_cpp::library::formatter::helper return builder.Append(TextBuilder{} .Append(step.keyword, theme.step.keyword) - .Append(FormatStepText(testStep, pickleStep, theme), theme.status.All(status)) + .Append(FormatStepText(testStep, pickleStep, status, theme), theme.status.All(status)) .Build(theme.status.All(status))) .Build(); } diff --git a/cucumber_cpp/library/formatter/helper/FormatMessages.hpp b/cucumber_cpp/library/formatter/helper/FormatMessages.hpp index 641ba347..8775a2fa 100644 --- a/cucumber_cpp/library/formatter/helper/FormatMessages.hpp +++ b/cucumber_cpp/library/formatter/helper/FormatMessages.hpp @@ -29,7 +29,7 @@ namespace cucumber_cpp::library::formatter::helper std::string FormatPickleTitle(const cucumber::messages::pickle& pickle, const cucumber::messages::scenario& scenario, const Theme& theme); std::string FormatPickleAttemptTitle(const cucumber::messages::pickle& pickle, std::size_t attempt, bool retry, const cucumber::messages::scenario& scenario, const Theme& theme); std::string FormatPickleLocation(const cucumber::messages::pickle& pickle, const std::optional& location, const Theme& theme); - std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, const Theme& theme); + std::string FormatStepText(const cucumber::messages::test_step& testStep, const cucumber::messages::pickle_step& pickleStep, cucumber::messages::test_step_result_status status, const Theme& theme); std::string FormatCodeLocation(const cucumber::messages::source_reference& sourceReference, const Theme& theme); std::string FormatCodeLocation(const cucumber::messages::step_definition* stepDefinition, const Theme& theme); std::string FormatFeatureTitle(const cucumber::messages::feature& feature, const Theme& theme); From 7ed5f332e50c5a0d7cbaa7c8b57e4d2e1cc3d4fe Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 00:27:18 +0000 Subject: [PATCH 171/196] feat: add UsageFormatter and related query enhancements for improved usage reporting --- cucumber_cpp/library/api/Formatters.cpp | 2 + cucumber_cpp/library/formatter/CMakeLists.txt | 2 + .../library/formatter/UsageFormatter.cpp | 155 ++++++++++++++++++ .../library/formatter/UsageFormatter.hpp | 21 +++ cucumber_cpp/library/query/Query.cpp | 17 ++ cucumber_cpp/library/query/Query.hpp | 11 +- cucumber_cpp/library/util/Duration.cpp | 7 + cucumber_cpp/library/util/Duration.hpp | 2 + 8 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 cucumber_cpp/library/formatter/UsageFormatter.cpp create mode 100644 cucumber_cpp/library/formatter/UsageFormatter.hpp diff --git a/cucumber_cpp/library/api/Formatters.cpp b/cucumber_cpp/library/api/Formatters.cpp index cb9b5355..cb8bd025 100644 --- a/cucumber_cpp/library/api/Formatters.cpp +++ b/cucumber_cpp/library/api/Formatters.cpp @@ -4,6 +4,7 @@ #include "cucumber_cpp/library/formatter/MessageFormatter.hpp" #include "cucumber_cpp/library/formatter/PrettyFormatter.hpp" #include "cucumber_cpp/library/formatter/SummaryFormatter.hpp" +#include "cucumber_cpp/library/formatter/UsageFormatter.hpp" #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" #include "nlohmann/json_fwd.hpp" @@ -35,6 +36,7 @@ namespace cucumber_cpp::library::api RegisterFormatter(); RegisterFormatter(); RegisterFormatter(); + RegisterFormatter(); } std::set> Formatters::GetAvailableFormatterNames() const diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index ea60ba1f..3d8fe4aa 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -11,6 +11,8 @@ target_sources(cucumber_cpp.library.formatter PRIVATE PrettyFormatter.hpp SummaryFormatter.cpp SummaryFormatter.hpp + UsageFormatter.cpp + UsageFormatter.hpp ) target_include_directories(cucumber_cpp.library.formatter PUBLIC diff --git a/cucumber_cpp/library/formatter/UsageFormatter.cpp b/cucumber_cpp/library/formatter/UsageFormatter.cpp new file mode 100644 index 00000000..b45db592 --- /dev/null +++ b/cucumber_cpp/library/formatter/UsageFormatter.cpp @@ -0,0 +1,155 @@ +#include "cucumber_cpp/library/formatter/UsageFormatter.hpp" +#include "cucumber/messages/duration.hpp" +#include "cucumber/messages/envelope.hpp" +#include "cucumber/messages/location.hpp" +#include "cucumber/messages/step_definition.hpp" +#include "cucumber/messages/step_definition_pattern_type.hpp" +#include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/query/Query.hpp" +#include "cucumber_cpp/library/util/Duration.hpp" +#include "fmt/base.h" +#include "fmt/format.h" +#include "fmt/ostream.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace cucumber_cpp::library::formatter +{ + namespace + { + struct UsageMatch + { + cucumber::messages::duration duration; + std::size_t line; + std::string text; + std::string uri; + }; + + struct Usage + { + std::size_t line; + std::list matches; + std::string pattern; + std::string patternType; + std::string uri; + }; + + std::map> BuildEmptyMapping(const query::Query& query) + { + std::map> mapping; + + for (const auto& [id, stepDefinition] : query.StepDefinitions()) + { + mapping[id] = Usage{ + .line = stepDefinition.source_reference.location.value_or(cucumber::messages::location{}).line, + .matches = {}, + .pattern = stepDefinition.pattern.source, + .patternType = std::string{ cucumber::messages::to_string(stepDefinition.pattern.type) }, + .uri = stepDefinition.source_reference.uri.value_or("") + }; + } + + return mapping; + } + + std::map> BuildMapping(const query::Query& query) + { + auto mapping = BuildEmptyMapping(query); + + for (const auto& [testCaseStartedId, testCaseFinished] : query.TestCaseFinishedByTestCaseStartedId()) + { + const auto& testCaseStarted = query.FindTestCaseStartedById(testCaseStartedId); + const auto& testCase = query.FindTestCaseBy(testCaseStarted); + const auto testStepFinishedAndTestStep = query.FindTestStepFinishedAndTestStepBy(testCaseStarted); + + for (const auto [testStepFinished, testStep] : testStepFinishedAndTestStep) + { + if (testStep->step_definition_ids->size() != 1) + continue; + + const auto& pickleStep = query.FindPickleStepById(testStep->pickle_step_id.value()); + const auto& pickle = query.FindPickleById(testCase.pickle_id); + const auto& step = query.FindStepBy(pickleStep); + const auto& stepDefinitionId = testStep->step_definition_ids->front(); + const auto& lineage = query.FindLineageByPickle(pickle); + + mapping.at(stepDefinitionId).matches.emplace_back(query.FindTestStepDurationByTestStepId(testStepFinished->test_step_id), step.location.line, pickleStep.text, lineage.gherkinDocument->uri.value_or("")); + } + } + + return mapping; + } + + void BuildResult(const query::Query& query) + {} + + std::map> GetUsage(const query::Query& query) + { + return BuildMapping(query); + } + } + + void UsageFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) + { + if (envelope.test_run_finished.has_value()) + { + const auto& mapping = GetUsage(query); + + std::size_t patternWidth = std::string("Pattern / Text").size(); + std::size_t durationWidth = std::string("Duration").size(); + std::size_t locationWidth = std::string("Location").size(); + + for (const auto& usage : mapping | std::views::values) + { + patternWidth = std::max(patternWidth, usage.pattern.size()); + cucumber::messages::duration totalDuration{}; + locationWidth = std::max(locationWidth, fmt::format("{}:{}", usage.uri, usage.line).size()); + + for (const auto& match : usage.matches) + { + patternWidth = std::max(patternWidth, match.text.size() + 2); + totalDuration += match.duration; + locationWidth = std::max(locationWidth, fmt::format("{}:{}", match.uri, match.line).size()); + } + + const auto meanDuration = usage.matches.empty() ? std::chrono::milliseconds{ 0 } : std::chrono::milliseconds{ util::DurationToMilliseconds(totalDuration) / usage.matches.size() }; + durationWidth = std::max(durationWidth, fmt::format("{}", meanDuration).size()); + } + + fmt::println(outputStream, "┌─{:─<{}}─┬─{:─<{}}─┬─{:─<{}}─┐", "", patternWidth, "", durationWidth, "", locationWidth); + fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", "Pattern / Text", patternWidth, "Duration", durationWidth, "Location", locationWidth); + + for (const auto& usage : mapping | std::views::values) + { + auto durations = usage.matches | std::views::transform([](const UsageMatch& match) -> cucumber::messages::duration + { + return match.duration; + }); + const auto totalDuration = std::accumulate(durations.begin(), durations.end(), cucumber::messages::duration{}, [](const auto& lhs, const auto& rhs) + { + return lhs + rhs; + }); + + const auto meanDuration = usage.matches.empty() ? std::chrono::milliseconds{ 0 } : std::chrono::milliseconds{ util::DurationToMilliseconds(totalDuration) / usage.matches.size() }; + + fmt::println(outputStream, "├─{:─<{}}─┼─{:─<{}}─┼─{:─<{}}─┤", "", patternWidth, "", durationWidth, "", locationWidth); + if (usage.matches.empty()) + fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, "UNUSED", durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); + else + fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, meanDuration, durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); + + for (const auto& match : usage.matches) + fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", match.text, patternWidth - 2, util::DurationToMilliseconds(match.duration), durationWidth, fmt::format("{}:{}", match.uri, match.line), locationWidth); + } + fmt::println(outputStream, "└─{:─<{}}─┴─{:─<{}}─┴─{:─<{}}─┘", "", patternWidth, "", durationWidth, "", locationWidth); + } + } +} diff --git a/cucumber_cpp/library/formatter/UsageFormatter.hpp b/cucumber_cpp/library/formatter/UsageFormatter.hpp new file mode 100644 index 00000000..7d920568 --- /dev/null +++ b/cucumber_cpp/library/formatter/UsageFormatter.hpp @@ -0,0 +1,21 @@ +#ifndef FORMATTER_USAGE_FORMATTER_HPP +#define FORMATTER_USAGE_FORMATTER_HPP + +#include "cucumber/messages/envelope.hpp" +#include "cucumber_cpp/library/formatter/Formatter.hpp" + +namespace cucumber_cpp::library::formatter +{ + struct UsageFormatter + : Formatter + { + using Formatter::Formatter; + + constexpr static auto name = "usage"; + + private: + void OnEnvelope(const cucumber::messages::envelope& envelope) override; + }; +} + +#endif diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index 6959fffa..64bf36ef 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -217,6 +217,16 @@ namespace cucumber_cpp::library::query return std::nullopt; } + const cucumber::messages::test_case_started& Query::FindTestCaseStartedById(const std::string& id) const + { + return testCaseStartedById.at(id); + } + + const std::map>& Query::StepDefinitions() const + { + return stepDefinitionById; + } + const std::map>& Query::TestCaseStarted() const { return testCaseStartedById; @@ -321,6 +331,11 @@ namespace cucumber_cpp::library::query return testCaseFinished.timestamp - testCaseStarted.timestamp; } + cucumber::messages::duration Query::FindTestStepDurationByTestStepId(const std::string& testStepId) const + { + return testStepFinishedByTestStepId.at(testStepId)->timestamp - testStepStartedByTestStepId.at(testStepId)->timestamp; + } + void Query::operator+=(const cucumber::messages::envelope& envelope) { if (envelope.meta) @@ -449,6 +464,7 @@ namespace cucumber_cpp::library::query void Query::operator+=(const cucumber::messages::test_step_started& testStepStarted) { testStepStartedByTestCaseStartedId[testStepStarted.test_case_started_id].push_back(testStepStarted); + testStepStartedByTestStepId[testStepStarted.test_step_id] = &testStepStartedByTestCaseStartedId[testStepStarted.test_case_started_id].back(); } void Query::operator+=(const cucumber::messages::attachment& attachment) @@ -465,6 +481,7 @@ namespace cucumber_cpp::library::query auto* testStepResultPtr = &testStepResults.emplace_front(testStepFinished.test_step_result); testStepFinishedByTestCaseStartedId[testStepFinished.test_case_started_id].push_back(testStepFinished); + testStepFinishedByTestStepId[testStepFinished.test_step_id] = &testStepFinishedByTestCaseStartedId[testStepFinished.test_case_started_id].back(); const auto& pickleId = pickleIdByTestStepId.at(testStepFinished.test_step_id); testStepResultByPickleId[pickleId].push_back(testStepResultPtr); diff --git a/cucumber_cpp/library/query/Query.hpp b/cucumber_cpp/library/query/Query.hpp index 7e074c39..e5acf6e0 100644 --- a/cucumber_cpp/library/query/Query.hpp +++ b/cucumber_cpp/library/query/Query.hpp @@ -149,6 +149,9 @@ namespace cucumber_cpp::library::query std::optional FindLocationOf(const cucumber::messages::pickle& pickle) const; + const cucumber::messages::test_case_started& FindTestCaseStartedById(const std::string& id) const; + + const std::map>& StepDefinitions() const; const std::map>& TestCaseStarted() const; const std::map>& TestCaseFinishedByTestCaseStartedId() const; @@ -168,9 +171,10 @@ namespace cucumber_cpp::library::query cucumber::messages::duration FindTestCaseDurationBy(const cucumber::messages::test_case_finished& testCaseFinished) const; cucumber::messages::duration FindTestCaseDurationBy(const cucumber::messages::test_case_started& testCaseStarted, const cucumber::messages::test_case_finished& testCaseFinished) const; + cucumber::messages::duration FindTestStepDurationByTestStepId(const std::string& testStepId) const; + private: - void - operator+=(const cucumber::messages::envelope& envelope); + void operator+=(const cucumber::messages::envelope& envelope); void operator+=(const cucumber::messages::gherkin_document& gherkinDocument); void operator+=(const cucumber::messages::pickle& pickle); @@ -237,6 +241,9 @@ namespace cucumber_cpp::library::query std::map, std::less<>> testStepStartedByTestCaseStartedId; std::map, std::less<>> testStepFinishedByTestCaseStartedId; + std::map> testStepStartedByTestStepId; + std::map> testStepFinishedByTestStepId; + std::map, std::less<>> suggestionsByPickleStepId; std::list undefinedParameterTypes; diff --git a/cucumber_cpp/library/util/Duration.cpp b/cucumber_cpp/library/util/Duration.cpp index 65a5b3a0..73ffd897 100644 --- a/cucumber_cpp/library/util/Duration.cpp +++ b/cucumber_cpp/library/util/Duration.cpp @@ -64,4 +64,11 @@ namespace cucumber_cpp::library::util return lhs; } + + cucumber::messages::duration operator+(const cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs) + { + cucumber::messages::duration result = lhs; + + return result += rhs; + } } diff --git a/cucumber_cpp/library/util/Duration.hpp b/cucumber_cpp/library/util/Duration.hpp index 1f109148..da30ba3c 100644 --- a/cucumber_cpp/library/util/Duration.hpp +++ b/cucumber_cpp/library/util/Duration.hpp @@ -10,6 +10,7 @@ namespace cucumber_cpp::library::util std::chrono::milliseconds DurationToMilliseconds(const cucumber::messages::duration& duration); cucumber::messages::duration& operator+=(cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs); + cucumber::messages::duration operator+(const cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs); struct Stopwatch { @@ -38,6 +39,7 @@ namespace cucumber_cpp::library::util namespace cucumber::messages { using cucumber_cpp::library::util::operator+=; + using cucumber_cpp::library::util::operator+; }; #endif From a334199930b0808cfd3c0e78030a6e5c1cc1818f Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 11:03:13 +0000 Subject: [PATCH 172/196] feat: enhance UsageFormatter with dry run support and mean duration calculation --- .vscode/launch.json | 30 ++++ .../library/formatter/UsageFormatter.cpp | 144 +++++++++++++----- .../library/formatter/UsageFormatter.hpp | 20 +++ .../library/formatter/helper/Theme.cpp | 13 ++ .../library/formatter/helper/Theme.hpp | 18 +++ .../library/support/SupportCodeLibrary.cpp | 10 +- cucumber_cpp/library/util/Duration.cpp | 12 +- cucumber_cpp/library/util/Duration.hpp | 1 + 8 files changed, 207 insertions(+), 41 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 66890a0b..a043d84a 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -72,6 +72,36 @@ } ] }, + { + "name": "(gdb) Launch dry run", + "type": "cppdbg", + "request": "launch", + "program": "${command:cmake.launchTargetPath}", + "args": [ + "--format", + "usage", + "--dry-run", + "--", + "${input:features}" + ], + "stopAtEntry": false, + "cwd": "${workspaceFolder}", + "environment": [ + { + "name": "ASAN_OPTIONS", + "value": "detect_leaks=0" + } + ], + "externalConsole": false, + "MIMode": "gdb", + "setupCommands": [ + { + "description": "Enable pretty-printing for gdb", + "text": "-enable-pretty-printing", + "ignoreFailures": true + } + ] + }, { "name": "(gdb) Launch - no tags", "type": "cppdbg", diff --git a/cucumber_cpp/library/formatter/UsageFormatter.cpp b/cucumber_cpp/library/formatter/UsageFormatter.cpp index b45db592..944c55cc 100644 --- a/cucumber_cpp/library/formatter/UsageFormatter.cpp +++ b/cucumber_cpp/library/formatter/UsageFormatter.cpp @@ -1,33 +1,42 @@ #include "cucumber_cpp/library/formatter/UsageFormatter.hpp" -#include "cucumber/messages/duration.hpp" #include "cucumber/messages/envelope.hpp" #include "cucumber/messages/location.hpp" -#include "cucumber/messages/step_definition.hpp" #include "cucumber/messages/step_definition_pattern_type.hpp" #include "cucumber/messages/test_step_result_status.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" #include "cucumber_cpp/library/query/Query.hpp" #include "cucumber_cpp/library/util/Duration.hpp" #include "fmt/base.h" #include "fmt/format.h" #include "fmt/ostream.h" +#include "nlohmann/json_fwd.hpp" #include #include #include #include #include #include -#include +#include #include #include +#include #include namespace cucumber_cpp::library::formatter { namespace { + bool HasExecuted(cucumber::messages::test_step_result_status status) + { + return status == cucumber::messages::test_step_result_status::UNKNOWN || + status == cucumber::messages::test_step_result_status::PASSED || + status == cucumber::messages::test_step_result_status::PENDING || + status == cucumber::messages::test_step_result_status::FAILED; + } + struct UsageMatch { - cucumber::messages::duration duration; + std::optional duration; std::size_t line; std::string text; std::string uri; @@ -40,6 +49,7 @@ namespace cucumber_cpp::library::formatter std::string pattern; std::string patternType; std::string uri; + std::optional meanDuration; }; std::map> BuildEmptyMapping(const query::Query& query) @@ -53,7 +63,8 @@ namespace cucumber_cpp::library::formatter .matches = {}, .pattern = stepDefinition.pattern.source, .patternType = std::string{ cucumber::messages::to_string(stepDefinition.pattern.type) }, - .uri = stepDefinition.source_reference.uri.value_or("") + .uri = stepDefinition.source_reference.uri.value_or(""), + .meanDuration = {}, }; } @@ -81,75 +92,134 @@ namespace cucumber_cpp::library::formatter const auto& stepDefinitionId = testStep->step_definition_ids->front(); const auto& lineage = query.FindLineageByPickle(pickle); - mapping.at(stepDefinitionId).matches.emplace_back(query.FindTestStepDurationByTestStepId(testStepFinished->test_step_id), step.location.line, pickleStep.text, lineage.gherkinDocument->uri.value_or("")); + std::optional duration{}; + if (HasExecuted(testStepFinished->test_step_result.status)) + duration = util::DurationToNanoSeconds(query.FindTestStepDurationByTestStepId(testStepFinished->test_step_id)); + + mapping.at(stepDefinitionId).matches.emplace_back(duration, step.location.line, pickleStep.text, lineage.gherkinDocument->uri.value_or("")); + } + } + + for (auto& usage : mapping | std::views::values) + { + + if (usage.matches.empty()) + usage.meanDuration = std::chrono::nanoseconds{ -1 }; + else + { + std::chrono::nanoseconds totalDuration{}; + std::size_t countDuration{}; + + for (const auto& match : usage.matches) + { + if (match.duration.has_value()) + { + ++countDuration; + totalDuration += match.duration.value_or(std::chrono::nanoseconds{}); + } + } + + if (countDuration != 0) + usage.meanDuration = totalDuration / countDuration; } } return mapping; } - void BuildResult(const query::Query& query) - {} - - std::map> GetUsage(const query::Query& query) + std::list GetUsage(const query::Query& query, bool unusedOnly) { - return BuildMapping(query); + const auto& mapping = BuildMapping(query); + auto mapValues = mapping | std::views::values | (std::views::filter([unusedOnly](const Usage& usage) + { + return !unusedOnly || usage.matches.empty(); + })); + + std::list usageList{ mapValues.begin(), mapValues.end() }; + + usageList.sort([](const Usage& lhs, const Usage& rhs) + { + if (lhs.matches.empty() && !rhs.matches.empty()) + return false; + if (!lhs.matches.empty() && rhs.matches.empty()) + return true; + + if (lhs.meanDuration == rhs.meanDuration) + return std::make_tuple(lhs.uri, lhs.line) < std::make_tuple(rhs.uri, rhs.line); + + return lhs.meanDuration > rhs.meanDuration; + }); + + for (auto& usage : usageList) + usage.matches.sort([](const UsageMatch& lhs, const UsageMatch& rhs) + { + if (lhs.duration == rhs.duration) + return std::make_tuple(lhs.uri, lhs.line, lhs.text) < std::make_tuple(rhs.uri, rhs.line, rhs.text); + + return lhs.duration > rhs.duration; + }); + + return usageList; } } + UsageFormatter::Options::Options(const nlohmann::json& json) + : unusedOnly{ json.value("unused_only", false) } + , theme{ helper::CreateTheme(json.value("theme", "cucumber")) } + { + } + void UsageFormatter::OnEnvelope(const cucumber::messages::envelope& envelope) { if (envelope.test_run_finished.has_value()) { - const auto& mapping = GetUsage(query); + const auto& mapping = GetUsage(query, options.unusedOnly); - std::size_t patternWidth = std::string("Pattern / Text").size(); - std::size_t durationWidth = std::string("Duration").size(); - std::size_t locationWidth = std::string("Location").size(); + auto patternWidth = std::string("Pattern / Text").size(); + auto durationWidth = std::string("Duration").size(); + auto locationWidth = std::string("Location").size(); - for (const auto& usage : mapping | std::views::values) + for (const auto& usage : mapping) { patternWidth = std::max(patternWidth, usage.pattern.size()); - cucumber::messages::duration totalDuration{}; locationWidth = std::max(locationWidth, fmt::format("{}:{}", usage.uri, usage.line).size()); + if (usage.meanDuration.has_value()) + durationWidth = std::max(durationWidth, fmt::format("{}", std::chrono::duration_cast(usage.meanDuration.value())).size()); + else + durationWidth = std::max(durationWidth, std::string("-").size()); + for (const auto& match : usage.matches) { patternWidth = std::max(patternWidth, match.text.size() + 2); - totalDuration += match.duration; locationWidth = std::max(locationWidth, fmt::format("{}:{}", match.uri, match.line).size()); } - - const auto meanDuration = usage.matches.empty() ? std::chrono::milliseconds{ 0 } : std::chrono::milliseconds{ util::DurationToMilliseconds(totalDuration) / usage.matches.size() }; - durationWidth = std::max(durationWidth, fmt::format("{}", meanDuration).size()); } - fmt::println(outputStream, "┌─{:─<{}}─┬─{:─<{}}─┬─{:─<{}}─┐", "", patternWidth, "", durationWidth, "", locationWidth); + fmt::println(outputStream, fmt::runtime(topRow), "", patternWidth, "", durationWidth, "", locationWidth); fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", "Pattern / Text", patternWidth, "Duration", durationWidth, "Location", locationWidth); - for (const auto& usage : mapping | std::views::values) - { - auto durations = usage.matches | std::views::transform([](const UsageMatch& match) -> cucumber::messages::duration - { - return match.duration; - }); - const auto totalDuration = std::accumulate(durations.begin(), durations.end(), cucumber::messages::duration{}, [](const auto& lhs, const auto& rhs) - { - return lhs + rhs; - }); + const auto horizontalDivider = fmt::format(fmt::runtime(middleRow), "", patternWidth, "", durationWidth, "", locationWidth); - const auto meanDuration = usage.matches.empty() ? std::chrono::milliseconds{ 0 } : std::chrono::milliseconds{ util::DurationToMilliseconds(totalDuration) / usage.matches.size() }; + for (const auto& usage : mapping) + { + fmt::println(outputStream, "{}", horizontalDivider); - fmt::println(outputStream, "├─{:─<{}}─┼─{:─<{}}─┼─{:─<{}}─┤", "", patternWidth, "", durationWidth, "", locationWidth); if (usage.matches.empty()) fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, "UNUSED", durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); + else if (usage.meanDuration.has_value()) + fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, std::chrono::duration_cast(usage.meanDuration.value()), durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); else - fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, meanDuration, durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); + fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, "-", durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); for (const auto& match : usage.matches) - fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", match.text, patternWidth - 2, util::DurationToMilliseconds(match.duration), durationWidth, fmt::format("{}:{}", match.uri, match.line), locationWidth); + if (match.duration.has_value()) + fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", match.text, patternWidth - 2, std::chrono::duration_cast(match.duration.value()), durationWidth, fmt::format("{}:{}", match.uri, match.line), locationWidth); + else + fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", match.text, patternWidth - 2, "-", durationWidth, fmt::format("{}:{}", match.uri, match.line), locationWidth); } - fmt::println(outputStream, "└─{:─<{}}─┴─{:─<{}}─┴─{:─<{}}─┘", "", patternWidth, "", durationWidth, "", locationWidth); + + fmt::println(outputStream, fmt::runtime(bottomRow), "", patternWidth, "", durationWidth, "", locationWidth); } } } diff --git a/cucumber_cpp/library/formatter/UsageFormatter.hpp b/cucumber_cpp/library/formatter/UsageFormatter.hpp index 7d920568..90315851 100644 --- a/cucumber_cpp/library/formatter/UsageFormatter.hpp +++ b/cucumber_cpp/library/formatter/UsageFormatter.hpp @@ -3,6 +3,11 @@ #include "cucumber/messages/envelope.hpp" #include "cucumber_cpp/library/formatter/Formatter.hpp" +#include "cucumber_cpp/library/formatter/helper/Theme.hpp" +#include "fmt/base.h" +#include "fmt/format.h" +#include "nlohmann/json_fwd.hpp" +#include namespace cucumber_cpp::library::formatter { @@ -14,7 +19,22 @@ namespace cucumber_cpp::library::formatter constexpr static auto name = "usage"; private: + struct Options + { + explicit Options(const nlohmann::json& json); + + bool unusedOnly; + helper::Theme theme; + }; + void OnEnvelope(const cucumber::messages::envelope& envelope) override; + + Options options{ formatOptions.value(name, nlohmann::json::object()) }; + + const std::string rowFormat = "{0}{1}{{:{1}<{{}}}}{1}{2}{1}{{:{1}<{{}}}}{1}{2}{1}{{:{1}<{{}}}}{1}{3}"; + const std::string topRow = fmt::format(fmt::runtime(rowFormat), options.theme.table.cornerTopLeft, options.theme.table.dash, options.theme.table.edgeTopT, options.theme.table.cornerTopRight); + const std::string middleRow = fmt::format(fmt::runtime(rowFormat), options.theme.table.edgeLeftT, options.theme.table.dash, options.theme.table.cross, options.theme.table.edgeRightT); + const std::string bottomRow = fmt::format(fmt::runtime(rowFormat), options.theme.table.cornerBottomLeft, options.theme.table.dash, options.theme.table.edgeBottomT, options.theme.table.cornerBottomRight); }; } diff --git a/cucumber_cpp/library/formatter/helper/Theme.cpp b/cucumber_cpp/library/formatter/helper/Theme.cpp index 8b5ed233..0021d621 100644 --- a/cucumber_cpp/library/formatter/helper/Theme.cpp +++ b/cucumber_cpp/library/formatter/helper/Theme.cpp @@ -80,6 +80,19 @@ namespace cucumber_cpp::library::formatter::helper .keyword = fmt::emphasis::bold, }, .symbol = { .bullet = "•" }, + .table{ + .cornerTopLeft{ "┌" }, + .cornerTopRight{ "┐" }, + .cornerBottomLeft{ "└" }, + .cornerBottomRight{ "┘" }, + .edgeTopT{ "┬" }, + .edgeBottomT{ "┴" }, + .edgeLeftT{ "├" }, + .edgeRightT{ "┤" }, + .dash{ "─" }, + .vertical{ "│" }, + .cross{ "┼" }, + }, }; return theme; diff --git a/cucumber_cpp/library/formatter/helper/Theme.hpp b/cucumber_cpp/library/formatter/helper/Theme.hpp index 167877d1..43bdd84b 100644 --- a/cucumber_cpp/library/formatter/helper/Theme.hpp +++ b/cucumber_cpp/library/formatter/helper/Theme.hpp @@ -95,6 +95,24 @@ namespace cucumber_cpp::library::formatter::helper { std::string bullet{ " " }; } symbol; + + struct + { + std::string cornerTopLeft{ "|" }; + std::string cornerTopRight{ "|" }; + std::string cornerBottomLeft{ "|" }; + std::string cornerBottomRight{ "|" }; + + std::string edgeTopT{ "|" }; + std::string edgeBottomT{ "|" }; + std::string edgeLeftT{ "|" }; + std::string edgeRightT{ "|" }; + + std::string dash{ "-" }; + std::string vertical{ "|" }; + + std::string cross{ "+" }; + } table; }; Theme CreateEmptyTheme(); diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index f8bf66e4..f0b2a15c 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -4,7 +4,11 @@ #include "cucumber_cpp/library/support/HookRegistry.hpp" #include "cucumber_cpp/library/support/StepRegistry.hpp" #include "cucumber_cpp/library/support/StepType.hpp" +#include "fmt/format.h" +#include "fmt/ranges.h" +#include "fmt/std.h" #include +#include #include #include #include @@ -63,10 +67,10 @@ namespace cucumber_cpp::library::support void PrintContents(std::string_view type, std::source_location sourceLocation, const std::map& registry) { #if defined(CCR_STANDALONE) - std::cout << std::format("Added ({}): {}:{}\n", type, sourceLocation.file_name(), sourceLocation.line()); - std::cout << "Registry contents:\n"; + fmt::println("Added ({}): {}:{}", type, std::filesystem::path{ sourceLocation.file_name() }, sourceLocation.line()); + fmt::println("Registry contents:"); for (const auto& [key, item] : registry) - std::cout << std::format(" {}:{}\n", key.file_name(), key.line()); + fmt::println(" {}:{}", std::filesystem::path{ key.file_name() }, key.line()); #endif } } diff --git a/cucumber_cpp/library/util/Duration.cpp b/cucumber_cpp/library/util/Duration.cpp index 73ffd897..8f0be59c 100644 --- a/cucumber_cpp/library/util/Duration.cpp +++ b/cucumber_cpp/library/util/Duration.cpp @@ -15,6 +15,11 @@ namespace cucumber_cpp::library::util std::chrono::duration_cast(nanos); } + std::chrono::nanoseconds ToNanoSeconds(std::chrono::seconds seconds, std::chrono::nanoseconds nanos) + { + return std::chrono::duration_cast(seconds) + nanos; + } + cucumber::messages::duration ToDuration(std::chrono::milliseconds millis) { return { @@ -52,7 +57,12 @@ namespace cucumber_cpp::library::util std::chrono::milliseconds DurationToMilliseconds(const cucumber::messages::duration& duration) { - return ToMillis(std::chrono::seconds(duration.seconds), std::chrono::nanoseconds(duration.nanos)); + return ToMillis(std::chrono::seconds{ duration.seconds }, std::chrono::nanoseconds{ duration.nanos }); + } + + std::chrono::nanoseconds DurationToNanoSeconds(const cucumber::messages::duration& duration) + { + return ToNanoSeconds(std::chrono::seconds{ duration.seconds }, std::chrono::nanoseconds{ duration.nanos }); } cucumber::messages::duration& operator+=(cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs) diff --git a/cucumber_cpp/library/util/Duration.hpp b/cucumber_cpp/library/util/Duration.hpp index da30ba3c..fb1741b0 100644 --- a/cucumber_cpp/library/util/Duration.hpp +++ b/cucumber_cpp/library/util/Duration.hpp @@ -9,6 +9,7 @@ namespace cucumber_cpp::library::util cucumber::messages::duration MillisecondsToDuration(std::chrono::milliseconds millis); std::chrono::milliseconds DurationToMilliseconds(const cucumber::messages::duration& duration); + std::chrono::nanoseconds DurationToNanoSeconds(const cucumber::messages::duration& duration); cucumber::messages::duration& operator+=(cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs); cucumber::messages::duration operator+(const cucumber::messages::duration& lhs, const cucumber::messages::duration& rhs); From fc244f688c7c024cc4999e775d000d1c36429cab Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 11:38:03 +0000 Subject: [PATCH 173/196] feat: improve scenario result counting and handle missing test step results --- cucumber_cpp/library/formatter/SummaryFormatter.cpp | 5 ++++- cucumber_cpp/library/query/Query.cpp | 3 +++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/formatter/SummaryFormatter.cpp b/cucumber_cpp/library/formatter/SummaryFormatter.cpp index ea67fdca..3b170254 100644 --- a/cucumber_cpp/library/formatter/SummaryFormatter.cpp +++ b/cucumber_cpp/library/formatter/SummaryFormatter.cpp @@ -199,7 +199,10 @@ namespace cucumber_cpp::library::formatter if (!testCaseFinished.will_be_retried) { - ++scenarioCounts[query.FindMostSevereTestStepResultBy(testCaseFinished).value()->status]; + if (const auto& testStepResultPtr = query.FindMostSevereTestStepResultBy(testCaseFinished); testStepResultPtr.has_value()) + ++scenarioCounts[testStepResultPtr.value()->status]; + else + ++scenarioCounts[cucumber::messages::test_step_result_status::PASSED]; const auto& testStepFinishedAndTestStep = query.FindTestStepFinishedAndTestStepBy(testCaseStarted); for (const auto& [testStepFinished, testStep] : testStepFinishedAndTestStep) diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index 64bf36ef..a1389028 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -298,6 +298,9 @@ namespace cucumber_cpp::library::query std::list> Query::FindTestStepFinishedAndTestStepBy(const cucumber::messages::test_case_started& testCaseStarted) const { + if (!testStepFinishedByTestCaseStartedId.contains(testCaseStarted.id)) + return {}; + const auto& testStepsFinished = testStepFinishedByTestCaseStartedId.at(testCaseStarted.id); auto view = testStepsFinished | std::views::transform([this](const auto& testStepFinished) { From 21a3b78611e2d03a6d87dca0b4e4c879887665ec Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 12:40:01 +0000 Subject: [PATCH 174/196] feat: add unused scenario and related test structure for usage reporting --- cucumber_cpp/acceptance_test/CMakeLists.txt | 8 ++++++++ .../features/test_scenarios_unused.feature | 4 ++++ .../acceptance_test/steps/CMakeLists.txt | 9 +++++++++ .../acceptance_test/steps/UsedUnused.cpp | 7 +++++++ cucumber_cpp/acceptance_test/test.bats | 20 +++++++++++++++++++ .../library/formatter/UsageFormatter.cpp | 12 +++++------ 6 files changed, 54 insertions(+), 6 deletions(-) create mode 100644 cucumber_cpp/acceptance_test/features/test_scenarios_unused.feature create mode 100644 cucumber_cpp/acceptance_test/steps/UsedUnused.cpp diff --git a/cucumber_cpp/acceptance_test/CMakeLists.txt b/cucumber_cpp/acceptance_test/CMakeLists.txt index eda7ed36..b04cd4c8 100644 --- a/cucumber_cpp/acceptance_test/CMakeLists.txt +++ b/cucumber_cpp/acceptance_test/CMakeLists.txt @@ -21,5 +21,13 @@ target_link_libraries(cucumber_cpp.acceptance_test.custom PRIVATE cucumber_cpp.acceptance_test.steps ) +add_executable(cucumber_cpp.acceptance_test.unused ${CCR_EXCLUDE_FROM_ALL}) + +target_link_libraries(cucumber_cpp.acceptance_test.unused PRIVATE + cucumber_cpp + cucumber_cpp.runner + cucumber_cpp.acceptance_test.usedunused +) + add_subdirectory(hooks) add_subdirectory(steps) diff --git a/cucumber_cpp/acceptance_test/features/test_scenarios_unused.feature b/cucumber_cpp/acceptance_test/features/test_scenarios_unused.feature new file mode 100644 index 00000000..c36fb3d9 --- /dev/null +++ b/cucumber_cpp/acceptance_test/features/test_scenarios_unused.feature @@ -0,0 +1,4 @@ +@unused +Feature: Simple feature file + Scenario: An unused scenario + Given this step is used diff --git a/cucumber_cpp/acceptance_test/steps/CMakeLists.txt b/cucumber_cpp/acceptance_test/steps/CMakeLists.txt index 25d017d4..046ef91c 100644 --- a/cucumber_cpp/acceptance_test/steps/CMakeLists.txt +++ b/cucumber_cpp/acceptance_test/steps/CMakeLists.txt @@ -9,3 +9,12 @@ target_sources(cucumber_cpp.acceptance_test.steps PRIVATE target_link_libraries(cucumber_cpp.acceptance_test.steps PRIVATE cucumber_cpp ) +add_library(cucumber_cpp.acceptance_test.usedunused OBJECT ${CCR_EXCLUDE_FROM_ALL}) + +target_sources(cucumber_cpp.acceptance_test.usedunused PRIVATE + UsedUnused.cpp +) + +target_link_libraries(cucumber_cpp.acceptance_test.usedunused PRIVATE + cucumber_cpp +) diff --git a/cucumber_cpp/acceptance_test/steps/UsedUnused.cpp b/cucumber_cpp/acceptance_test/steps/UsedUnused.cpp new file mode 100644 index 00000000..691b27c8 --- /dev/null +++ b/cucumber_cpp/acceptance_test/steps/UsedUnused.cpp @@ -0,0 +1,7 @@ +#include "cucumber_cpp/CucumberCpp.hpp" + +STEP("This step is unused") +{} + +STEP("this step is used") +{} diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index 6a9c772e..a1481f3a 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -202,3 +202,23 @@ teardown() { run $acceptance_test --format summary pretty --tags "@nested_steps" -- cucumber_cpp/acceptance_test/features assert_success } + +@test "Test usage formatter" { + run $acceptance_test.unused --format usage --tags "@unused" -- cucumber_cpp/acceptance_test/features + assert_success + assert_output --partial "│ this step is used │ " + assert_output --partial "│ this step is used │ " + assert_output --partial "│ This step is unused │ UNUSED │" + + run $acceptance_test.unused --format usage --tags "@unused" --dry-run -- cucumber_cpp/acceptance_test/features + assert_success + assert_output --partial "│ this step is used │ - │" + assert_output --partial "│ this step is used │ - │" + assert_output --partial "│ This step is unused │ UNUSED │" + + run $acceptance_test.unused --format usage --tags "@unused" --dry-run --format-options "{ \"usage\" : {\"theme\": \"plain\"} }" -- cucumber_cpp/acceptance_test/features + assert_success + assert_output --partial "| this step is used | - |" + assert_output --partial "| this step is used | - |" + assert_output --partial "| This step is unused | UNUSED |" +} diff --git a/cucumber_cpp/library/formatter/UsageFormatter.cpp b/cucumber_cpp/library/formatter/UsageFormatter.cpp index 944c55cc..8b3b44a9 100644 --- a/cucumber_cpp/library/formatter/UsageFormatter.cpp +++ b/cucumber_cpp/library/formatter/UsageFormatter.cpp @@ -197,7 +197,7 @@ namespace cucumber_cpp::library::formatter } fmt::println(outputStream, fmt::runtime(topRow), "", patternWidth, "", durationWidth, "", locationWidth); - fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", "Pattern / Text", patternWidth, "Duration", durationWidth, "Location", locationWidth); + fmt::println(outputStream, "{0} {1:<{2}} {0} {3:<{4}} {0} {5:<{6}} {0}", options.theme.table.vertical, "Pattern / Text", patternWidth, "Duration", durationWidth, "Location", locationWidth); const auto horizontalDivider = fmt::format(fmt::runtime(middleRow), "", patternWidth, "", durationWidth, "", locationWidth); @@ -206,17 +206,17 @@ namespace cucumber_cpp::library::formatter fmt::println(outputStream, "{}", horizontalDivider); if (usage.matches.empty()) - fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, "UNUSED", durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); + fmt::println(outputStream, "{0} {1:<{2}} {0} {3:<{4}} {0} {5:<{6}} {0}", options.theme.table.vertical, usage.pattern, patternWidth, "UNUSED", durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); else if (usage.meanDuration.has_value()) - fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, std::chrono::duration_cast(usage.meanDuration.value()), durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); + fmt::println(outputStream, "{0} {1:<{2}} {0} {3:<{4}} {0} {5:<{6}} {0}", options.theme.table.vertical, usage.pattern, patternWidth, std::chrono::duration_cast(usage.meanDuration.value()), durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); else - fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", usage.pattern, patternWidth, "-", durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); + fmt::println(outputStream, "{0} {1:<{2}} {0} {3:<{4}} {0} {5:<{6}} {0}", options.theme.table.vertical, usage.pattern, patternWidth, "-", durationWidth, fmt::format("{}:{}", usage.uri, usage.line), locationWidth); for (const auto& match : usage.matches) if (match.duration.has_value()) - fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", match.text, patternWidth - 2, std::chrono::duration_cast(match.duration.value()), durationWidth, fmt::format("{}:{}", match.uri, match.line), locationWidth); + fmt::println(outputStream, "{0} {1:<{2}} {0} {3:<{4}} {0} {5:<{6}} {0}", options.theme.table.vertical, match.text, patternWidth - 2, std::chrono::duration_cast(match.duration.value()), durationWidth, fmt::format("{}:{}", match.uri, match.line), locationWidth); else - fmt::println(outputStream, "│ {:<{}} │ {:<{}} │ {:<{}} │", match.text, patternWidth - 2, "-", durationWidth, fmt::format("{}:{}", match.uri, match.line), locationWidth); + fmt::println(outputStream, "{0} {1:<{2}} {0} {3:<{4}} {0} {5:<{6}} {0}", options.theme.table.vertical, match.text, patternWidth - 2, "-", durationWidth, fmt::format("{}:{}", match.uri, match.line), locationWidth); } fmt::println(outputStream, fmt::runtime(bottomRow), "", patternWidth, "", durationWidth, "", locationWidth); From 63523cc8aacde522b6dc0cf8d9bd420c9503aae7 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 12:49:02 +0000 Subject: [PATCH 175/196] feat: remove redundant disk usage checks from static analysis workflow --- .github/workflows/static-analysis.yml | 32 --------------------------- 1 file changed, 32 deletions(-) diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml index eff60f9b..2022c33d 100644 --- a/.github/workflows/static-analysis.yml +++ b/.github/workflows/static-analysis.yml @@ -22,25 +22,16 @@ jobs: env: SONAR_SERVER_URL: "https://sonarcloud.io" steps: - - run: du -sh / || true - - run: du -sh . || true - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 with: fetch-depth: 0 # Disable shallow clone to enable blame information persist-credentials: false - - run: du -sh / || true - - run: du -sh . || true - - uses: hendrikmuhs/ccache-action@5ebbd400eff9e74630f759d94ddd7b6c26299639 # v1.2.20 with: key: ${{ github.job }} max-size: 2G - - run: du -sh / || true - - run: du -sh . || true - - name: Build for coverage uses: lukka/run-cmake@af1be47fd7c933593f687731bc6fdbee024d3ff4 # v10.8 with: @@ -51,51 +42,28 @@ jobs: env: GTEST_OUTPUT: "xml:${{ github.workspace }}/testresults/" - - run: du -sh / || true - - run: du -sh . || true - - name: Run acceptance tests run: | bats --formatter junit cucumber_cpp/acceptance_test/test.bats | tee test-report.xml - - run: du -sh / || true - - run: du -sh . || true - - name: Collect coverage run: | gcovr --sonarqube=coverage.xml --exclude-lines-by-pattern '.*assert\(.*\);|.*really_assert\(.*\);|.*std::abort();' --exclude-unreachable-branches --exclude-throw-branches -j "$(nproc)" --exclude=.*/example/.* --exclude=.*/external/.* --exclude=.*/test/.* - - run: du -sh / || true - - run: du -sh . || true - - uses: philips-software/sonarqube-issue-conversion@9e9958764ba5fd1d302b039779dc902bedfa4d01 # v1.2.0 with: input: ${{ github.workspace }}/testresults/*.xml output: execution.xml transformation: gtest-to-generic-execution - - run: du -sh / || true - - run: du -sh . || true - - name: Convert results run: | cp .build/Coverage/compile_commands.json compile_commands.json - - run: du -sh / || true - - run: du -sh . || true - - - run: cmake --build --preset Coverage --target clean - - - run: du -sh / || true - - run: du -sh . || true - - uses: sonarsource/sonarqube-scan-action@a31c9398be7ace6bbfaf30c0bd5d415f843d45e9 # v7.0.0 env: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} - - run: du -sh / || true - - run: du -sh . || true - codeql: name: CodeQL runs-on: ubuntu-latest From 9766418eec01730a517354224e93f3952ac47be4 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 14:05:09 +0000 Subject: [PATCH 176/196] feat: update fmt library linkage to use fmt::fmt instead of fmt-header-only --- CMakeLists.txt | 2 +- cucumber_cpp/library/formatter/CMakeLists.txt | 2 +- cucumber_cpp/library/formatter/helper/CMakeLists.txt | 2 +- cucumber_cpp/library/runtime/CMakeLists.txt | 2 +- cucumber_cpp/library/support/CMakeLists.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index adc3cd62..91519a98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -24,7 +24,7 @@ option(CCR_ENABLE_COVERAGE "Enable compiler flags for code coverage measurements set(CMAKE_POSITION_INDEPENDENT_CODE ON) -if (CCR_STANDALONE) +if (CCR_STANDALONE AND NOT CCR_ENABLE_COVERAGE) if (NOT (CMAKE_CXX_COMPILER_ID MATCHES "MSVC" OR CMAKE_CXX_SIMULATE_ID MATCHES "MSVC")) add_compile_options(-fsanitize=address -fsanitize=undefined) add_link_options(-fsanitize=address -fsanitize=undefined) diff --git a/cucumber_cpp/library/formatter/CMakeLists.txt b/cucumber_cpp/library/formatter/CMakeLists.txt index 3d8fe4aa..da6e145a 100644 --- a/cucumber_cpp/library/formatter/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/CMakeLists.txt @@ -25,7 +25,7 @@ target_link_libraries(cucumber_cpp.library.formatter PUBLIC cucumber_cpp.library.query cucumber_cpp.library.support cucumber_cpp.library.util - fmt-header-only + fmt::fmt nlohmann_json::nlohmann_json pugixml::pugixml ) diff --git a/cucumber_cpp/library/formatter/helper/CMakeLists.txt b/cucumber_cpp/library/formatter/helper/CMakeLists.txt index 22044bb7..3db158fe 100644 --- a/cucumber_cpp/library/formatter/helper/CMakeLists.txt +++ b/cucumber_cpp/library/formatter/helper/CMakeLists.txt @@ -21,7 +21,7 @@ target_link_libraries(cucumber_cpp.library.formatter.helper PUBLIC cucumber_cpp.library.cucumber_expression cucumber_cpp.library.support cucumber_cpp.library.query - fmt-header-only + fmt::fmt ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/runtime/CMakeLists.txt b/cucumber_cpp/library/runtime/CMakeLists.txt index 88719757..ce295e84 100644 --- a/cucumber_cpp/library/runtime/CMakeLists.txt +++ b/cucumber_cpp/library/runtime/CMakeLists.txt @@ -24,7 +24,7 @@ target_link_libraries(cucumber_cpp.library.runtime PUBLIC cucumber_cpp.library.cucumber_expression cucumber_cpp.library.support cucumber_cpp.library.util - fmt-header-only + fmt::fmt ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index 5f6aee43..ca39bcbd 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -31,7 +31,7 @@ target_link_libraries(cucumber_cpp.library.support PUBLIC cucumber_cpp.library.cucumber_expression cucumber_cpp.library.tag_expression cucumber_cpp.library.util - fmt-header-only + fmt::fmt ) if (CCR_BUILD_TESTS) From d7aa5d252d0d79ed1e095f4b767033b9a39412b7 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 14:31:04 +0000 Subject: [PATCH 177/196] feat: update test for Step to check dataTable access --- cucumber_cpp/library/engine/test/TestStep.cpp | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/cucumber_cpp/library/engine/test/TestStep.cpp b/cucumber_cpp/library/engine/test/TestStep.cpp index faf56d9b..646eea6f 100644 --- a/cucumber_cpp/library/engine/test/TestStep.cpp +++ b/cucumber_cpp/library/engine/test/TestStep.cpp @@ -96,11 +96,10 @@ namespace cucumber_cpp::library::engine ASSERT_THAT(context.Contains("top level value"), testing::Eq(true)); } - // TEST_F(TestStep, ProvidesAccessToTable) - // { - // ASSERT_THAT(step.table[0][0].As(), testing::Eq(table[0][0].As())); - // ASSERT_THAT(step.table[1][1].As(), testing::Eq(table[1][1].As())); - // } + TEST_F(TestStep, ProvidesAccessToTable) + { + ASSERT_THAT(step.dataTable.has_value(), testing::Eq(false)); + } TEST_F(TestStep, ThrowsStepPendingExceptionOnPending) { From 8506b7ede3eb0f716bbabf2cf57df0efb07e3782 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 14:31:10 +0000 Subject: [PATCH 178/196] fix: correct variable name from linaege to lineage in Query.cpp --- cucumber_cpp/library/query/Query.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index a1389028..8ea0ccd8 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -521,17 +521,17 @@ namespace cucumber_cpp::library::query ++featureCountByName[feature.first.name]; - auto linaege = feature.second + featurePtr + featureCountByName[feature.first.name]; - lineageByUri[*linaege.gherkinDocument->uri] = linaege; + auto lineage = feature.second + featurePtr + featureCountByName[feature.first.name]; + lineageByUri[*lineage.gherkinDocument->uri] = lineage; for (const auto& child : feature.first.children) { if (child.background) *this += child.background->steps; if (child.scenario) - *this += { *child.scenario, linaege }; + *this += { *child.scenario, lineage }; if (child.rule) - *this += { *child.rule, linaege }; + *this += { *child.rule, lineage }; } } From 27ba908dc4064c9d59612f1371bf2239cd04638c Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 14:36:30 +0000 Subject: [PATCH 179/196] feat: remove Join utility functions from support library --- cucumber_cpp/library/support/CMakeLists.txt | 2 -- cucumber_cpp/library/support/Join.cpp | 27 --------------------- cucumber_cpp/library/support/Join.hpp | 14 ----------- 3 files changed, 43 deletions(-) delete mode 100644 cucumber_cpp/library/support/Join.cpp delete mode 100644 cucumber_cpp/library/support/Join.hpp diff --git a/cucumber_cpp/library/support/CMakeLists.txt b/cucumber_cpp/library/support/CMakeLists.txt index ca39bcbd..86bb415f 100644 --- a/cucumber_cpp/library/support/CMakeLists.txt +++ b/cucumber_cpp/library/support/CMakeLists.txt @@ -11,8 +11,6 @@ target_sources(cucumber_cpp.library.support PRIVATE Body.hpp HookRegistry.cpp HookRegistry.hpp - Join.cpp - Join.hpp StepRegistry.cpp StepRegistry.hpp StepType.hpp diff --git a/cucumber_cpp/library/support/Join.cpp b/cucumber_cpp/library/support/Join.cpp deleted file mode 100644 index 9663e9f1..00000000 --- a/cucumber_cpp/library/support/Join.cpp +++ /dev/null @@ -1,27 +0,0 @@ -#include "cucumber_cpp/library/support/Join.hpp" -#include -#include -#include -#include -#include - -namespace cucumber_cpp::library::support -{ - std::string Join(std::initializer_list parts, const std::string& separator) - { - return Join({ parts.begin(), parts.end() }, separator); - } - - std::string Join(std::span parts, const std::string& separator) - { - if (parts.empty()) - return ""; - - std::string joined = *parts.begin(); - - for (const auto part : parts | std::views::drop(1)) - joined += std::format("{}{}", separator, part); - - return joined; - } -} diff --git a/cucumber_cpp/library/support/Join.hpp b/cucumber_cpp/library/support/Join.hpp deleted file mode 100644 index 07b185ac..00000000 --- a/cucumber_cpp/library/support/Join.hpp +++ /dev/null @@ -1,14 +0,0 @@ -#ifndef SUPPORT_JOIN_HPP -#define SUPPORT_JOIN_HPP - -#include -#include -#include - -namespace cucumber_cpp::library::support -{ - std::string Join(std::initializer_list parts, const std::string& separator); - std::string Join(std::span parts, const std::string& separator); -} - -#endif From fcd6ac2dfb2a1c179ccd177a49f4885959f5b2ed Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 15:55:09 +0000 Subject: [PATCH 180/196] feat: enable all formatters for compatibility tests --- compatibility/compatibility.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compatibility/compatibility.cpp b/compatibility/compatibility.cpp index 3412dc31..c035a95b 100644 --- a/compatibility/compatibility.cpp +++ b/compatibility/compatibility.cpp @@ -272,7 +272,7 @@ namespace compatibility BroadcastListener broadcastListener{ devkit.ndjsonFile, devkit.ndjsonFile.parent_path() / "expected.ndjson", devkit.ndjsonFile.parent_path() / "actual.ndjson", broadcaster }; cucumber_cpp::library::api::Formatters formatters; - cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, { "summary" }, {}); + cucumber_cpp::library::api::RunCucumber(runOptions, parameterRegistry, *programContext, broadcaster, formatters, { "junit", "message", "pretty", "summary", "usage" }, {}); broadcastListener.CompareEnvelopes(); } From 1728fc9ae80aa9999547d265a210bfbb4b961024 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 15:55:20 +0000 Subject: [PATCH 181/196] fix: add null check for test_case_started_id in HandleAttachment --- cucumber_cpp/library/formatter/PrettyFormatter.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/formatter/PrettyFormatter.cpp b/cucumber_cpp/library/formatter/PrettyFormatter.cpp index e8b090cd..6e12da5b 100644 --- a/cucumber_cpp/library/formatter/PrettyFormatter.cpp +++ b/cucumber_cpp/library/formatter/PrettyFormatter.cpp @@ -120,7 +120,8 @@ namespace cucumber_cpp::library::formatter if (!options.includeAttachments) return; - helper::PrintAttachment(outputStream, attachment, scenarioIndentByTestCaseStartedId.at(attachment.test_case_started_id.value()), options.useStatusIcon, options.theme); + if (attachment.test_case_started_id.has_value()) + helper::PrintAttachment(outputStream, attachment, scenarioIndentByTestCaseStartedId.at(attachment.test_case_started_id.value()), options.useStatusIcon, options.theme); } void PrettyFormatter::HandleTestStepFinished(const cucumber::messages::test_step_finished& testStepFinished) From 85d5b33f9c486af35b5efd73f54b5b2b56ab980f Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 15:55:26 +0000 Subject: [PATCH 182/196] fix: add check for empty testStepFinishedAndTestSteps in Query.cpp --- cucumber_cpp/library/query/Query.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index 8ea0ccd8..2d4d5534 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -257,6 +257,8 @@ namespace cucumber_cpp::library::query for (const auto* testCaseStarted : FindAllTestCaseStarted()) { const auto testStepFinishedAndTestSteps = FindTestStepFinishedAndTestStepBy(*testCaseStarted); + if (testStepFinishedAndTestSteps.empty()) + continue; const auto [testStepFinished, testStep] = *std::ranges::max_element(testStepFinishedAndTestSteps, SortByStatus{}); ++result[testStepFinished->test_step_result.status]; } From 9ad849e8b45b2212a7da1f6b18d8938a1586b83f Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:26:17 +0000 Subject: [PATCH 183/196] fix: add null check for step_definition_ids in UsageFormatter --- cucumber_cpp/library/formatter/UsageFormatter.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/formatter/UsageFormatter.cpp b/cucumber_cpp/library/formatter/UsageFormatter.cpp index 8b3b44a9..95a035ca 100644 --- a/cucumber_cpp/library/formatter/UsageFormatter.cpp +++ b/cucumber_cpp/library/formatter/UsageFormatter.cpp @@ -83,7 +83,7 @@ namespace cucumber_cpp::library::formatter for (const auto [testStepFinished, testStep] : testStepFinishedAndTestStep) { - if (testStep->step_definition_ids->size() != 1) + if (!testStep->step_definition_ids.has_value() || testStep->step_definition_ids->size() != 1) continue; const auto& pickleStep = query.FindPickleStepById(testStep->pickle_step_id.value()); From a53f3d6b342cbcf2ffd5b615e0f9b7dfbc81b718 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:44:21 +0000 Subject: [PATCH 184/196] fix: update step_definition_ids access to use value() for better clarity --- cucumber_cpp/library/formatter/UsageFormatter.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cucumber_cpp/library/formatter/UsageFormatter.cpp b/cucumber_cpp/library/formatter/UsageFormatter.cpp index 95a035ca..dbe6cf5a 100644 --- a/cucumber_cpp/library/formatter/UsageFormatter.cpp +++ b/cucumber_cpp/library/formatter/UsageFormatter.cpp @@ -83,13 +83,13 @@ namespace cucumber_cpp::library::formatter for (const auto [testStepFinished, testStep] : testStepFinishedAndTestStep) { - if (!testStep->step_definition_ids.has_value() || testStep->step_definition_ids->size() != 1) + if (!testStep->step_definition_ids.has_value() || testStep->step_definition_ids.value().size() != 1) continue; const auto& pickleStep = query.FindPickleStepById(testStep->pickle_step_id.value()); const auto& pickle = query.FindPickleById(testCase.pickle_id); const auto& step = query.FindStepBy(pickleStep); - const auto& stepDefinitionId = testStep->step_definition_ids->front(); + const auto& stepDefinitionId = testStep->step_definition_ids.value().front(); const auto& lineage = query.FindLineageByPickle(pickle); std::optional duration{}; From 92d8e36d76fbd656ef7bf260bce5d492c2d6434f Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:44:26 +0000 Subject: [PATCH 185/196] refactor: replace std::format with fmt::format for consistency in formatting --- cucumber_cpp/acceptance_test/steps/Steps.cpp | 3 +-- .../library/cucumber_expression/test/TestExpression.cpp | 6 +++--- .../cucumber_expression/test/TestExpressionParser.cpp | 6 +++--- .../cucumber_expression/test/TestExpressionTokenizer.cpp | 6 +++--- .../library/cucumber_expression/test/TestTransformation.cpp | 6 +++--- 5 files changed, 13 insertions(+), 14 deletions(-) diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index 05784afc..47030c5a 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -3,7 +3,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include -#include #include #include #include @@ -45,7 +44,7 @@ STEP("a step step") WHEN("I print {string}", (const std::string& str)) { - std::cout << std::format("print: {}\n", str); + std::cout << fmt::format("print: {}\n", str); } THEN("an assertion is raised") diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp index 367b7fed..110a9bfc 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpression.cpp @@ -4,6 +4,7 @@ #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "fmt/format.h" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" #include "yaml-cpp/yaml.h" @@ -12,7 +13,6 @@ #include #include #include -#include #include #include #include @@ -37,7 +37,7 @@ namespace cucumber_cpp::library::cucumber_expression std::string FormatTestFailureMessage(const std::string& file, const YAML::Node& node, const Expression& expression) { - return std::format("file: {}\n" + return fmt::format("file: {}\n" "failed to match {}\n" "regex {}\n" "against {}", @@ -70,7 +70,7 @@ namespace cucumber_cpp::library::cucumber_expression { if (testdata["exception"]) ASSERT_ANY_THROW(Match(testdata["expression"].as(), testdata["text"].as())) - << std::format("Test failed for file: {}", file); + << fmt::format("Test failed for file: {}", file); else { if (testdata["expected_args"].IsNull()) diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpressionParser.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpressionParser.cpp index 5267c96f..3b3890c4 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpressionParser.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpressionParser.cpp @@ -1,11 +1,11 @@ #include "cucumber_cpp/library/cucumber_expression/Ast.hpp" #include "cucumber_cpp/library/cucumber_expression/ExpressionParser.hpp" +#include "fmt/format.h" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" #include "yaml-cpp/yaml.h" #include #include -#include #include #include #include @@ -107,13 +107,13 @@ namespace cucumber_cpp::library::cucumber_expression { if (testdata["exception"]) ASSERT_ANY_THROW(ExpressionParser{}.Parse(testdata["expression"].as())) - << std::format("Test failed for file: {}", file); + << fmt::format("Test failed for file: {}", file); else { const auto actual = ExpressionParser{}.Parse(testdata["expression"].as()); const auto expected = CreateNode(testdata["expected_ast"]); ASSERT_THAT(actual, testing::Eq(expected)) - << std::format("Test failed for file: {}", file); + << fmt::format("Test failed for file: {}", file); } } } diff --git a/cucumber_cpp/library/cucumber_expression/test/TestExpressionTokenizer.cpp b/cucumber_cpp/library/cucumber_expression/test/TestExpressionTokenizer.cpp index 62fe1cf8..b9c12cd2 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestExpressionTokenizer.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestExpressionTokenizer.cpp @@ -1,12 +1,12 @@ #include "cucumber_cpp/library/cucumber_expression/Ast.hpp" #include "cucumber_cpp/library/cucumber_expression/ExpressionTokenizer.hpp" +#include "fmt/format.h" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" #include "yaml-cpp/yaml.h" #include "gmock/gmock.h" #include #include -#include #include #include #include @@ -62,13 +62,13 @@ namespace cucumber_cpp::library::cucumber_expression for (const auto& [file, testdata] : GetTestData(testdataPath)) if (testdata["exception"]) ASSERT_ANY_THROW(ExpressionTokenizer{}.Tokenize(testdata["expression"].as())) - << std::format("Test failed for file: {}", file); + << fmt::format("Test failed for file: {}", file); else { const auto actual = ExpressionTokenizer{}.Tokenize(testdata["expression"].as()); const auto expected = CreateTokens(testdata["expected_tokens"]); ASSERT_THAT(actual, testing::ElementsAreArray(expected)) - << std::format("Test failed for file: {}", file); + << fmt::format("Test failed for file: {}", file); } } diff --git a/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp b/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp index 2295ab21..82210908 100644 --- a/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp +++ b/cucumber_cpp/library/cucumber_expression/test/TestTransformation.cpp @@ -1,5 +1,6 @@ #include "cucumber_cpp/library/cucumber_expression/Expression.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "fmt/format.h" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" #include "yaml-cpp/yaml.h" @@ -7,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -30,7 +30,7 @@ namespace cucumber_cpp::library::cucumber_expression std::string FormatMessage(const YAML::Node& node, const Expression& expression) { - return std::format("failed to match {}\n" + return fmt::format("failed to match {}\n" "regex {}\n" "against {}", node["expression"].as(), expression.Pattern(), node["text"].as()); @@ -48,7 +48,7 @@ namespace cucumber_cpp::library::cucumber_expression const auto actualRegex = expression.Pattern(); EXPECT_THAT(actualRegex, testing::StrEq(testdata["expected_regex"].as())) - << std::format("Test failed for {}", testdata["expression"].as()); + << fmt::format("Test failed for {}", testdata["expression"].as()); } } } From b26b67a3349a872f8097ca430558345284ac6b57 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:45:50 +0000 Subject: [PATCH 186/196] refactor: replace std::format with fmt::format for consistency across Argument, Errors, Expression, ParameterRegistry --- .../library/cucumber_expression/Argument.cpp | 3 ++- .../library/cucumber_expression/CMakeLists.txt | 1 + .../library/cucumber_expression/Errors.cpp | 14 +++++++------- .../library/cucumber_expression/Expression.cpp | 13 ++++++------- .../cucumber_expression/ParameterRegistry.cpp | 4 ++-- .../cucumber_expression/ParameterRegistry.hpp | 6 ++---- 6 files changed, 20 insertions(+), 21 deletions(-) diff --git a/cucumber_cpp/library/cucumber_expression/Argument.cpp b/cucumber_cpp/library/cucumber_expression/Argument.cpp index a8ed1e7f..a1c040d4 100644 --- a/cucumber_cpp/library/cucumber_expression/Argument.cpp +++ b/cucumber_cpp/library/cucumber_expression/Argument.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/library/cucumber_expression/Argument.hpp" +#include ".build/Coverage/_deps/libfmt-src/include/fmt/format.h" #include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" #include @@ -20,7 +21,7 @@ namespace cucumber_cpp::library::cucumber_expression std::vector Argument::BuildArguments(const cucumber::messages::group& group, std::span parameters) { if (group.children.size() != parameters.size()) - throw std::runtime_error(std::format("Mismatch between number of groups ({}) and parameters ({})", group.children.size(), parameters.size())); + throw std::runtime_error(fmt::format("Mismatch between number of groups ({}) and parameters ({})", group.children.size(), parameters.size())); std::size_t index{ 0 }; auto converted = parameters | std::views::transform([&group, &index](const Parameter& parameter) -> Argument diff --git a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt index ce67f181..66002367 100644 --- a/cucumber_cpp/library/cucumber_expression/CMakeLists.txt +++ b/cucumber_cpp/library/cucumber_expression/CMakeLists.txt @@ -28,6 +28,7 @@ target_include_directories(cucumber_cpp.library.cucumber_expression PUBLIC target_link_libraries(cucumber_cpp.library.cucumber_expression PUBLIC cucumber_gherkin_lib + fmt::fmt ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/cucumber_expression/Errors.cpp b/cucumber_cpp/library/cucumber_expression/Errors.cpp index 197a0120..47b0f1c3 100644 --- a/cucumber_cpp/library/cucumber_expression/Errors.cpp +++ b/cucumber_cpp/library/cucumber_expression/Errors.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/Ast.hpp" +#include "fmt/format.h" #include -#include #include #include #include @@ -29,7 +29,7 @@ namespace cucumber_cpp::library::cucumber_expression Error::Error(std::size_t column, std::string_view expression, std::string_view pointer, std::string_view problem, std::string_view solution) : std::runtime_error{ - std::format( + fmt::format( "This Cucumber Expression has a problem at column {}:\n" "\n" "{}\n" @@ -90,14 +90,14 @@ Otherwise rephrase your expression or consider using a regular expression instea token.Start(), expression, PointAtLocated(token), - std::format(R"(The '{}' does not have a matching '{}')", Token::SymbolOf(beginToken), Token::SymbolOf(endToken)), - std::format(R"(If you did not intend to use {} you can use '\\{}' to escape the {})", Token::PurposeOf(beginToken), Token::SymbolOf(beginToken), Token::PurposeOf(beginToken)), + fmt::format(R"(The '{}' does not have a matching '{}')", Token::SymbolOf(beginToken), Token::SymbolOf(endToken)), + fmt::format(R"(If you did not intend to use {} you can use '\\{}' to escape the {})", Token::PurposeOf(beginToken), Token::SymbolOf(beginToken), Token::PurposeOf(beginToken)), } {} NoEligibleParsers::NoEligibleParsers(std::span tokens) : std::runtime_error{ - std::format("No eligible parsers for [{}]", std::accumulate(tokens.begin() + 1, tokens.end(), Token::NameOf(tokens.begin()->Type()), + fmt::format("No eligible parsers for [{}]", std::accumulate(tokens.begin() + 1, tokens.end(), Token::NameOf(tokens.begin()->Type()), [](const auto& acc, const auto& token) -> std::string { return acc + ", " + Token::NameOf(token.Type()); @@ -161,8 +161,8 @@ For more complicated expressions consider using a regular expression instead.)", node.Start(), expression, PointAtLocated(node), - std::format(R"(Undefined parameter type '{}')", undefinedParameterName), - std::format(R"(Please register a ParameterType for '{}')", undefinedParameterName), + fmt::format(R"(Undefined parameter type '{}')", undefinedParameterName), + fmt::format(R"(Please register a ParameterType for '{}')", undefinedParameterName), } , expression{ std::move(expression) } , undefinedParameterName{ std::move(undefinedParameterName) } diff --git a/cucumber_cpp/library/cucumber_expression/Expression.cpp b/cucumber_cpp/library/cucumber_expression/Expression.cpp index 79dfdfaa..9be0a3c7 100644 --- a/cucumber_cpp/library/cucumber_expression/Expression.cpp +++ b/cucumber_cpp/library/cucumber_expression/Expression.cpp @@ -5,11 +5,10 @@ #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/cucumber_expression/ExpressionParser.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "fmt/format.h" #include -#include #include #include -#include #include #include #include @@ -99,7 +98,7 @@ namespace cucumber_cpp::library::cucumber_expression for (const auto& child : node.Children()) partialRegex += RewriteToRegex(child); - return std::format(R"((?:{})?)", partialRegex); + return fmt::format(R"((?:{})?)", partialRegex); } std::string Expression::RewriteAlternation(const Node& node) @@ -118,7 +117,7 @@ namespace cucumber_cpp::library::cucumber_expression for (const auto& child : node.Children() | std::views::drop(1)) partialRegex += '|' + RewriteToRegex(child); - return std::format(R"((?:{}))", partialRegex); + return fmt::format(R"((?:{}))", partialRegex); } std::string Expression::RewriteAlternative(const Node& node) @@ -143,13 +142,13 @@ namespace cucumber_cpp::library::cucumber_expression std::string partialRegex{}; if (parameter.regex.size() == 1) - partialRegex = std::format(R"(({}))", parameter.regex.front()); + partialRegex = fmt::format(R"(({}))", parameter.regex.front()); else { partialRegex = { parameter.regex.front() }; for (const auto& parameterRegex : parameter.regex | std::views::drop(1)) partialRegex += R"()|(?:)" + parameterRegex; - partialRegex = std::format(R"(((?:{})))", partialRegex); + partialRegex = fmt::format(R"(((?:{})))", partialRegex); } return partialRegex; } @@ -166,7 +165,7 @@ namespace cucumber_cpp::library::cucumber_expression for (const auto& child : node.Children()) partialRegex += RewriteToRegex(child); - return std::format("^{}$", partialRegex); + return fmt::format("^{}$", partialRegex); } std::string Expression::CreateEmptyRegexString(const Node& node) const diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp index f67c7cf0..05aeea97 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.cpp @@ -2,11 +2,11 @@ #include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/Errors.hpp" #include "cucumber_cpp/library/support/SupportCodeLibrary.hpp" +#include "fmt/format.h" #include #include #include #include -#include #include #include #include @@ -106,7 +106,7 @@ namespace cucumber_cpp::library::cucumber_expression if (name.empty()) throw CucumberExpressionError{ "The anonymous parameter type has already been defined" }; else - throw CucumberExpressionError{ std::format("There is already a parameter with name {}", name) }; + throw CucumberExpressionError{ fmt::format("There is already a parameter with name {}", name) }; } } diff --git a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp index bd52ec46..cf65eb73 100644 --- a/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp +++ b/cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp @@ -2,17 +2,15 @@ #define CUCUMBER_EXPRESSION_PARAMETERREGISTRY_HPP #include "cucumber/messages/group.hpp" +#include "fmt/format.h" #include -#include #include #include #include #include #include -#include #include #include -#include #include #include #include @@ -58,7 +56,7 @@ namespace cucumber_cpp::library::cucumber_expression To to{}; stream >> to; if (stream.fail()) - throw ConversionError{ std::format("Cannot convert parameter {} in to {}", s, typeid(To).name()) }; + throw ConversionError{ fmt::format("Cannot convert parameter {} in to {}", s, typeid(To).name()) }; return to; } From b51cee3b61517771a2adf1b9fdab3e4924398950 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:46:54 +0000 Subject: [PATCH 187/196] refactor: replace std::format with fmt::format in Query.cpp for consistency --- cucumber_cpp/library/query/CMakeLists.txt | 1 + cucumber_cpp/library/query/Query.cpp | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cucumber_cpp/library/query/CMakeLists.txt b/cucumber_cpp/library/query/CMakeLists.txt index ec90d8bb..8a0554c4 100644 --- a/cucumber_cpp/library/query/CMakeLists.txt +++ b/cucumber_cpp/library/query/CMakeLists.txt @@ -12,6 +12,7 @@ target_include_directories(cucumber_cpp.library.query PUBLIC target_link_libraries(cucumber_cpp.library.query PUBLIC cucumber_gherkin_lib cucumber_cpp.library.util + fmt::fmt ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/query/Query.cpp b/cucumber_cpp/library/query/Query.cpp index 2d4d5534..fc568730 100644 --- a/cucumber_cpp/library/query/Query.cpp +++ b/cucumber_cpp/library/query/Query.cpp @@ -33,10 +33,9 @@ #include "cucumber/messages/undefined_parameter_type.hpp" #include "cucumber_cpp/library/util/Broadcaster.hpp" #include "cucumber_cpp/library/util/Timestamp.hpp" +#include "fmt/format.h" #include #include -#include -#include #include #include #include @@ -68,13 +67,13 @@ namespace cucumber_cpp::library::query std::string Lineage::GetUniqueFeatureName() const { - return std::format("{}/{}", feature->name, featureIndex); + return fmt::format("{}/{}", feature->name, featureIndex); } std::string Lineage::GetScenarioAndOrRuleName() const { if (rule) - return std::format("{}/{}", rule->name, scenario->name); + return fmt::format("{}/{}", rule->name, scenario->name); return scenario->name; } From 9c067ad0a820fdedd7c6521a0c4efa6a63f53450 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:47:40 +0000 Subject: [PATCH 188/196] refactor: replace std::format with fmt::format in Body.cpp for consistency and remove unused Polyfill.hpp --- cucumber_cpp/library/support/Body.cpp | 5 ++--- cucumber_cpp/library/support/Polyfill.hpp | 18 ------------------ 2 files changed, 2 insertions(+), 21 deletions(-) delete mode 100644 cucumber_cpp/library/support/Polyfill.hpp diff --git a/cucumber_cpp/library/support/Body.cpp b/cucumber_cpp/library/support/Body.cpp index cbd8525d..898555fb 100644 --- a/cucumber_cpp/library/support/Body.cpp +++ b/cucumber_cpp/library/support/Body.cpp @@ -11,7 +11,6 @@ #include #include #include -#include #include #include #include @@ -35,9 +34,9 @@ namespace cucumber_cpp::library::support auto fileName = std::filesystem::relative(testPartResult.file_name(), std::filesystem::current_path()).string(); if (testStepResult.message) - testStepResult.message = std::format("{}\n{}:{}: Failure\n{}", testStepResult.message.value(), fileName, testPartResult.line_number(), testPartResult.message()); + testStepResult.message = fmt::format("{}\n{}:{}: Failure\n{}", testStepResult.message.value(), fileName, testPartResult.line_number(), testPartResult.message()); else - testStepResult.message = std::format("{}:{}: Failure\n{}", fileName, testPartResult.line_number(), testPartResult.message()); + testStepResult.message = fmt::format("{}:{}: Failure\n{}", fileName, testPartResult.line_number(), testPartResult.message()); } if (testPartResult.fatally_failed()) diff --git a/cucumber_cpp/library/support/Polyfill.hpp b/cucumber_cpp/library/support/Polyfill.hpp deleted file mode 100644 index 37792917..00000000 --- a/cucumber_cpp/library/support/Polyfill.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#ifndef SUPPORT_POLYFILL_HPP -#define SUPPORT_POLYFILL_HPP - -#include -#include -#include -#include - -namespace cucumber_cpp::library::support -{ - template - void print(std::ostream& outputStream, std::format_string fmt, Args&&... args) - { - outputStream << std::format(fmt, std::forward(args)...); - } -} - -#endif From 11514b22ae37993e6d1ab2802c1159652ab12571 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:48:44 +0000 Subject: [PATCH 189/196] refactor: replace std::format with fmt::format for consistency across tag_expression files --- cucumber_cpp/library/tag_expression/CMakeLists.txt | 1 + cucumber_cpp/library/tag_expression/Model.cpp | 10 +++++----- cucumber_cpp/library/tag_expression/Parser.cpp | 14 +++++++------- .../library/tag_expression/test/CMakeLists.txt | 1 + .../tag_expression/test/TestEvaluations.cpp | 4 ++-- .../library/tag_expression/test/TestParsing.cpp | 4 ++-- 6 files changed, 18 insertions(+), 16 deletions(-) diff --git a/cucumber_cpp/library/tag_expression/CMakeLists.txt b/cucumber_cpp/library/tag_expression/CMakeLists.txt index 01acf468..3848b4ed 100644 --- a/cucumber_cpp/library/tag_expression/CMakeLists.txt +++ b/cucumber_cpp/library/tag_expression/CMakeLists.txt @@ -17,6 +17,7 @@ target_include_directories(cucumber_cpp.library.tag_expression PUBLIC target_link_libraries(cucumber_cpp.library.tag_expression PUBLIC cucumber_gherkin_lib + fmt::fmt ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/tag_expression/Model.cpp b/cucumber_cpp/library/tag_expression/Model.cpp index 8535d45c..0c79905d 100644 --- a/cucumber_cpp/library/tag_expression/Model.cpp +++ b/cucumber_cpp/library/tag_expression/Model.cpp @@ -1,9 +1,9 @@ #include "cucumber_cpp/library/tag_expression/Model.hpp" #include "cucumber/messages/pickle_tag.hpp" #include "cucumber/messages/tag.hpp" +#include "fmt/format.h" #include #include -#include #include #include #include @@ -104,7 +104,7 @@ namespace cucumber_cpp::library::tag_expression if (!left || !right) return ""; - return std::format(R"(( {} and {} ))", static_cast(*left), static_cast(*right)); + return fmt::format(R"(( {} and {} ))", static_cast(*left), static_cast(*right)); } OrExpression::OrExpression(std::unique_ptr left, std::unique_ptr right) @@ -132,7 +132,7 @@ namespace cucumber_cpp::library::tag_expression if (!left || !right) return ""; - return std::format(R"(( {} or {} ))", static_cast(*left), static_cast(*right)); + return fmt::format(R"(( {} or {} ))", static_cast(*left), static_cast(*right)); } NotExpression::NotExpression(std::unique_ptr operand) @@ -160,8 +160,8 @@ namespace cucumber_cpp::library::tag_expression return ""; if (const auto& ref = *operand.get(); typeid(ref) == typeid(AndExpression) || typeid(ref) == typeid(OrExpression)) - return std::format(R"(not {})", static_cast(*operand)); + return fmt::format(R"(not {})", static_cast(*operand)); - return std::format(R"(not ( {} ))", static_cast(*operand)); + return fmt::format(R"(not ( {} ))", static_cast(*operand)); } } diff --git a/cucumber_cpp/library/tag_expression/Parser.cpp b/cucumber_cpp/library/tag_expression/Parser.cpp index 76e32a50..7f78b3e0 100644 --- a/cucumber_cpp/library/tag_expression/Parser.cpp +++ b/cucumber_cpp/library/tag_expression/Parser.cpp @@ -2,10 +2,10 @@ #include "cucumber_cpp/library/tag_expression/Error.hpp" #include "cucumber_cpp/library/tag_expression/Model.hpp" #include "cucumber_cpp/library/tag_expression/Token.hpp" +#include "fmt/format.h" #include #include #include -#include #include #include #include @@ -21,7 +21,7 @@ namespace cucumber_cpp::library::tag_expression void EnsureExpectedTokenType(TokenType tokenType, TokenType expected, std::string_view lastPart) { if (tokenType != expected) - throw Error(std::format(R"(Syntax error. Expected {} after {})", TokenTypeMap().at(expected), lastPart)); + throw Error(fmt::format(R"(Syntax error. Expected {} after {})", TokenTypeMap().at(expected), lastPart)); } void RequireArgCount(const Token& token, std::deque>& expressions, std::size_t number) @@ -37,7 +37,7 @@ namespace cucumber_cpp::library::tag_expression expressionsStr += static_cast(*expr); } - throw Error(std::format(R"({}: Too few operands (expressions={{{}}}))", token.keyword, expressionsStr)); + throw Error(fmt::format(R"({}: Too few operands (expressions={{{}}}))", token.keyword, expressionsStr)); } } @@ -74,7 +74,7 @@ namespace cucumber_cpp::library::tag_expression else if (token == NOT) PushUnary(token, expressions); else - throw Error(std::format("Unexpected token: {}", token.keyword)); + throw Error(fmt::format("Unexpected token: {}", token.keyword)); } std::vector Tokenize(std::string_view expression) @@ -88,7 +88,7 @@ namespace cucumber_cpp::library::tag_expression if (escaped) { if ((ch != '(' && ch != ')' && ch != '\\') && !std::isspace(ch, std::locale())) - throw Error(std::format(R"(Tag expression "{}" could not be parsed because of syntax error: Illegal escape before "{}".)", expression, ch)); + throw Error(fmt::format(R"(Tag expression "{}" could not be parsed because of syntax error: Illegal escape before "{}".)", expression, ch)); token += ch; escaped = false; } @@ -174,7 +174,7 @@ namespace cucumber_cpp::library::tag_expression } if (operations.empty()) - throw Error(std::format("Missing '(': Too few open-parens in: {}", expression)); + throw Error(fmt::format("Missing '(': Too few open-parens in: {}", expression)); else if (operations.top() == OPEN_PARENTHESIS) { @@ -192,7 +192,7 @@ namespace cucumber_cpp::library::tag_expression operations.pop(); if (lastOperation == OPEN_PARENTHESIS) - throw Error(std::format("Unclosed '(': Too many open-parens in: {}", expression)); + throw Error(fmt::format("Unclosed '(': Too many open-parens in: {}", expression)); PushExpression(lastOperation, expressions); } diff --git a/cucumber_cpp/library/tag_expression/test/CMakeLists.txt b/cucumber_cpp/library/tag_expression/test/CMakeLists.txt index 384f8893..64c6cf26 100644 --- a/cucumber_cpp/library/tag_expression/test/CMakeLists.txt +++ b/cucumber_cpp/library/tag_expression/test/CMakeLists.txt @@ -6,6 +6,7 @@ target_link_libraries(cucumber_cpp.library.tag_expression.test PUBLIC cucumber_cpp.library.tag_expression yaml-cpp::yaml-cpp GTest::gmock + fmt::fmt ) target_sources(cucumber_cpp.library.tag_expression.test PRIVATE diff --git a/cucumber_cpp/library/tag_expression/test/TestEvaluations.cpp b/cucumber_cpp/library/tag_expression/test/TestEvaluations.cpp index 0f28472b..65dab7f5 100644 --- a/cucumber_cpp/library/tag_expression/test/TestEvaluations.cpp +++ b/cucumber_cpp/library/tag_expression/test/TestEvaluations.cpp @@ -1,11 +1,11 @@ #include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "fmt/format.h" #include "yaml-cpp/node/emit.h" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" #include "yaml-cpp/yaml.h" #include #include -#include #include #include #include @@ -80,7 +80,7 @@ namespace cucumber_cpp::library::tag_expression return new TestEvaluations(node["expression"].as(), test["variables"], test["result"].as()); }; - auto* testInfo = testing::RegisterTest("TestEvaluations", std::format("Test_{}_{}", tests.size(), lineNumber).c_str(), nullptr, nullptr, testdataPath.string().c_str(), lineNumber, factory); + auto* testInfo = testing::RegisterTest("TestEvaluations", fmt::format("Test_{}_{}", tests.size(), lineNumber).c_str(), nullptr, nullptr, testdataPath.string().c_str(), lineNumber, factory); tests.push_back(testInfo); diff --git a/cucumber_cpp/library/tag_expression/test/TestParsing.cpp b/cucumber_cpp/library/tag_expression/test/TestParsing.cpp index edb3cd4a..0567a910 100644 --- a/cucumber_cpp/library/tag_expression/test/TestParsing.cpp +++ b/cucumber_cpp/library/tag_expression/test/TestParsing.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/library/tag_expression/Parser.hpp" +#include "fmt/format.h" #include "yaml-cpp/node/emit.h" #include "yaml-cpp/node/node.h" #include "yaml-cpp/node/parse.h" @@ -6,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -69,7 +69,7 @@ namespace cucumber_cpp::library::tag_expression return new TestParsingFixture(node["expression"].as(), node["formatted"].as()); }; - auto* testInfo = testing::RegisterTest("TestParsing", std::format("Test_{}_{}", tests.size(), lineNumber).c_str(), nullptr, nullptr, testdataPath.string().c_str(), lineNumber, factory); + auto* testInfo = testing::RegisterTest("TestParsing", fmt::format("Test_{}_{}", tests.size(), lineNumber).c_str(), nullptr, nullptr, testdataPath.string().c_str(), lineNumber, factory); tests.push_back(testInfo); From fc729ad684d39d37742721d12d336f50d54c28a4 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:49:30 +0000 Subject: [PATCH 190/196] refactor: replace std::format with fmt::format for consistency in Application and Timestamp files --- cucumber_cpp/library/Application.cpp | 9 ++++----- cucumber_cpp/library/util/CMakeLists.txt | 1 + cucumber_cpp/library/util/Timestamp.cpp | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cucumber_cpp/library/Application.cpp b/cucumber_cpp/library/Application.cpp index 1caad35d..b86bb37b 100644 --- a/cucumber_cpp/library/Application.cpp +++ b/cucumber_cpp/library/Application.cpp @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -91,7 +90,7 @@ namespace cucumber_cpp::library const auto iter = std::ranges::find(formattersSet, option.name, &std::pair::first); if (iter == formattersSet.end()) - return std::format("'{}' is not a valid formatter", option.name); + return fmt::format("'{}' is not a valid formatter", option.name); else return ""; }, @@ -138,17 +137,17 @@ namespace cucumber_cpp::library } catch (const InternalError& error) { - std::cout << std::format("InternalError error:\n{}\n", error.what()); + std::cout << fmt::format("InternalError error:\n{}\n", error.what()); return EXIT_FAILURE; } catch (const cucumber_expression::Error& error) { - std::cout << std::format("Cucumber Expression error:\n{}\n", error.what()); + std::cout << fmt::format("Cucumber Expression error:\n{}\n", error.what()); return EXIT_FAILURE; } catch (const std::exception& error) { - std::cout << std::format("Generic error:\n{}\n", error.what()); + std::cout << fmt::format("Generic error:\n{}\n", error.what()); return EXIT_FAILURE; } catch (...) diff --git a/cucumber_cpp/library/util/CMakeLists.txt b/cucumber_cpp/library/util/CMakeLists.txt index 6f4411a3..e897d0f3 100644 --- a/cucumber_cpp/library/util/CMakeLists.txt +++ b/cucumber_cpp/library/util/CMakeLists.txt @@ -19,6 +19,7 @@ target_include_directories(cucumber_cpp.library.util PUBLIC target_link_libraries(cucumber_cpp.library.util PUBLIC cucumber_gherkin_lib + fmt::fmt ) if (CCR_BUILD_TESTS) diff --git a/cucumber_cpp/library/util/Timestamp.cpp b/cucumber_cpp/library/util/Timestamp.cpp index 5b8d49b4..5d35e5e7 100644 --- a/cucumber_cpp/library/util/Timestamp.cpp +++ b/cucumber_cpp/library/util/Timestamp.cpp @@ -3,8 +3,8 @@ #include "cucumber/messages/duration.hpp" #include "cucumber/messages/timestamp.hpp" #include "cucumber_cpp/library/util/Duration.hpp" +#include "fmt/format.h" #include -#include #include namespace cucumber_cpp::library::util @@ -65,6 +65,6 @@ namespace cucumber_cpp::library::util { const auto duration = std::chrono::duration_cast(std::chrono::seconds(timestamp.seconds) + std::chrono::nanoseconds(timestamp.nanos)); const std::chrono::system_clock::time_point tp{ duration }; - return std::format("{:%FT%T%Z}", tp); + return fmt::format("{:%FT%T%Z}", tp); } } From 429f71661456b63ddc10d77658027932e86562d9 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 16:51:12 +0000 Subject: [PATCH 191/196] refactor: include fmt/chrono.h for consistency in Timestamp.cpp --- cucumber_cpp/library/util/Timestamp.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/cucumber_cpp/library/util/Timestamp.cpp b/cucumber_cpp/library/util/Timestamp.cpp index 5d35e5e7..d7cacd6d 100644 --- a/cucumber_cpp/library/util/Timestamp.cpp +++ b/cucumber_cpp/library/util/Timestamp.cpp @@ -3,6 +3,7 @@ #include "cucumber/messages/duration.hpp" #include "cucumber/messages/timestamp.hpp" #include "cucumber_cpp/library/util/Duration.hpp" +#include "fmt/chrono.h" #include "fmt/format.h" #include #include From 65b4b02933811c37a55875098d5e66f4f53eb131 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 22:33:41 +0000 Subject: [PATCH 192/196] refactor: update include path for fmt/format.h in Argument.cpp for consistency --- cucumber_cpp/library/cucumber_expression/Argument.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/cucumber_expression/Argument.cpp b/cucumber_cpp/library/cucumber_expression/Argument.cpp index a1c040d4..1f872143 100644 --- a/cucumber_cpp/library/cucumber_expression/Argument.cpp +++ b/cucumber_cpp/library/cucumber_expression/Argument.cpp @@ -1,7 +1,7 @@ #include "cucumber_cpp/library/cucumber_expression/Argument.hpp" -#include ".build/Coverage/_deps/libfmt-src/include/fmt/format.h" #include "cucumber/messages/group.hpp" #include "cucumber_cpp/library/cucumber_expression/ParameterRegistry.hpp" +#include "fmt/format.h" #include #include #include From f2dff722574813fb0c85e7b9aacc9b9d29d396f4 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 22:34:22 +0000 Subject: [PATCH 193/196] refactor: enhance exception message formatting in FormatMessages.cpp --- cucumber_cpp/library/formatter/helper/FormatMessages.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cucumber_cpp/library/formatter/helper/FormatMessages.cpp b/cucumber_cpp/library/formatter/helper/FormatMessages.cpp index bf1d3f9a..ec91cb78 100644 --- a/cucumber_cpp/library/formatter/helper/FormatMessages.cpp +++ b/cucumber_cpp/library/formatter/helper/FormatMessages.cpp @@ -304,6 +304,8 @@ namespace cucumber_cpp::library::formatter::helper if (testStepResult.exception.has_value() && testStepResult.exception.value().message.has_value()) { return TextBuilder{} + .Append(util::Trim(testStepResult.exception.value().type)) + .Append(": ") .Append(util::Trim(testStepResult.exception.value().message.value())) .Build(theme.status.All(testStepResult.status), true); } From 1a8d52cb60636d7dfc83287a17178496dba65579 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 22:34:31 +0000 Subject: [PATCH 194/196] refactor: demangle exception type names in Body.cpp for improved readability --- cucumber_cpp/library/support/Body.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/library/support/Body.cpp b/cucumber_cpp/library/support/Body.cpp index 898555fb..4c306a10 100644 --- a/cucumber_cpp/library/support/Body.cpp +++ b/cucumber_cpp/library/support/Body.cpp @@ -1,4 +1,5 @@ #include "cucumber_cpp/library/support/Body.hpp" +#include "cucumber/gherkin/demangle.hpp" #include "cucumber/messages/exception.hpp" #include "cucumber/messages/step_match_arguments_list.hpp" #include "cucumber/messages/test_step_result.hpp" @@ -99,7 +100,7 @@ namespace cucumber_cpp::library::support { testStepResult.status = cucumber::messages::test_step_result_status::FAILED; testStepResult.exception = cucumber::messages::exception{ - .type = typeid(e).name(), + .type = cucumber::gherkin::detail::demangle(typeid(e).name()).get(), .message = e.what(), }; } From b764e2695651c56235778d6e20fb4bc2d8a12091 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 22:34:43 +0000 Subject: [PATCH 195/196] refactor: add scenario for exception throwing and update test tags for failed tests --- .../acceptance_test/features/test_scenarios.feature | 7 +++++++ cucumber_cpp/acceptance_test/steps/Steps.cpp | 11 +++++++++++ cucumber_cpp/acceptance_test/test.bats | 2 +- 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/cucumber_cpp/acceptance_test/features/test_scenarios.feature b/cucumber_cpp/acceptance_test/features/test_scenarios.feature index d476c23b..2eb4280c 100644 --- a/cucumber_cpp/acceptance_test/features/test_scenarios.feature +++ b/cucumber_cpp/acceptance_test/features/test_scenarios.feature @@ -16,6 +16,13 @@ Feature: Simple feature file Then an assertion is raised Then a then step + @result:FAILED + Scenario: A failing scenario + Given a given step + When a when step + Then an exception is thrown + Then a then step + @result:UNDEFINED Scenario: A scenario with undefined step Given a missing step diff --git a/cucumber_cpp/acceptance_test/steps/Steps.cpp b/cucumber_cpp/acceptance_test/steps/Steps.cpp index 47030c5a..a6cc3119 100644 --- a/cucumber_cpp/acceptance_test/steps/Steps.cpp +++ b/cucumber_cpp/acceptance_test/steps/Steps.cpp @@ -49,9 +49,20 @@ WHEN("I print {string}", (const std::string& str)) THEN("an assertion is raised") { + EXPECT_THAT(1, testing::Eq(2)); ASSERT_THAT(false, testing::IsTrue()); } +THEN("an exception is thrown") +{ + struct CustomException : public std::runtime_error + { + using runtime_error::runtime_error; + }; + + throw CustomException{ "This is a custom exception" }; +} + THEN("two expectations are raised") { EXPECT_THAT(0, testing::Eq(1)); diff --git a/cucumber_cpp/acceptance_test/test.bats b/cucumber_cpp/acceptance_test/test.bats index a1481f3a..d7ba7f2f 100644 --- a/cucumber_cpp/acceptance_test/test.bats +++ b/cucumber_cpp/acceptance_test/test.bats @@ -25,7 +25,7 @@ teardown() { } @test "Failed tests" { - run $acceptance_test --format summary pretty message junit --tags "@result:FAILED" -- cucumber_cpp/acceptance_test/features + run $acceptance_test --format summary pretty message junit --tags "@smoke and @result:FAILED" -- cucumber_cpp/acceptance_test/features assert_failure } From de69483f2f37953cf6dee53f313a45e2f2565ea2 Mon Sep 17 00:00:00 2001 From: "Timmer, Daan" Date: Fri, 23 Jan 2026 23:12:03 +0000 Subject: [PATCH 196/196] refactor: replace std::forward_as_tuple with std::make_tuple for improved clarity in SourceLocationOrder --- cucumber_cpp/library/support/SupportCodeLibrary.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cucumber_cpp/library/support/SupportCodeLibrary.cpp b/cucumber_cpp/library/support/SupportCodeLibrary.cpp index f0b2a15c..6063b9ea 100644 --- a/cucumber_cpp/library/support/SupportCodeLibrary.cpp +++ b/cucumber_cpp/library/support/SupportCodeLibrary.cpp @@ -24,7 +24,7 @@ namespace cucumber_cpp::library::support { bool SourceLocationOrder::operator()(const std::source_location& lhs, const std::source_location& rhs) const { - return std::forward_as_tuple(lhs.file_name(), lhs.line()) < std::forward_as_tuple(rhs.file_name(), rhs.line()); + return std::make_tuple(std::string_view{ lhs.file_name() }, lhs.line()) < std::make_tuple(std::string_view{ rhs.file_name() }, rhs.line()); } DefinitionRegistration& DefinitionRegistration::Instance()